()
| 1624 | // the partner tiles. Links open externally via the document-level |
| 1625 | // a[target="_blank"] handler in main.js (Tauri open_url on desktop). |
| 1626 | function wireSupportersDialog() { |
| 1627 | const btn = document.getElementById("friendsBtn"); |
| 1628 | const dialog = document.getElementById("friendsDialog"); |
| 1629 | const close = document.getElementById("friendsClose"); |
| 1630 | const grid = document.getElementById("friendsDialogGrid"); |
| 1631 | if (!btn || !dialog) return; |
| 1632 | |
| 1633 | if (grid && grid.dataset.ready !== "1") { |
| 1634 | grid.dataset.ready = "1"; |
| 1635 | // Masonry: round-robin tiles into fixed columns so a tall tile in one |
| 1636 | // column does not push the next row down. Small per-tile tilt gives the |
| 1637 | // deliberately-uneven "frames on a wall" look. |
| 1638 | const COLS = 3; |
| 1639 | const tilts = ["-2deg", "1.5deg", "-1deg", "2deg", "-1.5deg", "1deg"]; |
| 1640 | const cols = []; |
| 1641 | for (let i = 0; i < COLS; i++) { |
| 1642 | const col = document.createElement("div"); |
| 1643 | col.className = "lib-friends-col"; |
| 1644 | cols.push(col); |
| 1645 | grid.appendChild(col); |
| 1646 | } |
| 1647 | FRIENDS.forEach((f, i) => { |
| 1648 | const a = document.createElement("a"); |
| 1649 | a.className = "lib-friend"; |
| 1650 | a.href = f.url; |
| 1651 | a.target = "_blank"; |
| 1652 | a.rel = "noopener noreferrer"; |
| 1653 | a.title = f.name; |
| 1654 | a.style.setProperty("--tilt", tilts[i % tilts.length]); |
| 1655 | // A monogram avatar (first initial) keeps the tile on-brand when an entry |
| 1656 | // has no image, or its image fails to load (e.g. before the asset is added). |
| 1657 | const makeMonogram = () => { |
| 1658 | const m = document.createElement("span"); |
| 1659 | m.className = "lib-friend-monogram"; |
| 1660 | m.textContent = (f.name || "?").trim().charAt(0).toUpperCase(); |
| 1661 | m.setAttribute("aria-hidden", "true"); |
| 1662 | return m; |
| 1663 | }; |
| 1664 | if (f.logo) { |
| 1665 | const img = document.createElement("img"); |
| 1666 | img.className = f.avatar ? "lib-friend-avatar" : "lib-friend-logo"; |
| 1667 | img.src = f.logo; |
| 1668 | img.alt = f.name; |
| 1669 | img.loading = "lazy"; |
| 1670 | img.addEventListener("error", () => img.replaceWith(makeMonogram())); |
| 1671 | a.appendChild(img); |
| 1672 | } else { |
| 1673 | a.appendChild(makeMonogram()); |
| 1674 | } |
| 1675 | const name = document.createElement("span"); |
| 1676 | name.className = "lib-friend-name"; |
| 1677 | name.textContent = f.name; |
| 1678 | a.appendChild(name); |
| 1679 | if (f.role) { |
| 1680 | const role = document.createElement("span"); |
| 1681 | role.className = "lib-friend-role"; |
| 1682 | role.textContent = f.role; |
| 1683 | a.appendChild(role); |
no test coverage detected