(function friends() {
"use strict";
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/" }
]
},
{
title: "Alts",
subtitle: "My other accounts, dead or alive",
members: [
{ name: "J", img: "/assets/alts/j.png", tier: "active-alt", discordId: "1500197577336033301", link: null},
{ name: "Uzi", img: "/assets/alts/uzi.png", tier: "active-alt", discordId: "526626867973849123", link: null },
{ name: "Clove <3", img: "/assets/alts/clove.png", tier: "dead-alt", discordId: "1125844710511104030", link: null},
{ name: "Clove ⛤", img: "/assets/alts/butterfly.png", tier: "dead-alt", discordId: "514994021970739201", link: null },
{ name: "Mrow", img: "/assets/alts/mrow.png", tier: "dead-alt", discordId: "219480349053288450", link: null }
]
}
];
var REFRESH_MS = 60000; // re-poll live friends once a minute
var root = document.getElementById("friends-root");
if (!root) return;
// title → URL-safe anchor id, e.g. "Close Friends" -> "close-friends"
function slugify(str) {
return String(str == null ? "" : str)
.toLowerCase()
.trim()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "");
}
// ---- 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;
// Animated banners (a_) must be requested WITHOUT the .gif extension:
// Discord's CDN throws HTTP 415 for some a_*.gif banners, but the
// extension-less URL works (wsrv then re-serves it as cookieless webp).
var animated = String(hash).startsWith("a_");
var url = "https://cdn.discordapp.com/banners/" + id + "/" + hash + (animated ? "" : ".png") + "?size=480";
return proxyImg(url, { 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("");
}
// Discord server (clan) tag — the little guild badge + tag next to a name
function guildTagBadgeUrl(pg) {
if (!pg || !pg.badge || !pg.identity_guild_id) return null;
return proxyImg("https://cdn.discordapp.com/guild-tag-badges/" + pg.identity_guild_id + "/" + pg.badge + ".png?size=24");
}
function renderClanTag(refs, pg) {
if (!refs.tag) return;
if (pg && pg.tag && pg.identity_enabled) {
var b = guildTagBadgeUrl(pg);
refs.tag.innerHTML = (b ? '
' : "") +
'' + esc(pg.tag) + "";
refs.tag.hidden = false;
} else {
refs.tag.hidden = true;
}
}
// ---- 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 =
'