| 1 |
(function() { |
| 2 |
"use strict"; |
| 3 |
const SHARED_STORES_SLOT = "__desktopModeSharedStores"; |
| 4 |
function resolveSlot() { |
| 5 |
const w2 = window; |
| 6 |
let slot = w2[SHARED_STORES_SLOT]; |
| 7 |
if (!slot) { |
| 8 |
slot = /* @__PURE__ */ new Map(); |
| 9 |
w2[SHARED_STORES_SLOT] = slot; |
| 10 |
} |
| 11 |
return slot; |
| 12 |
} |
| 13 |
function createSharedStore(key, initialState) { |
| 14 |
const slot = resolveSlot(); |
| 15 |
let record = slot.get(key); |
| 16 |
if (!record) { |
| 17 |
record = { |
| 18 |
state: initialState(), |
| 19 |
listeners: /* @__PURE__ */ new Set(), |
| 20 |
rebuild: initialState |
| 21 |
}; |
| 22 |
slot.set(key, record); |
| 23 |
} |
| 24 |
const handle = { |
| 25 |
// `record.state` is the live reference. The getter on the |
| 26 |
// `state` field reads the latest value even if `reset()` |
| 27 |
// reassigned it to a fresh object. |
| 28 |
get state() { |
| 29 |
return record.state; |
| 30 |
}, |
| 31 |
set state(next) { |
| 32 |
record.state = next; |
| 33 |
}, |
| 34 |
getState() { |
| 35 |
return record.state; |
| 36 |
}, |
| 37 |
notify() { |
| 38 |
for (const cb of Array.from(record.listeners)) { |
| 39 |
try { |
| 40 |
cb(record.state); |
| 41 |
} catch (err) { |
| 42 |
console.error( |
| 43 |
`[desktop-mode/shared-store:${key}] subscriber threw:`, |
| 44 |
err |
| 45 |
); |
| 46 |
} |
| 47 |
} |
| 48 |
}, |
| 49 |
subscribe(cb) { |
| 50 |
record.listeners.add(cb); |
| 51 |
return () => { |
| 52 |
record.listeners.delete(cb); |
| 53 |
}; |
| 54 |
}, |
| 55 |
setState(patch) { |
| 56 |
const cur = record.state; |
| 57 |
if (typeof cur !== "object" || cur === null) { |
| 58 |
console.warn( |
| 59 |
`[desktop-mode/shared-store:${key}] setState called on a primitive store; use the state setter instead.` |
| 60 |
); |
| 61 |
return; |
| 62 |
} |
| 63 |
Object.assign(cur, patch); |
| 64 |
handle.notify(); |
| 65 |
}, |
| 66 |
reset() { |
| 67 |
const fresh = record.rebuild(); |
| 68 |
const cur = record.state; |
| 69 |
if (typeof cur === "object" && cur !== null && typeof fresh === "object" && fresh !== null) { |
| 70 |
const target = cur; |
| 71 |
for (const k of Object.keys(target)) { |
| 72 |
delete target[k]; |
| 73 |
} |
| 74 |
Object.assign(target, fresh); |
| 75 |
} else { |
| 76 |
record.state = fresh; |
| 77 |
} |
| 78 |
record.listeners.clear(); |
| 79 |
} |
| 80 |
}; |
| 81 |
return handle; |
| 82 |
} |
| 83 |
async function loadPixi() { |
| 84 |
const wp = window.wp; |
| 85 |
const fn = wp?.desktop?.loadModules; |
| 86 |
if (typeof fn !== "function") { |
| 87 |
throw new Error( |
| 88 |
"wp.desktop.loadModules is not available — main shell may not have booted yet." |
| 89 |
); |
| 90 |
} |
| 91 |
await fn(["pixijs"]); |
| 92 |
} |
| 93 |
const wpBeatStore = createSharedStore( |
| 94 |
"desktop-mode/heartbeat-widget/wp-beats", |
| 95 |
() => ({ |
| 96 |
lastTickAt: 0, |
| 97 |
lastSendAt: 0, |
| 98 |
intervalSecs: 15, |
| 99 |
tickSeq: 0, |
| 100 |
booted: false |
| 101 |
}) |
| 102 |
); |
| 103 |
function bootWpBeatTracker() { |
| 104 |
const s = wpBeatStore; |
| 105 |
if (s.state.booted) { |
| 106 |
return; |
| 107 |
} |
| 108 |
s.state.booted = true; |
| 109 |
s.state.intervalSecs = wpHeartbeatInterval(); |
| 110 |
s.state.lastTickAt = performance.now(); |
| 111 |
const jq = window.jQuery; |
| 112 |
if (!jq) { |
| 113 |
return; |
| 114 |
} |
| 115 |
const $doc = jq(document); |
| 116 |
$doc.on("heartbeat-send", () => { |
| 117 |
s.state.lastSendAt = performance.now(); |
| 118 |
}); |
| 119 |
$doc.on("heartbeat-tick", () => { |
| 120 |
s.state.lastTickAt = performance.now(); |
| 121 |
s.state.intervalSecs = wpHeartbeatInterval(); |
| 122 |
s.state.tickSeq += 1; |
| 123 |
s.notify(); |
| 124 |
}); |
| 125 |
} |
| 126 |
const HEART_PALETTE = { |
| 127 |
rim: 6950693, |
| 128 |
bright: 16731501, |
| 129 |
hi: 16754104 |
| 130 |
}; |
| 131 |
const HEART_COLOR_REST = HEART_PALETTE.bright; |
| 132 |
const HEART_COLOR_BEAT = HEART_PALETTE.hi; |
| 133 |
const HEART_SIZE = 52; |
| 134 |
const mount = async (container, ctx) => { |
| 135 |
try { |
| 136 |
await loadPixi(); |
| 137 |
} catch (e) { |
| 138 |
renderFallback(container, e.message); |
| 139 |
return () => void 0; |
| 140 |
} |
| 141 |
return mountWithPixi(container, ctx); |
| 142 |
}; |
| 143 |
function logoUrl(ctx) { |
| 144 |
const base = (ctx?.pluginUrl ?? "").replace(/\/+$/, ""); |
| 145 |
return `${base}/assets/images/wp-logo.png`; |
| 146 |
} |
| 147 |
function renderFallback(container, message) { |
| 148 |
container.classList.add("desktop-mode-widget-heartbeat"); |
| 149 |
container.classList.add("desktop-mode-widget-heartbeat--fallback"); |
| 150 |
const wrap = document.createElement("div"); |
| 151 |
wrap.className = "desktop-mode-widget-heartbeat__fallback"; |
| 152 |
wrap.textContent = message || "Could not load animation engine."; |
| 153 |
container.appendChild(wrap); |
| 154 |
} |
| 155 |
const FRAME_HEIGHT_WITH_HEART = 230; |
| 156 |
const FRAME_HEIGHT_NO_HEART = 88; |
| 157 |
async function mountWithPixi(container, ctx) { |
| 158 |
const pixi = window.PIXI; |
| 159 |
if (!pixi) { |
| 160 |
renderFallback(container, "PIXI not available."); |
| 161 |
return () => void 0; |
| 162 |
} |
| 163 |
container.classList.add("desktop-mode-widget-heartbeat"); |
| 164 |
let showHeart = ctx.storage.get("showHeart") ?? true; |
| 165 |
if (!showHeart) { |
| 166 |
container.classList.add("desktop-mode-widget-heartbeat--no-heart"); |
| 167 |
} |
| 168 |
const stage = document.createElement("div"); |
| 169 |
stage.className = "desktop-mode-widget-heartbeat__stage"; |
| 170 |
container.appendChild(stage); |
| 171 |
const meta = document.createElement("div"); |
| 172 |
meta.className = "desktop-mode-widget-heartbeat__meta"; |
| 173 |
const label = document.createElement("div"); |
| 174 |
label.className = "desktop-mode-widget-heartbeat__label"; |
| 175 |
label.textContent = "Next beat in"; |
| 176 |
meta.appendChild(label); |
| 177 |
const remaining = document.createElement("div"); |
| 178 |
remaining.className = "desktop-mode-widget-heartbeat__remaining"; |
| 179 |
remaining.textContent = "—"; |
| 180 |
meta.appendChild(remaining); |
| 181 |
container.appendChild(meta); |
| 182 |
const bar = document.createElement("div"); |
| 183 |
bar.className = "desktop-mode-widget-heartbeat__bar"; |
| 184 |
const fill = document.createElement("div"); |
| 185 |
fill.className = "desktop-mode-widget-heartbeat__bar-fill"; |
| 186 |
bar.appendChild(fill); |
| 187 |
container.appendChild(bar); |
| 188 |
const app = new pixi.Application(); |
| 189 |
await app.init({ |
| 190 |
resizeTo: stage, |
| 191 |
backgroundAlpha: 0, |
| 192 |
antialias: true, |
| 193 |
autoDensity: true, |
| 194 |
resolution: Math.min(window.devicePixelRatio || 1, 2) |
| 195 |
}); |
| 196 |
stage.appendChild(app.canvas); |
| 197 |
const halo = buildHalo(pixi); |
| 198 |
const { view: heart, body: heartBody } = buildHeart(pixi); |
| 199 |
const logo = buildLogoSprite(pixi, logoUrl(ctx)); |
| 200 |
app.stage.addChild(halo); |
| 201 |
app.stage.addChild(heart); |
| 202 |
heart.addChild(logo); |
| 203 |
const centre = () => { |
| 204 |
halo.x = app.screen.width / 2; |
| 205 |
halo.y = app.screen.height / 2; |
| 206 |
heart.x = app.screen.width / 2; |
| 207 |
heart.y = app.screen.height / 2; |
| 208 |
}; |
| 209 |
centre(); |
| 210 |
const ro = new ResizeObserver(() => centre()); |
| 211 |
ro.observe(stage); |
| 212 |
bootWpBeatTracker(); |
| 213 |
let pulseAccum = 0; |
| 214 |
let bigBeatT = 0; |
| 215 |
let glow = 0; |
| 216 |
let lastSeenSeq = wpBeatStore.state.tickSeq; |
| 217 |
const unsubscribe = wpBeatStore.subscribe((s) => { |
| 218 |
if (s.tickSeq !== lastSeenSeq) { |
| 219 |
lastSeenSeq = s.tickSeq; |
| 220 |
bigBeatT = 1; |
| 221 |
glow = 1; |
| 222 |
} |
| 223 |
}); |
| 224 |
const jq = window.jQuery; |
| 225 |
let simHandle = null; |
| 226 |
if (!jq) { |
| 227 |
simHandle = setInterval(() => { |
| 228 |
wpBeatStore.state.lastTickAt = performance.now(); |
| 229 |
wpBeatStore.state.tickSeq += 1; |
| 230 |
wpBeatStore.notify(); |
| 231 |
}, wpBeatStore.state.intervalSecs * 1e3); |
| 232 |
} |
| 233 |
const detach = () => { |
| 234 |
unsubscribe(); |
| 235 |
if (simHandle !== null) { |
| 236 |
clearInterval(simHandle); |
| 237 |
} |
| 238 |
}; |
| 239 |
const tick = () => { |
| 240 |
const dt = app.ticker.deltaMS / 1e3; |
| 241 |
pulseAccum += dt; |
| 242 |
const restPhase = pulseAccum / 4 * Math.PI * 2; |
| 243 |
const restingScaleBase = 1 + 8e-3 * Math.sin(restPhase); |
| 244 |
let restingScale = restingScaleBase; |
| 245 |
let squishX = 0; |
| 246 |
let squishY = 0; |
| 247 |
if (bigBeatT > 0) { |
| 248 |
bigBeatT = Math.max(0, bigBeatT - dt / 0.9); |
| 249 |
const t = 1 - bigBeatT; |
| 250 |
let env; |
| 251 |
if (t < 0.1) { |
| 252 |
env = 0.55 * (t / 0.1); |
| 253 |
} else if (t < 0.28) { |
| 254 |
env = 0.55 - 0.5 * ((t - 0.1) / 0.18); |
| 255 |
} else if (t < 0.55) { |
| 256 |
env = 0.05 + 0.1 * Math.sin((t - 0.28) / 0.27 * Math.PI); |
| 257 |
} else { |
| 258 |
const k = (t - 0.55) / 0.45; |
| 259 |
env = 0.05 * (1 - k) * Math.exp(-2.5 * k); |
| 260 |
} |
| 261 |
restingScale += env; |
| 262 |
if (t < 0.2) { |
| 263 |
const sP = Math.sin(t / 0.2 * Math.PI); |
| 264 |
squishX = 0.1 * sP; |
| 265 |
squishY = -0.05 * sP; |
| 266 |
} |
| 267 |
} |
| 268 |
heart.scale.x = restingScale * (1 + squishX); |
| 269 |
heart.scale.y = restingScale * (1 + squishY); |
| 270 |
glow = Math.max(0, glow - dt / 1.2); |
| 271 |
halo.alpha = 0.1 + glow * 0.55; |
| 272 |
const haloScale = 1 + glow * 0.15; |
| 273 |
halo.scale.set(haloScale); |
| 274 |
heartBody.tint = lerpColor( |
| 275 |
HEART_COLOR_REST, |
| 276 |
HEART_COLOR_BEAT, |
| 277 |
Math.min(1, glow * 0.6 + bigBeatT * 0.4) |
| 278 |
); |
| 279 |
const elapsed = (performance.now() - wpBeatStore.state.lastTickAt) / 1e3; |
| 280 |
const intervalSecs = wpBeatStore.state.intervalSecs; |
| 281 |
const progress = clamp(elapsed / Math.max(intervalSecs, 1), 0, 1); |
| 282 |
fill.style.width = `${(progress * 100).toFixed(1)}%`; |
| 283 |
const remainSecs = Math.max(0, intervalSecs - elapsed); |
| 284 |
remaining.textContent = `${remainSecs.toFixed(1)}s`; |
| 285 |
}; |
| 286 |
app.ticker.add(tick); |
| 287 |
const resyncCanvasToStage = () => { |
| 288 |
requestAnimationFrame(() => { |
| 289 |
try { |
| 290 |
app.resize?.(); |
| 291 |
} catch (_e) { |
| 292 |
try { |
| 293 |
const sw = stage.clientWidth; |
| 294 |
const sh = stage.clientHeight; |
| 295 |
if (sw > 0 && sh > 0) { |
| 296 |
app.renderer.resize(sw, sh); |
| 297 |
} |
| 298 |
} catch (_err) { |
| 299 |
} |
| 300 |
} |
| 301 |
centre(); |
| 302 |
}); |
| 303 |
}; |
| 304 |
const onContextMenu = (e) => { |
| 305 |
e.preventDefault(); |
| 306 |
e.stopPropagation(); |
| 307 |
openHeartbeatMenu(e, showHeart, (next) => { |
| 308 |
showHeart = next; |
| 309 |
ctx.storage.set("showHeart", next); |
| 310 |
applyHeartVisibility(container, next); |
| 311 |
if (next) { |
| 312 |
resyncCanvasToStage(); |
| 313 |
} |
| 314 |
}); |
| 315 |
}; |
| 316 |
container.addEventListener("contextmenu", onContextMenu); |
| 317 |
applyHeartVisibility(container, showHeart); |
| 318 |
if (showHeart) { |
| 319 |
resyncCanvasToStage(); |
| 320 |
} |
| 321 |
return () => { |
| 322 |
container.removeEventListener("contextmenu", onContextMenu); |
| 323 |
detach(); |
| 324 |
ro.disconnect(); |
| 325 |
app.ticker.remove(tick); |
| 326 |
try { |
| 327 |
app.canvas?.remove(); |
| 328 |
} catch { |
| 329 |
} |
| 330 |
container.classList.remove("desktop-mode-widget-heartbeat"); |
| 331 |
container.classList.remove("desktop-mode-widget-heartbeat--no-heart"); |
| 332 |
}; |
| 333 |
} |
| 334 |
function applyHeartVisibility(container, showHeart) { |
| 335 |
container.classList.toggle("desktop-mode-widget-heartbeat--no-heart", !showHeart); |
| 336 |
const card = container.closest(".desktop-mode-widgets__card"); |
| 337 |
if (card) { |
| 338 |
card.classList.add("desktop-mode-widgets__card--heartbeat"); |
| 339 |
const h = showHeart ? FRAME_HEIGHT_WITH_HEART : FRAME_HEIGHT_NO_HEART; |
| 340 |
card.style.height = `${h}px`; |
| 341 |
} |
| 342 |
} |
| 343 |
function openHeartbeatMenu(e, showHeart, onToggle) { |
| 344 |
document.querySelectorAll(".desktop-mode-widget-heartbeat__menu").forEach((el) => el.remove()); |
| 345 |
const menu = document.createElement("wpd-context-menu"); |
| 346 |
menu.className = "desktop-mode-widget-heartbeat__menu"; |
| 347 |
menu.setAttribute("open", ""); |
| 348 |
menu.style.position = "fixed"; |
| 349 |
menu.style.left = `${e.clientX}px`; |
| 350 |
menu.style.top = `${e.clientY}px`; |
| 351 |
menu.style.zIndex = "10500"; |
| 352 |
const opt = document.createElement("wpd-context-menu-option"); |
| 353 |
opt.setAttribute("value", "show-heart"); |
| 354 |
if (showHeart) { |
| 355 |
opt.setAttribute("checked", ""); |
| 356 |
} |
| 357 |
opt.textContent = "Show heart"; |
| 358 |
menu.appendChild(opt); |
| 359 |
const close = () => { |
| 360 |
menu.remove(); |
| 361 |
document.removeEventListener("pointerdown", onOutside, true); |
| 362 |
document.removeEventListener("keydown", onKey, true); |
| 363 |
}; |
| 364 |
const onOutside = (ev) => { |
| 365 |
if (!menu.contains(ev.target)) { |
| 366 |
close(); |
| 367 |
} |
| 368 |
}; |
| 369 |
const onKey = (ev) => { |
| 370 |
if (ev.key === "Escape") { |
| 371 |
close(); |
| 372 |
} |
| 373 |
}; |
| 374 |
menu.addEventListener("wpd-context-menu-pick", () => { |
| 375 |
onToggle(!showHeart); |
| 376 |
close(); |
| 377 |
}); |
| 378 |
document.body.appendChild(menu); |
| 379 |
const rect = menu.getBoundingClientRect(); |
| 380 |
if (rect.right > window.innerWidth) { |
| 381 |
menu.style.left = `${Math.max(4, window.innerWidth - rect.width - 8)}px`; |
| 382 |
} |
| 383 |
if (rect.bottom > window.innerHeight) { |
| 384 |
menu.style.top = `${Math.max(4, window.innerHeight - rect.height - 8)}px`; |
| 385 |
} |
| 386 |
document.addEventListener("pointerdown", onOutside, true); |
| 387 |
document.addEventListener("keydown", onKey, true); |
| 388 |
} |
| 389 |
function buildHalo(pixi) { |
| 390 |
const g = new pixi.Graphics(); |
| 391 |
const radii = [HEART_SIZE * 1.2, HEART_SIZE * 1.05, HEART_SIZE * 0.85]; |
| 392 |
const alphas = [0.12, 0.18, 0.28]; |
| 393 |
radii.forEach((r, i) => { |
| 394 |
g.circle(0, 0, r); |
| 395 |
g.fill({ color: HEART_COLOR_BEAT, alpha: alphas[i] }); |
| 396 |
}); |
| 397 |
g.alpha = 0.1; |
| 398 |
return g; |
| 399 |
} |
| 400 |
function heartPath(scaleMul = 1) { |
| 401 |
const samples = 240; |
| 402 |
const pts = []; |
| 403 |
const s = HEART_SIZE / 17 * scaleMul; |
| 404 |
for (let i = 0; i <= samples; i++) { |
| 405 |
const t = i / samples * Math.PI * 2; |
| 406 |
const x = 16 * 1.08 * Math.sin(t) ** 3; |
| 407 |
const y = -(13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t)); |
| 408 |
pts.push(x * s, y * s); |
| 409 |
} |
| 410 |
return pts; |
| 411 |
} |
| 412 |
function buildHeart(pixi) { |
| 413 |
const wrap = new pixi.Container(); |
| 414 |
const bounds = heartBoundingY(); |
| 415 |
const shadow = new pixi.Graphics(); |
| 416 |
shadow.poly(heartPath(1.05)); |
| 417 |
shadow.fill({ color: 0, alpha: 0.55 }); |
| 418 |
shadow.y = 5; |
| 419 |
shadow.alpha = 0.55; |
| 420 |
wrap.addChild(shadow); |
| 421 |
const gradientCanvas = makeGradientCanvas(); |
| 422 |
const gradientTexture = pixi.Texture.from(gradientCanvas); |
| 423 |
const gradientSprite = new pixi.Sprite(gradientTexture); |
| 424 |
const heartHeight = bounds.maxY - bounds.minY; |
| 425 |
const overscan = HEART_SIZE * 2.5; |
| 426 |
gradientSprite.width = overscan; |
| 427 |
gradientSprite.height = heartHeight; |
| 428 |
gradientSprite.x = -overscan / 2; |
| 429 |
gradientSprite.y = bounds.minY; |
| 430 |
gradientSprite.tint = HEART_COLOR_REST; |
| 431 |
const mask = new pixi.Graphics(); |
| 432 |
mask.poly(heartPath(1)); |
| 433 |
mask.fill({ color: 16777215, alpha: 1 }); |
| 434 |
gradientSprite.mask = mask; |
| 435 |
wrap.addChild(mask); |
| 436 |
wrap.addChild(gradientSprite); |
| 437 |
const hi1 = new pixi.Graphics(); |
| 438 |
hi1.ellipse( |
| 439 |
-HEART_SIZE * 0.32, |
| 440 |
-HEART_SIZE * 0.5, |
| 441 |
HEART_SIZE * 0.18, |
| 442 |
HEART_SIZE * 0.1 |
| 443 |
); |
| 444 |
hi1.fill({ color: 16777215, alpha: 0.45 }); |
| 445 |
hi1.rotation = -0.5; |
| 446 |
wrap.addChild(hi1); |
| 447 |
const hi2 = new pixi.Graphics(); |
| 448 |
hi2.ellipse( |
| 449 |
HEART_SIZE * 0.2, |
| 450 |
-HEART_SIZE * 0.38, |
| 451 |
HEART_SIZE * 0.09, |
| 452 |
HEART_SIZE * 0.05 |
| 453 |
); |
| 454 |
hi2.fill({ color: 16777215, alpha: 0.22 }); |
| 455 |
hi2.rotation = 0.4; |
| 456 |
wrap.addChild(hi2); |
| 457 |
const outline = new pixi.Graphics(); |
| 458 |
outline.poly(heartPath(1)); |
| 459 |
outline.stroke({ color: 16777215, alpha: 0.18, width: 1 }); |
| 460 |
wrap.addChild(outline); |
| 461 |
return { view: wrap, body: gradientSprite }; |
| 462 |
} |
| 463 |
function heartBoundingY() { |
| 464 |
const pts = heartPath(1); |
| 465 |
let minY = Infinity; |
| 466 |
let maxY = -Infinity; |
| 467 |
for (let i = 1; i < pts.length; i += 2) { |
| 468 |
if (pts[i] < minY) { |
| 469 |
minY = pts[i]; |
| 470 |
} |
| 471 |
if (pts[i] > maxY) { |
| 472 |
maxY = pts[i]; |
| 473 |
} |
| 474 |
} |
| 475 |
return { minY, maxY }; |
| 476 |
} |
| 477 |
function makeGradientCanvas() { |
| 478 |
const c = document.createElement("canvas"); |
| 479 |
c.width = 2; |
| 480 |
c.height = 512; |
| 481 |
const ctx = c.getContext("2d"); |
| 482 |
if (!ctx) { |
| 483 |
return c; |
| 484 |
} |
| 485 |
const grad = ctx.createLinearGradient(0, 0, 0, 512); |
| 486 |
grad.addColorStop(0, "#ffd6e3"); |
| 487 |
grad.addColorStop(0.18, "#ff8ba3"); |
| 488 |
grad.addColorStop(0.42, "#ff4d6d"); |
| 489 |
grad.addColorStop(0.72, "#9a1f3d"); |
| 490 |
grad.addColorStop(1, "#3d061a"); |
| 491 |
ctx.fillStyle = grad; |
| 492 |
ctx.fillRect(0, 0, 2, 512); |
| 493 |
return c; |
| 494 |
} |
| 495 |
function buildLogoSprite(pixi, url) { |
| 496 |
const wrap = new pixi.Container(); |
| 497 |
wrap.y = HEART_SIZE * 0.08; |
| 498 |
const shadow = new pixi.Sprite(); |
| 499 |
shadow.anchor.set(0.5); |
| 500 |
shadow.tint = HEART_PALETTE.rim; |
| 501 |
shadow.alpha = 0.6; |
| 502 |
shadow.y = HEART_SIZE * 0.035; |
| 503 |
wrap.addChild(shadow); |
| 504 |
const mark = new pixi.Sprite(); |
| 505 |
mark.anchor.set(0.5); |
| 506 |
wrap.addChild(mark); |
| 507 |
const targetWidth = HEART_SIZE * 0.92; |
| 508 |
const img = new Image(); |
| 509 |
img.src = url; |
| 510 |
img.decode().then(() => { |
| 511 |
const resolution = Math.min(window.devicePixelRatio || 1, 2); |
| 512 |
const widthPx = Math.ceil(targetWidth * resolution * 1.7); |
| 513 |
const canvas = rasterizeLogo(img, widthPx); |
| 514 |
const texture = pixi.Texture.from(canvas); |
| 515 |
const scale = targetWidth / canvas.width; |
| 516 |
shadow.texture = texture; |
| 517 |
shadow.scale.set(scale); |
| 518 |
mark.texture = texture; |
| 519 |
mark.scale.set(scale); |
| 520 |
}).catch(() => { |
| 521 |
}); |
| 522 |
return wrap; |
| 523 |
} |
| 524 |
function rasterizeLogo(img, widthPx) { |
| 525 |
let src = img; |
| 526 |
let w2 = img.naturalWidth; |
| 527 |
let h = img.naturalHeight; |
| 528 |
while (w2 / 2 >= widthPx * 2) { |
| 529 |
w2 = Math.round(w2 / 2); |
| 530 |
h = Math.round(h / 2); |
| 531 |
const step = document.createElement("canvas"); |
| 532 |
step.width = w2; |
| 533 |
step.height = h; |
| 534 |
const g2 = step.getContext("2d"); |
| 535 |
if (!g2) { |
| 536 |
break; |
| 537 |
} |
| 538 |
g2.imageSmoothingEnabled = true; |
| 539 |
g2.imageSmoothingQuality = "high"; |
| 540 |
g2.drawImage(src, 0, 0, w2, h); |
| 541 |
src = step; |
| 542 |
} |
| 543 |
const out = document.createElement("canvas"); |
| 544 |
out.width = Math.max(1, widthPx); |
| 545 |
out.height = Math.max( |
| 546 |
1, |
| 547 |
Math.round(widthPx * img.naturalHeight / Math.max(1, img.naturalWidth)) |
| 548 |
); |
| 549 |
const g = out.getContext("2d"); |
| 550 |
if (g) { |
| 551 |
g.imageSmoothingEnabled = true; |
| 552 |
g.imageSmoothingQuality = "high"; |
| 553 |
g.drawImage(src, 0, 0, out.width, out.height); |
| 554 |
} |
| 555 |
return out; |
| 556 |
} |
| 557 |
function wpHeartbeatInterval() { |
| 558 |
const wp = window.wp; |
| 559 |
try { |
| 560 |
const fn = wp?.heartbeat?.interval; |
| 561 |
if (typeof fn === "function") { |
| 562 |
const v = Number(fn()); |
| 563 |
if (Number.isFinite(v) && v > 0) { |
| 564 |
return v; |
| 565 |
} |
| 566 |
} |
| 567 |
} catch (e) { |
| 568 |
} |
| 569 |
return 15; |
| 570 |
} |
| 571 |
function lerpColor(a, b, t) { |
| 572 |
const ar = Math.trunc(a / 65536) % 256; |
| 573 |
const ag = Math.trunc(a / 256) % 256; |
| 574 |
const ab = a % 256; |
| 575 |
const br = Math.trunc(b / 65536) % 256; |
| 576 |
const bg = Math.trunc(b / 256) % 256; |
| 577 |
const bb = b % 256; |
| 578 |
const r = Math.round(ar + (br - ar) * t); |
| 579 |
const g = Math.round(ag + (bg - ag) * t); |
| 580 |
const bv = Math.round(ab + (bb - ab) * t); |
| 581 |
return r * 65536 + g * 256 + bv; |
| 582 |
} |
| 583 |
function clamp(v, lo, hi) { |
| 584 |
if (v < lo) { |
| 585 |
return lo; |
| 586 |
} |
| 587 |
if (v > hi) { |
| 588 |
return hi; |
| 589 |
} |
| 590 |
return v; |
| 591 |
} |
| 592 |
const w = window; |
| 593 |
w.desktopModeWidgets = w.desktopModeWidgets || {}; |
| 594 |
w.desktopModeWidgets["desktop-mode/heartbeat"] = mount; |
| 595 |
})(); |
| 596 |
|