(function friends() {
"use strict";
// =====================================================================
// FRIENDS CONFIG ✏️ EDIT ME
// ---------------------------------------------------------------------
// Each friend is one card. Fields:
// name (required) display name
// img (required) static fallback image (used until/unless Lanyard
// gives a live avatar, and forever if they're not
// in the Lanyard server or the API is down)
// discordId (optional) Discord user ID. If set AND they're in the
// Lanyard server (https://discord.gg/lanyard),
// the card goes live: real avatar, status dot
// (green/idle/dnd/offline), badges + Nitro banner.
// Leave null for friends who aren't in the server —
// they get a blue "not connected" dot + static img.
// link (optional) where the card links to (their site, etc.)
//
// To make Camilla (etc.) live, paste her Discord ID into discordId.
// IMPORTANT: keep IDs as STRINGS in quotes ("123..."), not bare numbers —
// Discord IDs are too big for a JS number and get rounded (wrong user!).
// =====================================================================
var FRIENDS = [
{
title: "Fiancée",
members: [
{ name: "Aria", img: "/assets/friends/ari.png", tier: "wife", discordId: "1305215902685597797", link: null }
]
},
{
title: "Close Friends",
members: [
{ name: "Ria", img: "/assets/friends/ria.png", tier: "close", discordId: "1513506390088618145", link: null },
{ name: "Camilla", img: "/assets/friends/camilla.png", tier: "close", discordId: "1110542429838397471", link: "https://cammy-the-cat.com" },
{ name: "Saphie", img: "/assets/friends/saphie.png", tier: "close", discordId: "527709099186716673", link: null },
{ name: "Ari", img: "/assets/friends/meowhem.png", tier: "close", discordId: "1474568910736199825", link: "https://a.stupid.cat" }
]
},
{
title: "Friends",
members: [
{ name: "Fin", img: "/assets/friends/fin.png", tier: "friend", discordId: "867818211574808607", link: null },
{ name: "Meme", img: "/assets/friends/meme.png", tier: "friend", discordId: "812998699667161098", link: null },
{ name: "N", img: "/assets/friends/n.png", tier: "friend", discordId: "639399972407869450", link: null },
{ name: "Lylla", img: "/assets/friends/lylla.png", tier: "friend", discordId: "1009889543878611016", link: null },
{ name: "Simon", img: "/assets/friends/simon.png", tier: "friend", discordId: "758466783354814514", link: null }
]
},
{
title: "Other Peeps",
subtitle: "You can request to be added here!",
members: [
{ name: "Aureal", img: "/assets/friends/aureal.gif", tier: "known", discordId: "1498977251134279900", link: "https://aureal.dev/" }
]
}
];
var REFRESH_MS = 60000; // re-poll live friends once a minute
var root = document.getElementById("friends-root");
if (!root) return;
// ---- helpers (mirrors now-playing.js) -------------------------------
function esc(str) {
return String(str == null ? "" : str)
.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """);
}
function intToHex(n) {
return "#" + (Number(n) >>> 0).toString(16).padStart(6, "0").slice(-6);
}
// Re-serve Discord CDN images cookieless via wsrv.nl (same trick as the
// presence card) so no third-party cookies are set.
function proxyImg(url, opts) {
if (!url) return url;
if (!/^https:\/\/(cdn|media)\.discordapp\.(com|net)\//.test(url)) return url;
var src = url.replace(/^https:\/\//, "");
var q = "https://wsrv.nl/?url=" + encodeURIComponent(src) + "&output=webp";
if (opts && opts.w) q += "&w=" + opts.w + "&dpr=2&fit=cover";
return q;
}
function avatarUrl(u) {
if (!u || !u.avatar) {
var def = (Number(u && u.discriminator) || 0) % 5;
return proxyImg("https://cdn.discordapp.com/embed/avatars/" + def + ".png");
}
var ext = String(u.avatar).startsWith("a_") ? "gif" : "png";
return proxyImg("https://cdn.discordapp.com/avatars/" + u.id + "/" + u.avatar + "." + ext + "?size=128", { w: 96 });
}
function bannerUrl(id, hash) {
if (!id || !hash) return null;
var ext = String(hash).startsWith("a_") ? "gif" : "png";
return proxyImg("https://cdn.discordapp.com/banners/" + id + "/" + hash + "." + ext + "?size=480", { w: 480 });
}
// dstn.to badge list → small icon row (same source the presence card uses)
function renderDstnBadges(badges) {
if (!Array.isArray(badges)) return "";
return badges.map(function (b) {
var img = '';
return b.link
? '' + img + ""
: img;
}).join("");
}
// ---- build one card -------------------------------------------------
function buildCard(m) {
var card = document.createElement("article");
card.className = "friend-card" + (m.tier ? " tier-" + m.tier : "");
card.dataset.status = "unconnected"; // until proven otherwise
var nameTag = m.link ? "a" : "span";
var nameAttrs = m.link ? ' href="' + esc(m.link) + '" target="_blank" rel="noopener"' : "";
card.innerHTML =
'