@hadoku/task-ui-components 4.23.10 → 4.23.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"cosmetics.js","sources":["../../../../../node_modules/.pnpm/@wolffm+prefs-client@1.1.16_react@19.2.8_zod@4.6.7/node_modules/@wolffm/prefs-client/dist/cosmetics.js"],"sourcesContent":["// src/cosmetics-contract.ts\nvar COSMETIC_SLOTS = [\"effect\", \"gradient\", \"font\", \"overlay\", \"message\"];\nvar COSMETIC_ID_PATTERN = /^(effect|gradient|font|overlay|message)\\.[a-z0-9-]{1,32}$/;\nvar item = (slot, name, label, opts = {}) => ({ id: `${slot}.${name}`, slot, label, ...opts });\nvar archived = { hidden: true };\nvar COSMETICS = [\n item(\"effect\", \"glint\", \"Glint\"),\n item(\"effect\", \"aura\", \"Aura\"),\n item(\"effect\", \"hop\", \"Hop\"),\n item(\"effect\", \"outline\", \"Outline\"),\n item(\"effect\", \"twinkle\", \"Twinkle\", { earn: \"Bronze or better in a Major\" }),\n item(\"gradient\", \"gild\", \"Gilded\"),\n item(\"gradient\", \"gloss\", \"Gloss\"),\n item(\"gradient\", \"split\", \"Split\"),\n item(\"gradient\", \"brushed\", \"Brushed\"),\n item(\"gradient\", \"lumen\", \"Lumen\"),\n item(\"font\", \"script\", \"Script\"),\n item(\"font\", \"surf\", \"Surf\"),\n item(\"font\", \"gothic\", \"Gothic\"),\n item(\"font\", \"runic\", \"Runic\"),\n item(\"font\", \"arcade\", \"Arcade\"),\n item(\"font\", \"caps\", \"Small caps\", archived),\n item(\"font\", \"wide\", \"Wide\", archived),\n item(\"font\", \"corsair\", \"Corsair\", archived),\n item(\"font\", \"metal\", \"Metal\", archived),\n item(\"font\", \"comic\", \"Comic\", archived),\n item(\"font\", \"circuit\", \"Circuit\", archived),\n item(\"font\", \"hover\", \"Hover\", archived),\n item(\"font\", \"retro\", \"Retro\", archived),\n item(\"font\", \"pixel\", \"Pixel\", archived),\n item(\"overlay\", \"crown\", \"Crown\", {\n earn: \"Win a Major\",\n art: {\n file: \"crown.webp\",\n top: -40,\n width: 70,\n tilt: -8,\n pixel: false,\n visible: [0.027, 0.137, 0.973, 0.863]\n }\n }),\n item(\"overlay\", \"halo\", \"Halo\", {\n art: {\n file: \"halo.webp\",\n top: -34,\n width: 76,\n tilt: 0,\n pixel: true,\n visible: [0.033, 0.333, 0.967, 0.667]\n }\n }),\n item(\"overlay\", \"flowers\", \"Flower crown\", {\n art: {\n file: \"flowers.webp\",\n top: -22,\n width: 96,\n tilt: 0,\n pixel: false,\n visible: [0.027, 0.258, 0.969, 0.746]\n }\n }),\n item(\"overlay\", \"petals\", \"Petals\"),\n item(\"overlay\", \"horns\", \"Horns\", {\n ...archived,\n art: {\n file: \"horns.webp\",\n top: -26,\n width: 100,\n tilt: 0,\n pixel: true,\n visible: [0.033, 0.133, 0.967, 0.867]\n }\n }),\n item(\"overlay\", \"runes\", \"Runes\", archived),\n item(\"overlay\", \"comet\", \"Comet\", archived),\n item(\"overlay\", \"shine\", \"Shine\", archived),\n item(\"overlay\", \"glow\", \"Glow\", archived),\n item(\"message\", \"type\", \"Typewriter\"),\n item(\"message\", \"decode\", \"Decode\", { earn: \"Silver or better in a Major\" }),\n item(\"message\", \"cascade\", \"Cascade\"),\n item(\"message\", \"words\", \"Word by word\"),\n item(\"message\", \"bubble\", \"Bubble\", archived),\n item(\"message\", \"drift\", \"Drift\")\n];\nvar COSMETICS_BY_ID = new Map(\n COSMETICS.map((i) => [i.id, i])\n);\nvar EARNED_COSMETICS = COSMETICS.filter(\n (i) => i.earn !== void 0 && !i.hidden\n);\nfunction ownedFrom(granted) {\n const out = new Set(COSMETICS.filter((i) => !i.hidden && i.earn === void 0).map((i) => i.id));\n for (const id of granted) {\n const it = COSMETICS_BY_ID.get(id);\n if (it && !it.hidden) out.add(id);\n }\n return out;\n}\nvar PLACEMENT_DEFAULT = { x: 0, y: 0, r: 0, s: 100, flip: false, back: false };\nvar PLACEMENT_LIMITS = {\n x: [-60, 60],\n y: [-60, 100],\n r: [-45, 45],\n s: [70, 140]\n};\nfunction overlayBounds(art, p) {\n const [a, b, c, d] = art.visible;\n const x0 = p.flip ? 1 - c : a;\n const x1 = p.flip ? 1 - a : c;\n const L = 50 - art.width / 2;\n const T = art.top;\n const W = art.width;\n const ox = L + W * 0.5;\n const oy = T + W * 0.6;\n const cx = L + W / 2;\n const cy = T + W / 2;\n const t = art.tilt * Math.PI / 180;\n const r = p.r * Math.PI / 180;\n const sc = p.s / 100;\n const xs = [];\n const ys = [];\n for (const [fx, fy] of [\n [x0, b],\n [x1, b],\n [x0, d],\n [x1, d]\n ]) {\n const px = L + fx * W;\n const py = T + fy * W;\n const tx = cx + (px - cx) * Math.cos(t) - (py - cy) * Math.sin(t);\n const ty = cy + (px - cx) * Math.sin(t) + (py - cy) * Math.cos(t);\n const dx = (tx - ox) * sc;\n const dy = (ty - oy) * sc;\n xs.push(ox + p.x + dx * Math.cos(r) - dy * Math.sin(r));\n ys.push(oy + p.y + dx * Math.sin(r) + dy * Math.cos(r));\n }\n return {\n left: Math.min(...xs),\n top: Math.min(...ys),\n right: Math.max(...xs),\n bottom: Math.max(...ys)\n };\n}\nvar ART_ITEMS = COSMETICS.filter((i) => !!i.art);\nvar PLACEMENT_ZONE = {\n left: -25,\n top: Math.floor(Math.min(...ART_ITEMS.map((i) => overlayBounds(i.art, PLACEMENT_DEFAULT).top))),\n right: 125,\n bottom: 110\n};\nvar clamp = (v, [lo, hi]) => Math.min(hi, Math.max(lo, v));\nfunction fitPlacement(art, raw) {\n const num = (v, fallback) => typeof v === \"number\" && Number.isFinite(v) ? v : fallback;\n const p = {\n x: Math.round(clamp(num(raw.x, 0), PLACEMENT_LIMITS.x)),\n y: Math.round(clamp(num(raw.y, 0), PLACEMENT_LIMITS.y)),\n r: Math.round(clamp(num(raw.r, 0), PLACEMENT_LIMITS.r)),\n s: Math.round(clamp(num(raw.s, 100), PLACEMENT_LIMITS.s)),\n flip: raw.flip === true,\n back: raw.back === true\n };\n const z = PLACEMENT_ZONE;\n let box = overlayBounds(art, p);\n const room = Math.min(\n (z.right - z.left - 2) / (box.right - box.left),\n (z.bottom - z.top - 2) / (box.bottom - box.top)\n );\n if (room < 1) {\n p.s = Math.max(PLACEMENT_LIMITS.s[0], Math.floor(p.s * room));\n box = overlayBounds(art, p);\n }\n p.x += Math.ceil(Math.max(0, z.left - box.left)) - Math.ceil(Math.max(0, box.right - z.right));\n p.y += Math.ceil(Math.max(0, z.top - box.top)) - Math.ceil(Math.max(0, box.bottom - z.bottom));\n return p;\n}\nfunction emptyLoadout() {\n return {\n equipped: { effect: null, gradient: null, font: null, overlay: null, message: null },\n placement: {}\n };\n}\nfunction cleanWorn(raw) {\n if (!raw || typeof raw !== \"object\") return void 0;\n const src = raw;\n const worn = {};\n for (const slot of COSMETIC_SLOTS) {\n const id = src[slot];\n const it = typeof id === \"string\" ? COSMETICS_BY_ID.get(id) : void 0;\n if (it && !it.hidden && it.slot === slot) worn[slot] = it.id;\n }\n const art = worn.overlay ? COSMETICS_BY_ID.get(worn.overlay)?.art : void 0;\n if (art && src.placement && typeof src.placement === \"object\") {\n worn.placement = fitPlacement(art, src.placement);\n }\n return Object.keys(worn).length ? worn : void 0;\n}\nfunction cleanLoadout(raw, owned) {\n const out = emptyLoadout();\n const src = raw && typeof raw === \"object\" ? raw : {};\n for (const slot of COSMETIC_SLOTS) {\n const id = src.equipped?.[slot];\n const it = typeof id === \"string\" ? COSMETICS_BY_ID.get(id) : void 0;\n if (it && !it.hidden && owned.has(it.id) && it.slot === slot) {\n out.equipped[slot] = it.id;\n }\n }\n for (const [id, p] of Object.entries(src.placement ?? {})) {\n const it = COSMETICS_BY_ID.get(id);\n const art = it?.art;\n if (art && !it.hidden && owned.has(id) && p && typeof p === \"object\") {\n out.placement[id] = fitPlacement(art, p);\n }\n }\n return out;\n}\nfunction wornCosmetics(loadout) {\n const worn = {};\n for (const slot of COSMETIC_SLOTS) {\n const id = loadout.equipped[slot];\n if (id) worn[slot] = id;\n }\n const overlay = loadout.equipped.overlay;\n if (overlay && COSMETICS_BY_ID.get(overlay)?.art && loadout.placement[overlay]) {\n worn.placement = loadout.placement[overlay];\n }\n return Object.keys(worn).length ? worn : void 0;\n}\n\n// src/cosmetics.ts\nvar COSMETICS_ASSET_BASE = \"/cosmetics\";\nvar STYLE_ID = \"hadoku-cosmetics\";\nvar esc = (s) => s.replace(\n /[&<>\"']/g,\n (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[c]\n);\nvar nameOf = (id) => id ? id.slice(id.indexOf(\".\") + 1) : \"none\";\nfunction colourStyle(hex) {\n return hex && /^#[0-9a-f]{6}$/i.test(hex) ? `--hc-base:${hex}` : \"\";\n}\nfunction placementTransform(p) {\n const sx = p.s / 100 * (p.flip ? -1 : 1);\n return `translate(calc(var(--hc-sz) * ${(p.x / 100).toFixed(3)}),calc(var(--hc-sz) * ${(p.y / 100).toFixed(3)})) rotate(${p.r}deg) scale(${sx.toFixed(3)},${(p.s / 100).toFixed(3)})`;\n}\nvar SPLIT_EFFECTS = /* @__PURE__ */ new Set([\"hop\"]);\nfunction nameHtml(text, worn, opts = {}) {\n const fx = nameOf(worn?.effect);\n const gr = nameOf(worn?.gradient);\n const font = nameOf(worn?.font);\n const t = esc(text);\n const split = SPLIT_EFFECTS.has(fx);\n const tx = split ? `<span class=\"hc-tx\">${[...text].map(\n (c, i) => `<span class=\"hc-ch\" style=\"--hc-i:${i}\"><span class=\"hc-c\">${c === \" \" ? \"&nbsp;\" : esc(c)}</span></span>`\n ).join(\"\")}</span>` : `<span class=\"hc-tx\">${t}</span>`;\n const layer = (cls2) => `<span class=\"hc-lyr${cls2}\" aria-hidden=\"true\">${t}</span>`;\n let back = \"\";\n let front = \"\";\n if (fx === \"aura\" || fx === \"outline\") back += layer(\" hc-g\");\n if (fx === \"glint\") front += `<span class=\"hc-win\" aria-hidden=\"true\">${layer(\"\")}</span>`;\n if (fx === \"twinkle\") {\n front += [\n [-6, -25, 0],\n [52, 55, 0.95],\n [94, -15, 1.9]\n ].map(\n ([x, y, d]) => `<i class=\"hc-tw\" aria-hidden=\"true\" style=\"left:${x}%;top:${y}%;animation-delay:${d}s\"></i>`\n ).join(\"\");\n }\n const cls = `hc-name hc-fx-${fx} hc-gr-${gr} hc-font-${font}${split ? \" hc-split\" : \"\"}${opts.large ? \" hc-large\" : \"\"}`;\n return `<span class=\"${cls}\">${back}${tx}${front}</span>`;\n}\nvar RUNES_SVG = (() => {\n let marks = \"\";\n for (let i = 0; i < 12; i++) {\n marks += `<g transform=\"rotate(${i * 30} 50 50)\"><path d=\"M50 3 l2.5 4 -2.5 4 -2.5 -4z\" fill=\"currentColor\" opacity=\"${i % 3 ? 0.55 : 1}\"/></g>`;\n }\n return `<svg viewBox=\"0 0 100 100\" aria-hidden=\"true\"><circle cx=\"50\" cy=\"50\" r=\"44\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1\" stroke-dasharray=\"3 5\" opacity=\".7\"/>${marks}</svg>`;\n})();\nfunction decorHtml(worn, assetBase = COSMETICS_ASSET_BASE) {\n const id = worn?.overlay;\n if (!id) return \"\";\n const art = COSMETICS_BY_ID.get(id)?.art;\n if (art) {\n const p = worn?.placement ?? PLACEMENT_DEFAULT;\n return `<span class=\"hc-ol hc-art${art.pixel ? \" hc-px\" : \"\"}\" data-hc-art=\"${esc(id)}\" style=\"left:${50 - art.width / 2}%;top:${art.top}%;width:${art.width}%;z-index:${p.back ? 0 : 3}\"><span class=\"hc-adj\" style=\"transform:${placementTransform(p)}\"><img alt=\"\" draggable=\"false\" style=\"--hc-tilt:${art.tilt}deg\" src=\"${esc(`${assetBase}/art/${art.file}`)}\"></span></span>`;\n }\n switch (nameOf(id)) {\n case \"runes\":\n return `<span class=\"hc-ol hc-runes\">${RUNES_SVG}</span>`;\n case \"comet\":\n return `<span class=\"hc-ol hc-comet\"></span>`;\n case \"shine\":\n return `<span class=\"hc-ol hc-clip\"><span class=\"hc-band\"></span></span>`;\n case \"glow\":\n return `<span class=\"hc-ol hc-glow\"></span>`;\n case \"petals\":\n return [15, 40, 62, 85].map(\n (l, i) => `<i class=\"hc-ol hc-petal\" style=\"left:${l}%;animation-delay:${(i * 0.85).toFixed(2)}s\"></i>`\n ).join(\"\");\n default:\n return \"\";\n }\n}\nfunction avatarHtml(o) {\n const pic = o.src ? `<img alt=\"\" src=\"${esc(o.src)}\">` : \"\";\n return `<span class=\"hc-av\" style=\"--hc-sz:${o.size}px\"><span class=\"hc-pic\">${pic}</span>${decorHtml(o.worn, o.assetBase)}</span>`;\n}\nvar GLYPHS = \"!<>-_/[]{}=+*^?#%&01\";\nvar LETTER_MODES = /* @__PURE__ */ new Set([\"type\", \"decode\", \"cascade\"]);\nvar REVEAL_MS = 1400;\nvar LETTER_LIMIT = 160;\nfunction messageHtml(text, worn, opts = {}) {\n let mode = nameOf(worn?.message);\n if (mode === \"none\") return `<span class=\"hc-msg\">${esc(text)}</span>`;\n const words = text.split(/\\s+/).filter(Boolean);\n if (LETTER_MODES.has(mode) && text.length > LETTER_LIMIT) mode = \"words\";\n const keep = opts.keep ? \" data-hc-keep\" : \"\";\n let body;\n let step;\n if (LETTER_MODES.has(mode)) {\n step = Math.min(38, REVEAL_MS / Math.max(1, text.length));\n let n = 0;\n const rnd = () => GLYPHS[Math.floor(Math.random() * GLYPHS.length)];\n body = words.map(\n (w) => `<span class=\"hc-w\">${[...w].map((c) => {\n const i = n++;\n return mode === \"decode\" ? `<span class=\"hc-ch\" style=\"--hc-i:${i}\" data-g=\"${esc(rnd())}\" data-h=\"${esc(rnd())}\"><span class=\"hc-r\">${esc(c)}</span></span>` : `<span class=\"hc-ch\" style=\"--hc-i:${i}\">${esc(c)}</span>`;\n }).join(\"\")}</span>`\n ).join(\" \");\n if (mode === \"type\") body += `<span class=\"hc-caret\" style=\"--hc-i:${n}\"></span>`;\n const lastClass = mode === \"type\" ? \"hc-caret\" : mode === \"decode\" ? \"hc-r\" : \"hc-ch\";\n const at = body.lastIndexOf(`<span class=\"${lastClass}\"`);\n body = `${body.slice(0, at)}<span data-hc-last class=\"${lastClass}\"${body.slice(at + `<span class=\"${lastClass}\"`.length)}`;\n } else {\n step = Math.min(90, REVEAL_MS / Math.max(1, words.length));\n body = words.map(\n (w, i) => `<span class=\"hc-w\" style=\"--hc-i:${i}\"${i === words.length - 1 ? \" data-hc-last\" : \"\"}>${esc(w)}</span>`\n ).join(\" \");\n }\n return `<span class=\"hc-msg hc-mt-${mode}\" style=\"--hc-step:${step.toFixed(1)}ms\" data-text=\"${esc(text)}\"${keep}><span class=\"hc-sr\">${esc(text)}</span><span aria-hidden=\"true\">${body}</span></span>`;\n}\nfunction finishReveal(e) {\n const t = e.target;\n if (e.pseudoElement || !t?.hasAttribute?.(\"data-hc-last\")) return;\n const msg = t.closest(\".hc-msg\");\n if (!msg || msg.hasAttribute(\"data-hc-keep\")) return;\n msg.className = \"hc-msg\";\n msg.removeAttribute(\"style\");\n msg.textContent = msg.getAttribute(\"data-text\") ?? \"\";\n}\nvar FONTS = {\n script: {\n file: \"caveat\",\n family: \"Caveat\",\n generic: \"cursive\",\n css: \"font-weight:700;font-size:1.3em;line-height:1\"\n },\n surf: {\n file: \"pacifico\",\n family: \"Pacifico\",\n generic: \"cursive\",\n css: \"font-weight:400;font-size:1.02em;line-height:1.1\"\n },\n gothic: {\n file: \"gothic\",\n family: \"Grenze Gotisch\",\n generic: \"serif\",\n css: \"font-weight:600;font-size:1.15em\"\n },\n runic: { file: \"uncial\", family: \"Uncial Antiqua\", generic: \"serif\", css: \"font-weight:400\" },\n corsair: {\n file: \"pirata\",\n family: \"Pirata One\",\n generic: \"serif\",\n css: \"font-weight:400;font-size:1.12em;letter-spacing:.02em\"\n },\n metal: {\n file: \"metal\",\n family: \"Metal Mania\",\n generic: \"cursive\",\n css: \"font-weight:400;font-size:1.12em;letter-spacing:.02em\"\n },\n comic: {\n file: \"bangers\",\n family: \"Bangers\",\n generic: \"cursive\",\n css: \"font-weight:400;font-size:1.15em;letter-spacing:.05em\"\n },\n circuit: {\n file: \"orbitron\",\n family: \"Orbitron\",\n generic: \"sans-serif\",\n css: \"font-weight:700;letter-spacing:.03em\"\n },\n hover: {\n file: \"audiowide\",\n family: \"Audiowide\",\n generic: \"sans-serif\",\n css: \"font-weight:400;font-size:.92em\"\n },\n retro: { file: \"righteous\", family: \"Righteous\", generic: \"sans-serif\", css: \"font-weight:400\" },\n arcade: {\n file: \"bungee\",\n family: \"Bungee\",\n generic: \"sans-serif\",\n css: \"font-weight:400;font-size:.9em\"\n },\n pixel: {\n file: \"pixel\",\n family: \"Press Start 2P\",\n generic: \"monospace\",\n css: \"font-weight:400;font-size:.62em;line-height:1.6\"\n }\n};\nfunction cosmeticsCss(assetBase = COSMETICS_ASSET_BASE) {\n const fontFaces = Object.entries(FONTS).map(\n ([k, f]) => `@font-face{font-family:\"hc-${k}\";src:url(\"${assetBase}/fonts/${f.file}.woff2\") format(\"woff2\");font-display:swap}`\n ).join(\"\\n\");\n const fontRules = Object.entries(FONTS).map(([k, f]) => `.hc-font-${k}{font-family:\"hc-${k}\",\"${f.family}\",${f.generic};${f.css}}`).join(\"\\n\");\n return `${fontFaces}\n/* Colours: everything derives from the character colour. */\n.hc-name,.hc-av,.hc-msg{--hc-b:var(--hc-base,currentColor);--hc-hi:color-mix(in oklch,var(--hc-b),white 50%);--hc-lo:color-mix(in oklch,var(--hc-b),black 50%);--hc-glow:color-mix(in oklch,var(--hc-b) 75%,transparent)}\n\n/* --- name ---\n Bleed: a gradient is a background clipped to the letters, and a background covers only the box; script\n swashes reach outside it. So the text box and every layer over it get room on all sides, cancelled by a\n negative margin so nothing moves. */\n.hc-name{--hc-bx:.4em;--hc-by:.3em;--hc-grad:none;position:relative;display:inline-block;white-space:nowrap;color:var(--hc-b);font-weight:700;line-height:1.25}\n.hc-name.hc-large{font-size:1.6em}\n.hc-name .hc-tx{position:relative;z-index:1;display:inline-block;padding:var(--hc-by) var(--hc-bx);margin:calc(-1 * var(--hc-by)) calc(-1 * var(--hc-bx))}\n.hc-name .hc-lyr{position:absolute;left:calc(-1 * var(--hc-bx));top:calc(-1 * var(--hc-by));padding:var(--hc-by) var(--hc-bx);pointer-events:none;white-space:nowrap}\n.hc-name .hc-win{position:absolute;inset:calc(-1 * var(--hc-by)) calc(-1 * var(--hc-bx));overflow:hidden}\n.hc-name .hc-win .hc-lyr{left:0;top:0}\n.hc-name .hc-ch,.hc-name .hc-c{display:inline-block}\n.hc-gr-gild{--hc-grad:linear-gradient(180deg,var(--hc-hi) 0%,var(--hc-b) 55%,var(--hc-lo) 100%)}\n.hc-gr-gloss{--hc-grad:linear-gradient(180deg,color-mix(in oklch,var(--hc-hi),white 35%) 0%,var(--hc-hi) 36%,color-mix(in oklch,var(--hc-hi),var(--hc-b)) 48%,var(--hc-b) 60%,var(--hc-lo) 100%)}\n.hc-gr-split{--hc-grad:linear-gradient(180deg,var(--hc-hi) 0 52%,var(--hc-b) 52% 100%)}\n.hc-gr-brushed{--hc-grad:repeating-linear-gradient(90deg,transparent 0 1px,color-mix(in oklch,var(--hc-hi) 40%,transparent) 1px 2px),linear-gradient(180deg,var(--hc-hi),var(--hc-b) 55%,var(--hc-lo))}\n.hc-gr-lumen{--hc-grad:radial-gradient(ellipse 60% 90% at 50% 35%,var(--hc-hi),var(--hc-b) 55%,var(--hc-lo) 100%)}\n.hc-name:not(.hc-gr-none) .hc-tx{background:var(--hc-grad);-webkit-background-clip:text;background-clip:text;color:transparent}\n.hc-name.hc-split:not(.hc-gr-none) .hc-tx{background:none}\n.hc-name.hc-split:not(.hc-gr-none) .hc-tx .hc-c{padding:var(--hc-by) var(--hc-bx);margin:calc(-1 * var(--hc-by)) calc(-1 * var(--hc-bx));background:var(--hc-grad);-webkit-background-clip:text;background-clip:text;color:transparent}\n.hc-fx-glint .hc-win{z-index:3;animation:hc-win-go 4.5s cubic-bezier(.4,0,.2,1) infinite;-webkit-mask-image:linear-gradient(100deg,transparent 35%,#000 50%,transparent 65%);mask-image:linear-gradient(100deg,transparent 35%,#000 50%,transparent 65%)}\n.hc-fx-glint .hc-win .hc-lyr{color:var(--hc-hi);animation:hc-win-back 4.5s cubic-bezier(.4,0,.2,1) infinite}\n@keyframes hc-win-go{0%{transform:translateX(-100%)}55%,100%{transform:translateX(100%)}}\n@keyframes hc-win-back{0%{transform:translateX(100%)}55%,100%{transform:translateX(-100%)}}\n.hc-fx-aura .hc-lyr.hc-g{color:transparent;text-shadow:0 0 6px var(--hc-glow),0 0 14px var(--hc-glow);animation:hc-breathe 3s ease-in-out infinite}\n.hc-fx-outline .hc-lyr.hc-g{color:transparent;-webkit-text-stroke:3px var(--hc-hi);animation:hc-breathe 3.6s ease-in-out infinite}\n@keyframes hc-breathe{0%,100%{opacity:.25}50%{opacity:1}}\n.hc-fx-hop .hc-c{animation:hc-hop 3.6s ease-in-out calc(var(--hc-i) * 45ms) infinite}\n@keyframes hc-hop{0%,14%,100%{transform:none}5%{transform:translateY(-4px)}10%{transform:translateY(.5px)}}\n.hc-fx-twinkle .hc-tw{position:absolute;width:7px;height:7px;z-index:3;pointer-events:none;background:linear-gradient(var(--hc-hi),var(--hc-hi)) center/1.4px 100% no-repeat,linear-gradient(90deg,var(--hc-hi),var(--hc-hi)) center/100% 1.4px no-repeat;animation:hc-star 2.8s ease-in-out infinite both}\n@keyframes hc-star{0%,100%{opacity:0;transform:scale(.3) rotate(0deg)}25%{opacity:1;transform:scale(1) rotate(45deg)}50%{opacity:0;transform:scale(.3) rotate(90deg)}}\n.hc-font-caps{font-variant:small-caps;letter-spacing:.08em}\n.hc-font-wide{letter-spacing:.22em;font-weight:600;text-transform:uppercase;font-size:.82em}\n${fontRules}\n\n/* --- avatar --- */\n.hc-av{--hc-sz:40px;position:relative;display:inline-block;width:var(--hc-sz);height:var(--hc-sz);flex:none;isolation:isolate;vertical-align:middle}\n.hc-av .hc-pic{position:absolute;inset:0;border-radius:50%;overflow:hidden;z-index:1;background:color-mix(in oklch,var(--hc-b) 30%,transparent)}\n.hc-av .hc-pic img{width:100%;height:100%;display:block;object-fit:cover;image-rendering:pixelated}\n.hc-av .hc-ol{position:absolute;pointer-events:none}\n.hc-ol.hc-art{display:block}\n.hc-ol.hc-art .hc-adj{display:block;position:relative;transform-origin:50% 60%}\n.hc-ol.hc-art img{display:block;width:100%;height:auto;filter:drop-shadow(0 1px 1px rgba(0,0,0,.45));animation:hc-art-bob 3.4s ease-in-out infinite;-webkit-user-drag:none;user-select:none}\n.hc-ol.hc-art.hc-px img{image-rendering:pixelated}\n@keyframes hc-art-bob{0%,100%{transform:rotate(var(--hc-tilt,0deg)) translateY(0)}50%{transform:rotate(var(--hc-tilt,0deg)) translateY(-2px)}}\n.hc-ol.hc-runes{inset:-9px;z-index:2;color:var(--hc-hi);animation:hc-turn 14s linear infinite}\n.hc-ol.hc-runes svg{width:100%;height:100%;display:block}\n@keyframes hc-turn{to{transform:rotate(360deg)}}\n.hc-ol.hc-comet{inset:-6px;z-index:2;border-radius:50%;background:conic-gradient(from 0deg,transparent 0 270deg,color-mix(in oklch,var(--hc-hi) 10%,transparent) 300deg,var(--hc-hi) 358deg,transparent 360deg);-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - 3px),#000 calc(100% - 3px));mask:radial-gradient(farthest-side,transparent calc(100% - 3px),#000 calc(100% - 3px));animation:hc-turn 2.4s linear infinite}\n.hc-ol.hc-clip{inset:0;border-radius:50%;overflow:hidden;z-index:2}\n.hc-ol .hc-band{position:absolute;top:-30%;left:0;width:45%;height:160%;background:linear-gradient(90deg,transparent,color-mix(in oklch,var(--hc-hi) 70%,transparent),transparent);transform:translateX(-130%) rotate(20deg);animation:hc-band 5s ease-in-out infinite}\n@keyframes hc-band{0%{transform:translateX(-130%) rotate(20deg)}45%,100%{transform:translateX(260%) rotate(20deg)}}\n.hc-ol.hc-glow{inset:-30%;border-radius:50%;z-index:0;background:radial-gradient(circle,var(--hc-glow) 0,transparent 65%);animation:hc-glowp 3.2s ease-in-out infinite}\n@keyframes hc-glowp{0%,100%{opacity:.35;transform:scale(.92)}50%{opacity:.9;transform:scale(1.04)}}\n.hc-ol.hc-petal{top:-8%;height:100%;width:5px;z-index:2;animation:hc-fall 3.4s linear infinite both}\n.hc-ol.hc-petal::after{content:\"\";position:absolute;left:0;top:0;width:5px;height:7px;border-radius:5px 0;background:#f7b6c8;transform:rotate(35deg)}\n@keyframes hc-fall{0%{transform:translate(0,0);opacity:0}12%{opacity:.9}100%{transform:translate(-6px,100%);opacity:0}}\n\n/* --- message reveal: fill backwards only; the page swaps back to plain text when it ends --- */\n.hc-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}\n.hc-msg .hc-w{display:inline-block;white-space:nowrap}\n.hc-msg .hc-ch{display:inline-block}\n.hc-mt-type .hc-ch{animation:hc-in .01s linear calc(var(--hc-i) * var(--hc-step)) backwards}\n.hc-mt-type .hc-caret{display:inline-block;width:2px;height:1em;margin-left:2px;vertical-align:-2px;background:var(--hc-b);opacity:0;animation:hc-caret 1.5s steps(1) calc(var(--hc-i) * var(--hc-step)) backwards}\n@keyframes hc-caret{0%{opacity:0}1%{opacity:1}20%{opacity:0}40%{opacity:1}60%{opacity:0}80%{opacity:1}100%{opacity:0}}\n.hc-mt-cascade .hc-ch{animation:hc-drop .35s cubic-bezier(.3,1.5,.5,1) calc(var(--hc-i) * var(--hc-step)) backwards}\n.hc-mt-decode .hc-ch{position:relative}\n.hc-mt-decode .hc-ch .hc-r{display:inline-block;animation:hc-in .01s linear calc(var(--hc-i) * var(--hc-step) + 260ms) backwards}\n.hc-mt-decode .hc-ch::before,.hc-mt-decode .hc-ch::after{position:absolute;left:0;top:0;color:var(--hc-b);opacity:0;animation:hc-dec-a .26s steps(1) calc(var(--hc-i) * var(--hc-step)) backwards}\n.hc-mt-decode .hc-ch::before{content:attr(data-g)}\n.hc-mt-decode .hc-ch::after{content:attr(data-h);animation-name:hc-dec-b}\n@keyframes hc-dec-a{0%{opacity:.85}50%,100%{opacity:0}}\n@keyframes hc-dec-b{0%{opacity:0}50%{opacity:.85}100%{opacity:0}}\n.hc-mt-words .hc-w{animation:hc-rise .4s ease-out calc(var(--hc-i) * var(--hc-step)) backwards}\n.hc-mt-bubble .hc-w{animation:hc-pop .35s cubic-bezier(.3,1.6,.5,1) calc(var(--hc-i) * var(--hc-step)) backwards}\n.hc-mt-drift .hc-w{animation:hc-slide .45s cubic-bezier(.2,.8,.2,1) calc(var(--hc-i) * var(--hc-step)) backwards}\n@keyframes hc-in{from{opacity:0}to{opacity:1}}\n@keyframes hc-drop{0%{transform:translateY(-10px);opacity:0}100%{transform:none;opacity:1}}\n@keyframes hc-rise{0%{transform:translateY(10px);opacity:0}100%{transform:none;opacity:1}}\n@keyframes hc-pop{0%{transform:scale(.6);opacity:0}100%{transform:scale(1);opacity:1}}\n@keyframes hc-slide{0%{transform:translateX(-18px);opacity:0}100%{transform:none;opacity:1}}\n\n.hc-idle .hc-name,.hc-idle .hc-name *,.hc-idle .hc-av *,.hc-idle .hc-av *::after{animation-play-state:paused!important}\n@media (prefers-reduced-motion:reduce){.hc-name *,.hc-av *,.hc-av *::after,.hc-msg *,.hc-msg *::before,.hc-msg *::after{animation:none!important}}\n`;\n}\nfunction ensureCosmeticsStyles(doc = document, assetBase = COSMETICS_ASSET_BASE) {\n if (doc.getElementById(STYLE_ID)) return;\n const style = doc.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent = cosmeticsCss(assetBase);\n doc.head.appendChild(style);\n doc.addEventListener(\"animationend\", finishReveal);\n}\nexport {\n COSMETICS,\n COSMETICS_ASSET_BASE,\n COSMETICS_BY_ID,\n COSMETIC_ID_PATTERN,\n COSMETIC_SLOTS,\n EARNED_COSMETICS,\n PLACEMENT_DEFAULT,\n PLACEMENT_LIMITS,\n PLACEMENT_ZONE,\n avatarHtml,\n cleanLoadout,\n cleanWorn,\n colourStyle,\n cosmeticsCss,\n decorHtml,\n emptyLoadout,\n ensureCosmeticsStyles,\n finishReveal,\n fitPlacement,\n messageHtml,\n nameHtml,\n overlayBounds,\n ownedFrom,\n placementTransform,\n wornCosmetics\n};\n"],"names":[],"mappings":"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","x_google_ignoreList":[0]}
1
+ {"version":3,"file":"cosmetics.js","sources":["../../../../../node_modules/.pnpm/@wolffm+prefs-client@1.1.18_react@19.2.8_zod@4.6.7/node_modules/@wolffm/prefs-client/dist/cosmetics.js"],"sourcesContent":["// src/cosmetics-contract.ts\nvar COSMETIC_SLOTS = [\"effect\", \"gradient\", \"font\", \"overlay\", \"message\"];\nvar COSMETIC_ID_PATTERN = /^(effect|gradient|font|overlay|message)\\.[a-z0-9-]{1,32}$/;\nvar item = (slot, name, label, opts = {}) => ({ id: `${slot}.${name}`, slot, label, ...opts });\nvar archived = { hidden: true };\nvar COSMETICS = [\n item(\"effect\", \"glint\", \"Glint\"),\n item(\"effect\", \"aura\", \"Aura\"),\n item(\"effect\", \"hop\", \"Hop\"),\n item(\"effect\", \"outline\", \"Outline\"),\n item(\"effect\", \"twinkle\", \"Twinkle\", { earn: \"Bronze or better in a Major\" }),\n item(\"gradient\", \"gild\", \"Gilded\"),\n item(\"gradient\", \"gloss\", \"Gloss\"),\n item(\"gradient\", \"split\", \"Split\"),\n item(\"gradient\", \"brushed\", \"Brushed\"),\n item(\"gradient\", \"lumen\", \"Lumen\"),\n item(\"font\", \"script\", \"Script\"),\n item(\"font\", \"surf\", \"Surf\"),\n item(\"font\", \"gothic\", \"Gothic\"),\n item(\"font\", \"runic\", \"Runic\"),\n item(\"font\", \"arcade\", \"Arcade\"),\n item(\"font\", \"caps\", \"Small caps\", archived),\n item(\"font\", \"wide\", \"Wide\", archived),\n item(\"font\", \"corsair\", \"Corsair\", archived),\n item(\"font\", \"metal\", \"Metal\", archived),\n item(\"font\", \"comic\", \"Comic\", archived),\n item(\"font\", \"circuit\", \"Circuit\", archived),\n item(\"font\", \"hover\", \"Hover\", archived),\n item(\"font\", \"retro\", \"Retro\", archived),\n item(\"font\", \"pixel\", \"Pixel\", archived),\n item(\"overlay\", \"crown\", \"Crown\", {\n earn: \"Win a Major\",\n art: {\n file: \"crown.webp\",\n top: -40,\n width: 70,\n tilt: -8,\n pixel: false,\n visible: [0.027, 0.137, 0.973, 0.863]\n }\n }),\n item(\"overlay\", \"halo\", \"Halo\", {\n art: {\n file: \"halo.webp\",\n top: -34,\n width: 76,\n tilt: 0,\n pixel: true,\n visible: [0.033, 0.333, 0.967, 0.667]\n }\n }),\n item(\"overlay\", \"flowers\", \"Flower crown\", {\n art: {\n file: \"flowers.webp\",\n top: -22,\n width: 96,\n tilt: 0,\n pixel: false,\n visible: [0.027, 0.258, 0.969, 0.746]\n }\n }),\n item(\"overlay\", \"petals\", \"Petals\"),\n item(\"overlay\", \"horns\", \"Horns\", {\n ...archived,\n art: {\n file: \"horns.webp\",\n top: -26,\n width: 100,\n tilt: 0,\n pixel: true,\n visible: [0.033, 0.133, 0.967, 0.867]\n }\n }),\n item(\"overlay\", \"runes\", \"Runes\", archived),\n item(\"overlay\", \"comet\", \"Comet\", archived),\n item(\"overlay\", \"shine\", \"Shine\", archived),\n item(\"overlay\", \"glow\", \"Glow\", archived),\n item(\"message\", \"type\", \"Typewriter\"),\n item(\"message\", \"decode\", \"Decode\", { earn: \"Silver or better in a Major\" }),\n item(\"message\", \"cascade\", \"Cascade\"),\n item(\"message\", \"words\", \"Word by word\"),\n item(\"message\", \"bubble\", \"Bubble\", archived),\n item(\"message\", \"drift\", \"Drift\")\n];\nvar COSMETICS_BY_ID = new Map(\n COSMETICS.map((i) => [i.id, i])\n);\nvar EARNED_COSMETICS = COSMETICS.filter(\n (i) => i.earn !== void 0 && !i.hidden\n);\nfunction ownedFrom(granted) {\n const out = new Set(COSMETICS.filter((i) => !i.hidden && i.earn === void 0).map((i) => i.id));\n for (const id of granted) {\n const it = COSMETICS_BY_ID.get(id);\n if (it && !it.hidden) out.add(id);\n }\n return out;\n}\nvar PLACEMENT_DEFAULT = { x: 0, y: 0, r: 0, s: 100, flip: false, back: false };\nvar PLACEMENT_LIMITS = {\n x: [-60, 60],\n y: [-60, 100],\n r: [-45, 45],\n s: [70, 140]\n};\nfunction overlayBounds(art, p) {\n const [a, b, c, d] = art.visible;\n const x0 = p.flip ? 1 - c : a;\n const x1 = p.flip ? 1 - a : c;\n const L = 50 - art.width / 2;\n const T = art.top;\n const W = art.width;\n const ox = L + W * 0.5;\n const oy = T + W * 0.6;\n const cx = L + W / 2;\n const cy = T + W / 2;\n const t = art.tilt * Math.PI / 180;\n const r = p.r * Math.PI / 180;\n const sc = p.s / 100;\n const xs = [];\n const ys = [];\n for (const [fx, fy] of [\n [x0, b],\n [x1, b],\n [x0, d],\n [x1, d]\n ]) {\n const px = L + fx * W;\n const py = T + fy * W;\n const tx = cx + (px - cx) * Math.cos(t) - (py - cy) * Math.sin(t);\n const ty = cy + (px - cx) * Math.sin(t) + (py - cy) * Math.cos(t);\n const dx = (tx - ox) * sc;\n const dy = (ty - oy) * sc;\n xs.push(ox + p.x + dx * Math.cos(r) - dy * Math.sin(r));\n ys.push(oy + p.y + dx * Math.sin(r) + dy * Math.cos(r));\n }\n return {\n left: Math.min(...xs),\n top: Math.min(...ys),\n right: Math.max(...xs),\n bottom: Math.max(...ys)\n };\n}\nvar ART_ITEMS = COSMETICS.filter((i) => !!i.art);\nvar PLACEMENT_ZONE = {\n left: -25,\n top: Math.floor(Math.min(...ART_ITEMS.map((i) => overlayBounds(i.art, PLACEMENT_DEFAULT).top))),\n right: 125,\n bottom: 110\n};\nvar clamp = (v, [lo, hi]) => Math.min(hi, Math.max(lo, v));\nfunction fitPlacement(art, raw) {\n const num = (v, fallback) => typeof v === \"number\" && Number.isFinite(v) ? v : fallback;\n const p = {\n x: Math.round(clamp(num(raw.x, 0), PLACEMENT_LIMITS.x)),\n y: Math.round(clamp(num(raw.y, 0), PLACEMENT_LIMITS.y)),\n r: Math.round(clamp(num(raw.r, 0), PLACEMENT_LIMITS.r)),\n s: Math.round(clamp(num(raw.s, 100), PLACEMENT_LIMITS.s)),\n flip: raw.flip === true,\n back: raw.back === true\n };\n const z = PLACEMENT_ZONE;\n let box = overlayBounds(art, p);\n const room = Math.min(\n (z.right - z.left - 2) / (box.right - box.left),\n (z.bottom - z.top - 2) / (box.bottom - box.top)\n );\n if (room < 1) {\n p.s = Math.max(PLACEMENT_LIMITS.s[0], Math.floor(p.s * room));\n box = overlayBounds(art, p);\n }\n p.x += Math.ceil(Math.max(0, z.left - box.left)) - Math.ceil(Math.max(0, box.right - z.right));\n p.y += Math.ceil(Math.max(0, z.top - box.top)) - Math.ceil(Math.max(0, box.bottom - z.bottom));\n return p;\n}\nfunction emptyLoadout() {\n return {\n equipped: { effect: null, gradient: null, font: null, overlay: null, message: null },\n placement: {}\n };\n}\nfunction cleanWorn(raw) {\n if (!raw || typeof raw !== \"object\") return void 0;\n const src = raw;\n const worn = {};\n for (const slot of COSMETIC_SLOTS) {\n const id = src[slot];\n const it = typeof id === \"string\" ? COSMETICS_BY_ID.get(id) : void 0;\n if (it && !it.hidden && it.slot === slot) worn[slot] = it.id;\n }\n const art = worn.overlay ? COSMETICS_BY_ID.get(worn.overlay)?.art : void 0;\n if (art && src.placement && typeof src.placement === \"object\") {\n worn.placement = fitPlacement(art, src.placement);\n }\n return Object.keys(worn).length ? worn : void 0;\n}\nfunction cleanLoadout(raw, owned) {\n const out = emptyLoadout();\n const src = raw && typeof raw === \"object\" ? raw : {};\n for (const slot of COSMETIC_SLOTS) {\n const id = src.equipped?.[slot];\n const it = typeof id === \"string\" ? COSMETICS_BY_ID.get(id) : void 0;\n if (it && !it.hidden && owned.has(it.id) && it.slot === slot) {\n out.equipped[slot] = it.id;\n }\n }\n for (const [id, p] of Object.entries(src.placement ?? {})) {\n const it = COSMETICS_BY_ID.get(id);\n const art = it?.art;\n if (art && !it.hidden && owned.has(id) && p && typeof p === \"object\") {\n out.placement[id] = fitPlacement(art, p);\n }\n }\n return out;\n}\nfunction wornCosmetics(loadout) {\n const worn = {};\n for (const slot of COSMETIC_SLOTS) {\n const id = loadout.equipped[slot];\n if (id) worn[slot] = id;\n }\n const overlay = loadout.equipped.overlay;\n if (overlay && COSMETICS_BY_ID.get(overlay)?.art && loadout.placement[overlay]) {\n worn.placement = loadout.placement[overlay];\n }\n return Object.keys(worn).length ? worn : void 0;\n}\n\n// src/picture-colour.ts\nvar MIN_LIGHTNESS = 0.3;\nvar MAX_LIGHTNESS = 0.82;\nvar SAMPLE_SIZE = 24;\nvar MIN_ALPHA = 32;\nfunction averagePictureColour(pixels) {\n let r = 0;\n let g = 0;\n let b = 0;\n let weight = 0;\n for (let i = 0; i + 3 < pixels.length; i += 4) {\n const a = pixels[i + 3];\n if (a < MIN_ALPHA) continue;\n r += pixels[i] * a;\n g += pixels[i + 1] * a;\n b += pixels[i + 2] * a;\n weight += a;\n }\n if (weight === 0) return null;\n return clampLightness(r / weight, g / weight, b / weight);\n}\nfunction clampLightness(r, g, b) {\n const [h, s, l] = rgbToHsl(r / 255, g / 255, b / 255);\n const clamped = Math.min(MAX_LIGHTNESS, Math.max(MIN_LIGHTNESS, l));\n const [r2, g2, b2] = hslToRgb(h, s, clamped);\n const hex = (v) => Math.round(v * 255).toString(16).padStart(2, \"0\");\n return `#${hex(r2)}${hex(g2)}${hex(b2)}`;\n}\nfunction rgbToHsl(r, g, b) {\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n if (max === min) return [0, 0, l];\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n let h;\n if (max === r) h = (g - b) / d + (g < b ? 6 : 0);\n else if (max === g) h = (b - r) / d + 2;\n else h = (r - g) / d + 4;\n return [h / 6, s, l];\n}\nfunction hslToRgb(h, s, l) {\n if (s === 0) return [l, l, l];\n const q = l < 0.5 ? l * (1 + s) : l + s - l * s;\n const p = 2 * l - q;\n const channel = (t) => {\n if (t < 0) t += 1;\n if (t > 1) t -= 1;\n if (t < 1 / 6) return p + (q - p) * 6 * t;\n if (t < 1 / 2) return q;\n if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;\n return p;\n };\n return [channel(h + 1 / 3), channel(h), channel(h - 1 / 3)];\n}\nvar colours = /* @__PURE__ */ new Map();\nvar pending = /* @__PURE__ */ new Map();\nvar listeners = /* @__PURE__ */ new Set();\nfunction pictureColour(url) {\n return colours.get(url);\n}\nfunction onPictureColour(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\nfunction readPictureColour(url) {\n if (colours.has(url)) return Promise.resolve(colours.get(url) ?? null);\n const inFlight = pending.get(url);\n if (inFlight) return inFlight;\n const read = sample(url).then((colour) => {\n pending.delete(url);\n colours.set(url, colour);\n for (const l of listeners) l();\n return colour;\n });\n pending.set(url, read);\n return read;\n}\nfunction sample(url) {\n if (typeof Image === \"undefined\" || typeof document === \"undefined\") {\n return Promise.resolve(null);\n }\n return new Promise((resolve) => {\n const img = new Image();\n img.crossOrigin = \"anonymous\";\n img.referrerPolicy = \"no-referrer\";\n img.onload = () => {\n const canvas = document.createElement(\"canvas\");\n canvas.width = SAMPLE_SIZE;\n canvas.height = SAMPLE_SIZE;\n const ctx = canvas.getContext(\"2d\", { willReadFrequently: true });\n if (!ctx) {\n resolve(null);\n return;\n }\n ctx.drawImage(img, 0, 0, SAMPLE_SIZE, SAMPLE_SIZE);\n try {\n resolve(averagePictureColour(ctx.getImageData(0, 0, SAMPLE_SIZE, SAMPLE_SIZE).data));\n } catch (err) {\n console.debug(\"[prefs-client] picture colour unreadable\", url, err);\n resolve(null);\n }\n };\n img.onerror = () => resolve(null);\n img.src = url;\n });\n}\n\n// src/cosmetics.ts\nvar COSMETICS_ASSET_BASE = \"/cosmetics\";\nvar STYLE_ID = \"hadoku-cosmetics\";\nvar esc = (s) => s.replace(\n /[&<>\"']/g,\n (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[c]\n);\nvar nameOf = (id) => id ? id.slice(id.indexOf(\".\") + 1) : \"none\";\nfunction colourStyle(hex) {\n return hex && /^#[0-9a-f]{6}$/i.test(hex) ? `--hc-base:${hex}` : \"\";\n}\nfunction placementTransform(p) {\n const sx = p.s / 100 * (p.flip ? -1 : 1);\n return `translate(calc(var(--hc-sz) * ${(p.x / 100).toFixed(3)}),calc(var(--hc-sz) * ${(p.y / 100).toFixed(3)})) rotate(${p.r}deg) scale(${sx.toFixed(3)},${(p.s / 100).toFixed(3)})`;\n}\nvar SPLIT_EFFECTS = /* @__PURE__ */ new Set([\"hop\"]);\nfunction nameHtml(text, worn, opts = {}) {\n const fx = nameOf(worn?.effect);\n const gr = nameOf(worn?.gradient);\n const font = nameOf(worn?.font);\n const t = esc(text);\n const split = SPLIT_EFFECTS.has(fx);\n const tx = split ? `<span class=\"hc-tx\">${[...text].map(\n (c, i) => `<span class=\"hc-ch\" style=\"--hc-i:${i}\"><span class=\"hc-c\">${c === \" \" ? \"&nbsp;\" : esc(c)}</span></span>`\n ).join(\"\")}</span>` : `<span class=\"hc-tx\">${t}</span>`;\n const layer = (cls2) => `<span class=\"hc-lyr${cls2}\" aria-hidden=\"true\">${t}</span>`;\n let back = \"\";\n let front = \"\";\n if (fx === \"aura\" || fx === \"outline\") back += layer(\" hc-g\");\n if (fx === \"glint\") front += `<span class=\"hc-win\" aria-hidden=\"true\">${layer(\"\")}</span>`;\n if (fx === \"twinkle\") {\n front += [\n [-6, -25, 0],\n [52, 55, 0.95],\n [94, -15, 1.9]\n ].map(\n ([x, y, d]) => `<i class=\"hc-tw\" aria-hidden=\"true\" style=\"left:${x}%;top:${y}%;animation-delay:${d}s\"></i>`\n ).join(\"\");\n }\n const cls = `hc-name hc-fx-${fx} hc-gr-${gr} hc-font-${font}${split ? \" hc-split\" : \"\"}${opts.large ? \" hc-large\" : \"\"}`;\n return `<span class=\"${cls}\">${back}${tx}${front}</span>`;\n}\nvar RUNES_SVG = (() => {\n let marks = \"\";\n for (let i = 0; i < 12; i++) {\n marks += `<g transform=\"rotate(${i * 30} 50 50)\"><path d=\"M50 3 l2.5 4 -2.5 4 -2.5 -4z\" fill=\"currentColor\" opacity=\"${i % 3 ? 0.55 : 1}\"/></g>`;\n }\n return `<svg viewBox=\"0 0 100 100\" aria-hidden=\"true\"><circle cx=\"50\" cy=\"50\" r=\"44\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1\" stroke-dasharray=\"3 5\" opacity=\".7\"/>${marks}</svg>`;\n})();\nfunction decorHtml(worn, assetBase = COSMETICS_ASSET_BASE) {\n const id = worn?.overlay;\n if (!id) return \"\";\n const art = COSMETICS_BY_ID.get(id)?.art;\n if (art) {\n const p = worn?.placement ?? PLACEMENT_DEFAULT;\n return `<span class=\"hc-ol hc-art${art.pixel ? \" hc-px\" : \"\"}\" data-hc-art=\"${esc(id)}\" style=\"left:${50 - art.width / 2}%;top:${art.top}%;width:${art.width}%;z-index:${p.back ? 0 : 3}\"><span class=\"hc-adj\" style=\"transform:${placementTransform(p)}\"><img alt=\"\" draggable=\"false\" style=\"--hc-tilt:${art.tilt}deg\" src=\"${esc(`${assetBase}/art/${art.file}`)}\"></span></span>`;\n }\n switch (nameOf(id)) {\n case \"runes\":\n return `<span class=\"hc-ol hc-runes\">${RUNES_SVG}</span>`;\n case \"comet\":\n return `<span class=\"hc-ol hc-comet\"></span>`;\n case \"shine\":\n return `<span class=\"hc-ol hc-clip\"><span class=\"hc-band\"></span></span>`;\n case \"glow\":\n return `<span class=\"hc-ol hc-glow\"></span>`;\n case \"petals\":\n return [15, 40, 62, 85].map(\n (l, i) => `<i class=\"hc-ol hc-petal\" style=\"left:${l}%;animation-delay:${(i * 0.85).toFixed(2)}s\"></i>`\n ).join(\"\");\n default:\n return \"\";\n }\n}\nfunction avatarHtml(o) {\n const pic = o.src ? `<img alt=\"\" src=\"${esc(o.src)}\">` : \"\";\n return `<span class=\"hc-av\" style=\"--hc-sz:${o.size}px\"><span class=\"hc-pic\">${pic}</span>${decorHtml(o.worn, o.assetBase)}</span>`;\n}\nvar GLYPHS = \"!<>-_/[]{}=+*^?#%&01\";\nvar LETTER_MODES = /* @__PURE__ */ new Set([\"type\", \"decode\", \"cascade\"]);\nvar REVEAL_MS = 1400;\nvar LETTER_LIMIT = 160;\nfunction messageHtml(text, worn, opts = {}) {\n let mode = nameOf(worn?.message);\n if (mode === \"none\") return `<span class=\"hc-msg\">${esc(text)}</span>`;\n const words = text.split(/\\s+/).filter(Boolean);\n if (LETTER_MODES.has(mode) && text.length > LETTER_LIMIT) mode = \"words\";\n const keep = opts.keep ? \" data-hc-keep\" : \"\";\n let body;\n let step;\n if (LETTER_MODES.has(mode)) {\n step = Math.min(38, REVEAL_MS / Math.max(1, text.length));\n let n = 0;\n const rnd = () => GLYPHS[Math.floor(Math.random() * GLYPHS.length)];\n body = words.map(\n (w) => `<span class=\"hc-w\">${[...w].map((c) => {\n const i = n++;\n return mode === \"decode\" ? `<span class=\"hc-ch\" style=\"--hc-i:${i}\" data-g=\"${esc(rnd())}\" data-h=\"${esc(rnd())}\"><span class=\"hc-r\">${esc(c)}</span></span>` : `<span class=\"hc-ch\" style=\"--hc-i:${i}\">${esc(c)}</span>`;\n }).join(\"\")}</span>`\n ).join(\" \");\n if (mode === \"type\") body += `<span class=\"hc-caret\" style=\"--hc-i:${n}\"></span>`;\n const lastClass = mode === \"type\" ? \"hc-caret\" : mode === \"decode\" ? \"hc-r\" : \"hc-ch\";\n const at = body.lastIndexOf(`<span class=\"${lastClass}\"`);\n body = `${body.slice(0, at)}<span data-hc-last class=\"${lastClass}\"${body.slice(at + `<span class=\"${lastClass}\"`.length)}`;\n } else {\n step = Math.min(90, REVEAL_MS / Math.max(1, words.length));\n body = words.map(\n (w, i) => `<span class=\"hc-w\" style=\"--hc-i:${i}\"${i === words.length - 1 ? \" data-hc-last\" : \"\"}>${esc(w)}</span>`\n ).join(\" \");\n }\n return `<span class=\"hc-msg hc-mt-${mode}\" style=\"--hc-step:${step.toFixed(1)}ms\" data-text=\"${esc(text)}\"${keep}><span class=\"hc-sr\">${esc(text)}</span><span aria-hidden=\"true\">${body}</span></span>`;\n}\nfunction finishReveal(e) {\n const t = e.target;\n if (e.pseudoElement || !t?.hasAttribute?.(\"data-hc-last\")) return;\n const msg = t.closest(\".hc-msg\");\n if (!msg || msg.hasAttribute(\"data-hc-keep\")) return;\n msg.className = \"hc-msg\";\n msg.removeAttribute(\"style\");\n msg.textContent = msg.getAttribute(\"data-text\") ?? \"\";\n}\nvar FONTS = {\n script: {\n file: \"caveat\",\n family: \"Caveat\",\n generic: \"cursive\",\n css: \"font-weight:700;font-size:1.3em;line-height:1\"\n },\n surf: {\n file: \"pacifico\",\n family: \"Pacifico\",\n generic: \"cursive\",\n css: \"font-weight:400;font-size:1.02em;line-height:1.1\"\n },\n gothic: {\n file: \"gothic\",\n family: \"Grenze Gotisch\",\n generic: \"serif\",\n css: \"font-weight:600;font-size:1.15em\"\n },\n runic: { file: \"uncial\", family: \"Uncial Antiqua\", generic: \"serif\", css: \"font-weight:400\" },\n corsair: {\n file: \"pirata\",\n family: \"Pirata One\",\n generic: \"serif\",\n css: \"font-weight:400;font-size:1.12em;letter-spacing:.02em\"\n },\n metal: {\n file: \"metal\",\n family: \"Metal Mania\",\n generic: \"cursive\",\n css: \"font-weight:400;font-size:1.12em;letter-spacing:.02em\"\n },\n comic: {\n file: \"bangers\",\n family: \"Bangers\",\n generic: \"cursive\",\n css: \"font-weight:400;font-size:1.15em;letter-spacing:.05em\"\n },\n circuit: {\n file: \"orbitron\",\n family: \"Orbitron\",\n generic: \"sans-serif\",\n css: \"font-weight:700;letter-spacing:.03em\"\n },\n hover: {\n file: \"audiowide\",\n family: \"Audiowide\",\n generic: \"sans-serif\",\n css: \"font-weight:400;font-size:.92em\"\n },\n retro: { file: \"righteous\", family: \"Righteous\", generic: \"sans-serif\", css: \"font-weight:400\" },\n arcade: {\n file: \"bungee\",\n family: \"Bungee\",\n generic: \"sans-serif\",\n css: \"font-weight:400;font-size:.9em\"\n },\n pixel: {\n file: \"pixel\",\n family: \"Press Start 2P\",\n generic: \"monospace\",\n css: \"font-weight:400;font-size:.62em;line-height:1.6\"\n }\n};\nfunction cosmeticsCss(assetBase = COSMETICS_ASSET_BASE) {\n const fontFaces = Object.entries(FONTS).map(\n ([k, f]) => `@font-face{font-family:\"hc-${k}\";src:url(\"${assetBase}/fonts/${f.file}.woff2\") format(\"woff2\");font-display:swap}`\n ).join(\"\\n\");\n const fontRules = Object.entries(FONTS).map(([k, f]) => `.hc-font-${k}{font-family:\"hc-${k}\",\"${f.family}\",${f.generic};${f.css}}`).join(\"\\n\");\n return `${fontFaces}\n/* Colours: everything derives from the character colour. */\n.hc-name,.hc-av,.hc-msg{--hc-b:var(--hc-base,currentColor);--hc-hi:color-mix(in oklch,var(--hc-b),white 50%);--hc-lo:color-mix(in oklch,var(--hc-b),black 50%);--hc-glow:color-mix(in oklch,var(--hc-b) 75%,transparent)}\n\n/* --- name ---\n Bleed: a gradient is a background clipped to the letters, and a background covers only the box; script\n swashes reach outside it. So the text box and every layer over it get room on all sides, cancelled by a\n negative margin so nothing moves. */\n.hc-name{--hc-bx:.4em;--hc-by:.3em;--hc-grad:none;position:relative;display:inline-block;white-space:nowrap;color:var(--hc-b);font-weight:700;line-height:1.25}\n.hc-name.hc-large{font-size:1.6em}\n.hc-name .hc-tx{position:relative;z-index:1;display:inline-block;padding:var(--hc-by) var(--hc-bx);margin:calc(-1 * var(--hc-by)) calc(-1 * var(--hc-bx))}\n.hc-name .hc-lyr{position:absolute;left:calc(-1 * var(--hc-bx));top:calc(-1 * var(--hc-by));padding:var(--hc-by) var(--hc-bx);pointer-events:none;white-space:nowrap}\n.hc-name .hc-win{position:absolute;inset:calc(-1 * var(--hc-by)) calc(-1 * var(--hc-bx));overflow:hidden}\n.hc-name .hc-win .hc-lyr{left:0;top:0}\n.hc-name .hc-ch,.hc-name .hc-c{display:inline-block}\n.hc-gr-gild{--hc-grad:linear-gradient(180deg,var(--hc-hi) 0%,var(--hc-b) 55%,var(--hc-lo) 100%)}\n.hc-gr-gloss{--hc-grad:linear-gradient(180deg,color-mix(in oklch,var(--hc-hi),white 35%) 0%,var(--hc-hi) 36%,color-mix(in oklch,var(--hc-hi),var(--hc-b)) 48%,var(--hc-b) 60%,var(--hc-lo) 100%)}\n.hc-gr-split{--hc-grad:linear-gradient(180deg,var(--hc-hi) 0 52%,var(--hc-b) 52% 100%)}\n.hc-gr-brushed{--hc-grad:repeating-linear-gradient(90deg,transparent 0 1px,color-mix(in oklch,var(--hc-hi) 40%,transparent) 1px 2px),linear-gradient(180deg,var(--hc-hi),var(--hc-b) 55%,var(--hc-lo))}\n.hc-gr-lumen{--hc-grad:radial-gradient(ellipse 60% 90% at 50% 35%,var(--hc-hi),var(--hc-b) 55%,var(--hc-lo) 100%)}\n.hc-name:not(.hc-gr-none) .hc-tx{background:var(--hc-grad);-webkit-background-clip:text;background-clip:text;color:transparent}\n.hc-name.hc-split:not(.hc-gr-none) .hc-tx{background:none}\n.hc-name.hc-split:not(.hc-gr-none) .hc-tx .hc-c{padding:var(--hc-by) var(--hc-bx);margin:calc(-1 * var(--hc-by)) calc(-1 * var(--hc-bx));background:var(--hc-grad);-webkit-background-clip:text;background-clip:text;color:transparent}\n.hc-fx-glint .hc-win{z-index:3;animation:hc-win-go 4.5s cubic-bezier(.4,0,.2,1) infinite;-webkit-mask-image:linear-gradient(100deg,transparent 35%,#000 50%,transparent 65%);mask-image:linear-gradient(100deg,transparent 35%,#000 50%,transparent 65%)}\n.hc-fx-glint .hc-win .hc-lyr{color:var(--hc-hi);animation:hc-win-back 4.5s cubic-bezier(.4,0,.2,1) infinite}\n@keyframes hc-win-go{0%{transform:translateX(-100%)}55%,100%{transform:translateX(100%)}}\n@keyframes hc-win-back{0%{transform:translateX(100%)}55%,100%{transform:translateX(-100%)}}\n.hc-fx-aura .hc-lyr.hc-g{color:transparent;text-shadow:0 0 6px var(--hc-glow),0 0 14px var(--hc-glow);animation:hc-breathe 3s ease-in-out infinite}\n.hc-fx-outline .hc-lyr.hc-g{color:transparent;-webkit-text-stroke:3px var(--hc-hi);animation:hc-breathe 3.6s ease-in-out infinite}\n@keyframes hc-breathe{0%,100%{opacity:.25}50%{opacity:1}}\n.hc-fx-hop .hc-c{animation:hc-hop 3.6s ease-in-out calc(var(--hc-i) * 45ms) infinite}\n@keyframes hc-hop{0%,14%,100%{transform:none}5%{transform:translateY(-4px)}10%{transform:translateY(.5px)}}\n.hc-fx-twinkle .hc-tw{position:absolute;width:7px;height:7px;z-index:3;pointer-events:none;background:linear-gradient(var(--hc-hi),var(--hc-hi)) center/1.4px 100% no-repeat,linear-gradient(90deg,var(--hc-hi),var(--hc-hi)) center/100% 1.4px no-repeat;animation:hc-star 2.8s ease-in-out infinite both}\n@keyframes hc-star{0%,100%{opacity:0;transform:scale(.3) rotate(0deg)}25%{opacity:1;transform:scale(1) rotate(45deg)}50%{opacity:0;transform:scale(.3) rotate(90deg)}}\n.hc-font-caps{font-variant:small-caps;letter-spacing:.08em}\n.hc-font-wide{letter-spacing:.22em;font-weight:600;text-transform:uppercase;font-size:.82em}\n${fontRules}\n\n/* --- avatar --- */\n.hc-av{--hc-sz:40px;position:relative;display:inline-block;width:var(--hc-sz);height:var(--hc-sz);flex:none;isolation:isolate;vertical-align:middle}\n.hc-av .hc-pic{position:absolute;inset:0;border-radius:50%;overflow:hidden;z-index:1;background:color-mix(in oklch,var(--hc-b) 30%,transparent)}\n.hc-av .hc-pic img{width:100%;height:100%;display:block;object-fit:cover;image-rendering:pixelated}\n.hc-av .hc-ol{position:absolute;pointer-events:none}\n.hc-ol.hc-art{display:block}\n.hc-ol.hc-art .hc-adj{display:block;position:relative;transform-origin:50% 60%}\n.hc-ol.hc-art img{display:block;width:100%;height:auto;filter:drop-shadow(0 1px 1px rgba(0,0,0,.45));animation:hc-art-bob 3.4s ease-in-out infinite;-webkit-user-drag:none;user-select:none}\n.hc-ol.hc-art.hc-px img{image-rendering:pixelated}\n@keyframes hc-art-bob{0%,100%{transform:rotate(var(--hc-tilt,0deg)) translateY(0)}50%{transform:rotate(var(--hc-tilt,0deg)) translateY(-2px)}}\n.hc-ol.hc-runes{inset:-9px;z-index:2;color:var(--hc-hi);animation:hc-turn 14s linear infinite}\n.hc-ol.hc-runes svg{width:100%;height:100%;display:block}\n@keyframes hc-turn{to{transform:rotate(360deg)}}\n.hc-ol.hc-comet{inset:-6px;z-index:2;border-radius:50%;background:conic-gradient(from 0deg,transparent 0 270deg,color-mix(in oklch,var(--hc-hi) 10%,transparent) 300deg,var(--hc-hi) 358deg,transparent 360deg);-webkit-mask:radial-gradient(farthest-side,transparent calc(100% - 3px),#000 calc(100% - 3px));mask:radial-gradient(farthest-side,transparent calc(100% - 3px),#000 calc(100% - 3px));animation:hc-turn 2.4s linear infinite}\n.hc-ol.hc-clip{inset:0;border-radius:50%;overflow:hidden;z-index:2}\n.hc-ol .hc-band{position:absolute;top:-30%;left:0;width:45%;height:160%;background:linear-gradient(90deg,transparent,color-mix(in oklch,var(--hc-hi) 70%,transparent),transparent);transform:translateX(-130%) rotate(20deg);animation:hc-band 5s ease-in-out infinite}\n@keyframes hc-band{0%{transform:translateX(-130%) rotate(20deg)}45%,100%{transform:translateX(260%) rotate(20deg)}}\n.hc-ol.hc-glow{inset:-30%;border-radius:50%;z-index:0;background:radial-gradient(circle,var(--hc-glow) 0,transparent 65%);animation:hc-glowp 3.2s ease-in-out infinite}\n@keyframes hc-glowp{0%,100%{opacity:.35;transform:scale(.92)}50%{opacity:.9;transform:scale(1.04)}}\n.hc-ol.hc-petal{top:-8%;height:100%;width:5px;z-index:2;animation:hc-fall 3.4s linear infinite both}\n.hc-ol.hc-petal::after{content:\"\";position:absolute;left:0;top:0;width:5px;height:7px;border-radius:5px 0;background:#f7b6c8;transform:rotate(35deg)}\n@keyframes hc-fall{0%{transform:translate(0,0);opacity:0}12%{opacity:.9}100%{transform:translate(-6px,100%);opacity:0}}\n\n/* --- message reveal: fill backwards only; the page swaps back to plain text when it ends --- */\n.hc-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}\n.hc-msg .hc-w{display:inline-block;white-space:nowrap}\n.hc-msg .hc-ch{display:inline-block}\n.hc-mt-type .hc-ch{animation:hc-in .01s linear calc(var(--hc-i) * var(--hc-step)) backwards}\n.hc-mt-type .hc-caret{display:inline-block;width:2px;height:1em;margin-left:2px;vertical-align:-2px;background:var(--hc-b);opacity:0;animation:hc-caret 1.5s steps(1) calc(var(--hc-i) * var(--hc-step)) backwards}\n@keyframes hc-caret{0%{opacity:0}1%{opacity:1}20%{opacity:0}40%{opacity:1}60%{opacity:0}80%{opacity:1}100%{opacity:0}}\n.hc-mt-cascade .hc-ch{animation:hc-drop .35s cubic-bezier(.3,1.5,.5,1) calc(var(--hc-i) * var(--hc-step)) backwards}\n.hc-mt-decode .hc-ch{position:relative}\n.hc-mt-decode .hc-ch .hc-r{display:inline-block;animation:hc-in .01s linear calc(var(--hc-i) * var(--hc-step) + 260ms) backwards}\n.hc-mt-decode .hc-ch::before,.hc-mt-decode .hc-ch::after{position:absolute;left:0;top:0;color:var(--hc-b);opacity:0;animation:hc-dec-a .26s steps(1) calc(var(--hc-i) * var(--hc-step)) backwards}\n.hc-mt-decode .hc-ch::before{content:attr(data-g)}\n.hc-mt-decode .hc-ch::after{content:attr(data-h);animation-name:hc-dec-b}\n@keyframes hc-dec-a{0%{opacity:.85}50%,100%{opacity:0}}\n@keyframes hc-dec-b{0%{opacity:0}50%{opacity:.85}100%{opacity:0}}\n.hc-mt-words .hc-w{animation:hc-rise .4s ease-out calc(var(--hc-i) * var(--hc-step)) backwards}\n.hc-mt-bubble .hc-w{animation:hc-pop .35s cubic-bezier(.3,1.6,.5,1) calc(var(--hc-i) * var(--hc-step)) backwards}\n.hc-mt-drift .hc-w{animation:hc-slide .45s cubic-bezier(.2,.8,.2,1) calc(var(--hc-i) * var(--hc-step)) backwards}\n@keyframes hc-in{from{opacity:0}to{opacity:1}}\n@keyframes hc-drop{0%{transform:translateY(-10px);opacity:0}100%{transform:none;opacity:1}}\n@keyframes hc-rise{0%{transform:translateY(10px);opacity:0}100%{transform:none;opacity:1}}\n@keyframes hc-pop{0%{transform:scale(.6);opacity:0}100%{transform:scale(1);opacity:1}}\n@keyframes hc-slide{0%{transform:translateX(-18px);opacity:0}100%{transform:none;opacity:1}}\n\n.hc-idle .hc-name,.hc-idle .hc-name *,.hc-idle .hc-av *,.hc-idle .hc-av *::after{animation-play-state:paused!important}\n@media (prefers-reduced-motion:reduce){.hc-name *,.hc-av *,.hc-av *::after,.hc-msg *,.hc-msg *::before,.hc-msg *::after{animation:none!important}}\n`;\n}\nfunction ensureCosmeticsStyles(doc = document, assetBase = COSMETICS_ASSET_BASE) {\n if (doc.getElementById(STYLE_ID)) return;\n const style = doc.createElement(\"style\");\n style.id = STYLE_ID;\n style.textContent = cosmeticsCss(assetBase);\n doc.head.appendChild(style);\n doc.addEventListener(\"animationend\", finishReveal);\n}\nexport {\n COSMETICS,\n COSMETICS_ASSET_BASE,\n COSMETICS_BY_ID,\n COSMETIC_ID_PATTERN,\n COSMETIC_SLOTS,\n EARNED_COSMETICS,\n PLACEMENT_DEFAULT,\n PLACEMENT_LIMITS,\n PLACEMENT_ZONE,\n avatarHtml,\n averagePictureColour,\n cleanLoadout,\n cleanWorn,\n colourStyle,\n cosmeticsCss,\n decorHtml,\n emptyLoadout,\n ensureCosmeticsStyles,\n finishReveal,\n fitPlacement,\n messageHtml,\n nameHtml,\n onPictureColour,\n overlayBounds,\n ownedFrom,\n pictureColour,\n placementTransform,\n readPictureColour,\n wornCosmetics\n};\n"],"names":[],"mappings":"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","x_google_ignoreList":[0]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hadoku/task-ui-components",
3
- "version": "4.23.10",
4
- "gitHead": "ead000880dc9819230c581dc6ed3abe2ed0fd556",
3
+ "version": "4.23.12",
4
+ "gitHead": "588edd74c0ffe50241cf2e47e0c3862403e3cd1a",
5
5
  "description": "Reusable UI components with Bento grid and metallic gradient support",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -18,9 +18,17 @@
18
18
  *
19
19
  * Styles are inline for the reason AvatarPicker's are: apps compile this
20
20
  * package's CSS at their own build. Tokens only; the cosmetics' own colours come
21
- * from the person's character colour (and, for overlay images, the art).
21
+ * from the person's colour, their picture's average or else their character's
22
+ * (and, for overlay images, the art).
22
23
  */
23
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
24
+ import React, {
25
+ useCallback,
26
+ useEffect,
27
+ useMemo,
28
+ useRef,
29
+ useState,
30
+ useSyncExternalStore
31
+ } from 'react'
24
32
  import { Icon } from '@wolffm/themes'
25
33
  import type { AvatarSettings, LoadoutUpdate } from '@wolffm/prefs-client/avatar'
26
34
  import {
@@ -35,7 +43,10 @@ import {
35
43
  fitPlacement,
36
44
  messageHtml,
37
45
  nameHtml,
46
+ onPictureColour,
47
+ pictureColour,
38
48
  placementTransform,
49
+ readPictureColour,
39
50
  wornCosmetics,
40
51
  type AvatarCosmetics,
41
52
  type CosmeticSlot,
@@ -344,6 +355,22 @@ function useDragScroll<T extends HTMLElement>() {
344
355
  return setEl
345
356
  }
346
357
 
358
+ /**
359
+ * The colour a picture gives a person's name: its average, read once
360
+ * (@wolffm/prefs-client, the same reader watchparty's chat uses). Null for no
361
+ * picture, or while it is unread or unreadable.
362
+ */
363
+ function usePictureColour(url: string | null): string | null {
364
+ useEffect(() => {
365
+ if (url) void readPictureColour(url)
366
+ }, [url])
367
+ return useSyncExternalStore(
368
+ onPictureColour,
369
+ () => (url ? (pictureColour(url) ?? null) : null),
370
+ () => null
371
+ )
372
+ }
373
+
347
374
  export interface AvatarBuilderProps {
348
375
  /** The person's display name, for the preview. */
349
376
  name: string | null
@@ -358,7 +385,6 @@ export function AvatarBuilder({ name, isAdmin = false }: AvatarBuilderProps) {
358
385
  const [error, setError] = useState<string | null>(null)
359
386
  const [tab, setTab] = useState<Tab>('picture')
360
387
  const [tryOn, setTryOn] = useState<{ slot: CosmeticSlot; id: string } | null>(null)
361
- const [showLocked, setShowLocked] = useState(true)
362
388
  const [replay, setReplay] = useState(0)
363
389
  const narrow = useNarrow()
364
390
  const root = useRef<HTMLDivElement>(null)
@@ -368,6 +394,8 @@ export function AvatarBuilder({ name, isAdmin = false }: AvatarBuilderProps) {
368
394
  const seq = useRef(0)
369
395
 
370
396
  useEffect(() => ensureCosmeticsStyles(), [])
397
+ // A Discord, Steam or uploaded picture colours the name; a character, its own colour.
398
+ const tint = usePictureColour(settings?.avatar.url ?? null)
371
399
 
372
400
  useEffect(() => {
373
401
  let live = true
@@ -475,11 +503,11 @@ export function AvatarBuilder({ name, isAdmin = false }: AvatarBuilderProps) {
475
503
  const characters = new Map((sets ?? []).flatMap(s => s.characters).map(c => [c.id, c]))
476
504
  const character = settings.sprite ? characters.get(settings.sprite) : undefined
477
505
  const picture = settings.avatar.url ?? (character ? portraitUrl(character) : null)
478
- // Every name cosmetic derives its colours from the character's.
479
- const colour: React.CSSProperties =
480
- character && /^#[0-9a-f]{6}$/i.test(character.color)
481
- ? ({ '--hc-base': character.color } as React.CSSProperties)
482
- : {}
506
+ // Every name cosmetic derives its colours from the person's: the picture's
507
+ // average, else the character's, as the chat draws them.
508
+ const base =
509
+ tint ?? (character && /^#[0-9a-f]{6}$/i.test(character.color) ? character.color : null)
510
+ const colour: React.CSSProperties = base ? ({ '--hc-base': base } as React.CSSProperties) : {}
483
511
  const displayName = name || 'You'
484
512
 
485
513
  const worn: AvatarCosmetics = { ...(settings.avatar.cosmetics ?? {}) }
@@ -656,8 +684,6 @@ export function AvatarBuilder({ name, isAdmin = false }: AvatarBuilderProps) {
656
684
  owned={owned}
657
685
  equipped={loadout?.equipped[tab] ?? null}
658
686
  tryOn={tryOn}
659
- showLocked={showLocked}
660
- onShowLocked={setShowLocked}
661
687
  isAdmin={isAdmin}
662
688
  preview={id => {
663
689
  const only: AvatarCosmetics = { [tab]: id ?? undefined }
@@ -817,8 +843,6 @@ interface SlotGridProps {
817
843
  owned: ReadonlySet<string>
818
844
  equipped: string | null
819
845
  tryOn: { slot: CosmeticSlot; id: string } | null
820
- showLocked: boolean
821
- onShowLocked: (on: boolean) => void
822
846
  /** Admins see archived items, greyed and not selectable. */
823
847
  isAdmin: boolean
824
848
  preview: (id: string | null) => string
@@ -836,8 +860,6 @@ function SlotGrid({
836
860
  owned,
837
861
  equipped,
838
862
  tryOn,
839
- showLocked,
840
- onShowLocked,
841
863
  isAdmin,
842
864
  preview,
843
865
  colour,
@@ -846,7 +868,6 @@ function SlotGrid({
846
868
  }: SlotGridProps) {
847
869
  const items = COSMETICS.filter(i => i.slot === slot && (!i.hidden || isAdmin))
848
870
  const locked = items.filter(i => !i.hidden && !owned.has(i.id))
849
- const shown = showLocked ? items : items.filter(i => i.hidden || owned.has(i.id))
850
871
 
851
872
  return (
852
873
  <>
@@ -860,7 +881,7 @@ function SlotGrid({
860
881
  <Html html={preview(null)} style={{ ...S.tilePv, ...colour }} />
861
882
  <span style={S.tileName}>None</span>
862
883
  </button>
863
- {shown.map(item => {
884
+ {items.map(item => {
864
885
  if (item.hidden) {
865
886
  return (
866
887
  <button
@@ -884,7 +905,9 @@ function SlotGrid({
884
905
  key={item.id}
885
906
  type="button"
886
907
  aria-pressed={own ? on : trying}
887
- aria-label={own ? item.label : `${item.label}, locked. ${item.earn ?? ''}. Try it on.`}
908
+ aria-label={
909
+ own ? item.label : `${item.label}, locked. ${item.earn ?? ''}. Try it on.`
910
+ }
888
911
  style={{
889
912
  ...S.tile,
890
913
  ...(on ? S.tileOn : {}),
@@ -913,14 +936,6 @@ function SlotGrid({
913
936
  {locked.length === 1 ? 'One to earn.' : `${locked.length} to earn.`} Pick one to try it
914
937
  on.
915
938
  </span>
916
- <label>
917
- <input
918
- type="checkbox"
919
- checked={showLocked}
920
- onChange={e => onShowLocked(e.currentTarget.checked)}
921
- />{' '}
922
- Show locked
923
- </label>
924
939
  </div>
925
940
  )}
926
941
  </>
@@ -128,6 +128,11 @@ export function AvatarPicker({ settings, sets, busy, run }: AvatarPickerProps) {
128
128
  }
129
129
  const shown = settings.avatar
130
130
  const current = sets?.[setIdx]
131
+ // The tab on show. Picking an available source also wears it; Upload opens
132
+ // with nothing uploaded yet, to choose a file.
133
+ const [view, setView] = useState<AvatarSource>(shown.source)
134
+ useEffect(() => setView(shown.source), [shown.source])
135
+ const openable = (src: AvatarSource) => src === 'upload' || available[src]
131
136
 
132
137
  return (
133
138
  <div style={S.stack}>
@@ -143,22 +148,19 @@ export function AvatarPicker({ settings, sets, busy, run }: AvatarPickerProps) {
143
148
  key={src}
144
149
  type="button"
145
150
  role="radio"
146
- aria-checked={shown.source === src}
147
- disabled={busy || !available[src]}
148
- title={
149
- available[src]
150
- ? undefined
151
- : src === 'upload'
152
- ? 'Upload a picture below first'
153
- : `Link ${LABEL[src]} under Settings › Account first`
154
- }
151
+ aria-checked={view === src}
152
+ disabled={busy || !openable(src)}
153
+ title={openable(src) ? undefined : `Link ${LABEL[src]} under Settings › Account first`}
155
154
  style={{
156
155
  ...S.source,
157
- ...(shown.source === src ? S.sourceOn : {}),
158
- opacity: available[src] ? 1 : 0.5
156
+ ...(view === src ? S.sourceOn : {}),
157
+ opacity: openable(src) ? 1 : 0.5
159
158
  }}
160
159
  onClick={() => {
161
- run(() => avatarClient.choose(src)).catch(reportErr)
160
+ setView(src)
161
+ if (available[src] && shown.source !== src) {
162
+ run(() => avatarClient.choose(src)).catch(reportErr)
163
+ }
162
164
  }}
163
165
  >
164
166
  <Picture src={thumbOf[src]} style={S.thumb} />
@@ -167,7 +169,7 @@ export function AvatarPicker({ settings, sets, busy, run }: AvatarPickerProps) {
167
169
  ))}
168
170
  </div>
169
171
 
170
- {shown.source === 'sprite' &&
172
+ {view === 'sprite' &&
171
173
  (sets ? (
172
174
  <>
173
175
  {sets.length > 1 && (
@@ -211,37 +213,44 @@ export function AvatarPicker({ settings, sets, busy, run }: AvatarPickerProps) {
211
213
  <span className="settings-popout__hint">…</span>
212
214
  ))}
213
215
 
214
- <div style={S.line}>
215
- <span className="settings-popout__label" style={S.grow}>
216
- Upload
217
- </span>
218
- <label className="settings-popout__btn" style={{ opacity: busy ? 0.5 : 1 }}>
219
- {settings.upload ? 'Replace' : 'Choose image'}
220
- <input
221
- type="file"
222
- accept={settings.limits.acceptedTypes.join(',')}
223
- hidden
224
- disabled={busy}
225
- onChange={e => {
226
- const file = e.currentTarget.files?.[0]
227
- e.currentTarget.value = ''
228
- if (file) run(() => avatarClient.upload(file)).catch(reportErr)
229
- }}
230
- />
231
- </label>
232
- {settings.upload && (
233
- <button
234
- type="button"
235
- className="settings-popout__btn"
236
- disabled={busy}
237
- onClick={() => {
238
- run(() => avatarClient.removeUpload()).catch(reportErr)
239
- }}
240
- >
241
- Remove
242
- </button>
243
- )}
244
- </div>
216
+ {view === 'upload' && (
217
+ <div style={S.line}>
218
+ <span className="settings-popout__label" style={S.grow}>
219
+ {settings.upload ? 'Your picture' : 'Choose a picture to use'}
220
+ </span>
221
+ <label className="settings-popout__btn" style={{ opacity: busy ? 0.5 : 1 }}>
222
+ {settings.upload ? 'Replace' : 'Choose image'}
223
+ <input
224
+ type="file"
225
+ accept={settings.limits.acceptedTypes.join(',')}
226
+ hidden
227
+ disabled={busy}
228
+ onChange={e => {
229
+ const file = e.currentTarget.files?.[0]
230
+ e.currentTarget.value = ''
231
+ // An upload is worn at once: picking a file on this tab means "use this".
232
+ if (file)
233
+ run(async () => {
234
+ await avatarClient.upload(file)
235
+ return avatarClient.choose('upload')
236
+ }).catch(reportErr)
237
+ }}
238
+ />
239
+ </label>
240
+ {settings.upload && (
241
+ <button
242
+ type="button"
243
+ className="settings-popout__btn"
244
+ disabled={busy}
245
+ onClick={() => {
246
+ run(() => avatarClient.removeUpload()).catch(reportErr)
247
+ }}
248
+ >
249
+ Remove
250
+ </button>
251
+ )}
252
+ </div>
253
+ )}
245
254
  </div>
246
255
  )
247
256
  }
@@ -268,8 +268,6 @@ export function ConnectedSettings({
268
268
 
269
269
  // Avatar: the dialog's picker loads only when opened, never at mount (AvatarPicker).
270
270
  const [avatarOpen, setAvatarOpen] = useState(false)
271
- // Linked accounts: loaded only when the row is opened (LinkedAccounts).
272
- const [linksOpen, setLinksOpen] = useState(false)
273
271
  const avatarBtn = useRef<HTMLButtonElement>(null)
274
272
  // Service identities this person may mint for (whoami.serviceGrants).
275
273
  const [serviceGrants, setServiceGrants] = useState(0)
@@ -742,31 +740,6 @@ export function ConnectedSettings({
742
740
  />
743
741
  </section>
744
742
  )}
745
-
746
- {/* Service access: only for someone the operator granted a
747
- service identity. Collapsed until Open. */}
748
- {!resolving && serviceGrants > 0 && (
749
- <section className="settings-popout__row settings-popout__row--stack">
750
- <span className="settings-popout__inline">
751
- <span className="settings-popout__label settings-popout__label--grow">
752
- Service access
753
- </span>
754
- <button
755
- type="button"
756
- className="settings-popout__btn"
757
- aria-expanded={serviceOpen}
758
- onClick={() => setServiceOpen(v => !v)}
759
- >
760
- {serviceOpen ? 'Done' : 'Open'}
761
- </button>
762
- </span>
763
- {serviceOpen && (
764
- <React.Suspense fallback={<span className="settings-popout__hint">…</span>}>
765
- <ServiceAccess />
766
- </React.Suspense>
767
- )}
768
- </section>
769
- )}
770
743
  </>
771
744
  )}
772
745
 
@@ -957,24 +930,37 @@ export function ConnectedSettings({
957
930
  )}
958
931
 
959
932
  {/* Discord and Steam: linked here, so their pictures can be chosen in
960
- the avatar dialog. Collapsed until Open: the read can refresh a
961
- linked picture, which opening the gear must not cost. */}
933
+ the avatar dialog. Mounted with the Account tab: the read can refresh
934
+ a linked picture, which opening the gear on another tab must not cost. */}
962
935
  {!resolving && userType !== 'public' && (
936
+ <section className="settings-popout__row settings-popout__row--stack">
937
+ <span className="settings-popout__label">Linked accounts</span>
938
+ <LinkedAccounts />
939
+ </section>
940
+ )}
941
+
942
+ {/* Service access: only for someone the operator granted a
943
+ service identity. Collapsed until Open. */}
944
+ {!resolving && serviceGrants > 0 && (
963
945
  <section className="settings-popout__row settings-popout__row--stack">
964
946
  <span className="settings-popout__inline">
965
947
  <span className="settings-popout__label settings-popout__label--grow">
966
- Linked accounts
948
+ Service access
967
949
  </span>
968
950
  <button
969
951
  type="button"
970
952
  className="settings-popout__btn"
971
- aria-expanded={linksOpen}
972
- onClick={() => setLinksOpen(v => !v)}
953
+ aria-expanded={serviceOpen}
954
+ onClick={() => setServiceOpen(v => !v)}
973
955
  >
974
- {linksOpen ? 'Done' : 'Open'}
956
+ {serviceOpen ? 'Done' : 'Open'}
975
957
  </button>
976
958
  </span>
977
- {linksOpen && <LinkedAccounts />}
959
+ {serviceOpen && (
960
+ <React.Suspense fallback={<span className="settings-popout__hint">…</span>}>
961
+ <ServiceAccess />
962
+ </React.Suspense>
963
+ )}
978
964
  </section>
979
965
  )}
980
966
 
@@ -4,9 +4,9 @@
4
4
  * part of changing the avatar). A linked account's picture then shows up as a
5
5
  * choice in the avatar dialog's Picture tab.
6
6
  *
7
- * Loaded only when the person opens the row, never at mount: the read can
8
- * refresh a linked picture from Discord or Steam, which opening the gear must
9
- * not cost (settings-prefetch e2e).
7
+ * Mounted only with the Account tab, never when the gear opens elsewhere: the
8
+ * read can refresh a linked picture from Discord or Steam, which opening the
9
+ * gear must not cost (settings-prefetch e2e).
10
10
  *
11
11
  * Linking signs in through a popup (`linkInPopup`), so the page under the
12
12
  * popout never navigates and a game keeps running.