(function friends() {
"use strict";
var FRIENDS = [
{
title: "Fiancée",
members: [
{ name: "Aria", tier: "wife", discordId: "1305215902685597797", link: null }
]
},
{
title: "Close Friends",
members: [
{ name: "Lilly", tier: "close", discordId: "908055723659898902", link: null },
{ name: "Ria", tier: "close", discordId: "1513506390088618145", link: null },
{ name: "Camilla", tier: "close", discordId: "1110542429838397471", link: "https://cammy-the-cat.com" },
{ name: "Saphie", tier: "close", discordId: "527709099186716673", link: null },
{ name: "Ari", tier: "close", discordId: "1474568910736199825", link: "https://a.stupid.cat" }
]
},
{
title: "Friends",
members: [
{ name: "Fin", tier: "friend", discordId: "867818211574808607", link: null },
{ name: "Meme", tier: "friend", discordId: "812998699667161098", link: null },
{ name: "N", tier: "friend", discordId: "639399972407869450", link: null },
{ name: "Lylla", tier: "friend", discordId: "1009889543878611016", link: null },
{ name: "Simon", tier: "friend", discordId: "758466783354814514", link: null }
]
},
{
title: "Other Peeps",
subtitle: "You can request to be added here!",
members: [
{ name: "furi", tier: "known", discordId: "781445370177126401", link: "https://furina.is-a.dev"},
{ name: "pokemon", tier: "known", discordId: "784443338627612673", link: "https://devmatei.com/" }
]
},
{
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: "clovetwilight3", img: "/assets/alts/clovetwilight3.png", tier: "dead-alt", discordId: null, link: null },
{ name: "estrogenhrt", img: "/assets/alts/estrogenhrt.png", tier: "dead-alt", discordId: null, 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 discord.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);
}
function rgbTriplet(n) {
n = Number(n) >>> 0;
return ((n >> 16) & 255) + ", " + ((n >> 8) & 255) + ", " + (n & 255);
}
// Nitro profile gradient -> card background (mirrors the /discord card)
function applyGrad(refs, colors) {
if (!colors || colors.length < 2) return;
refs.el.style.setProperty("--fc-grad-1-rgb", rgbTriplet(colors[0]));
refs.el.style.setProperty("--fc-grad-2-rgb", rgbTriplet(colors[1]));
refs.el.classList.add("has-profile-grad");
}
// Nitro "display name styles" -> gradient on the name text (same effect the
// /discord card uses). Lanyard + dstn.to expose this; our API does not yet.
// Discord display-name fonts (display_name_styles.font_id) -> the local
// look-alike @font-face families in css/fonts.css (loaded site-wide via
// main.css @import). Mapping confirmed against Discord's font_id enum;
// mirrors NAME_FONTS in js/discord.js. (1/2/5/9 have no file -> default.)
var FONT_BY_ID = {
3: "'DDN Sakura', cursive", // CHERRY_BOMB
4: "'DDN Jellybean', cursive", // CHICLE
6: "'DDN Modern', sans-serif", // MUSEO_MODERNO
7: "'DDN Medieval', serif", // NEO_CASTEL
8: "'DDN 8Bit', monospace", // PIXELIFY
10: "'DDN Vampyre', serif", // SINISTRE
11: "'DDN gg sans', sans-serif", // DEFAULT
12: "'DDN Tempo', serif" // ZILLA_SLAB
};
function applyNameStyle(refs, styles) {
if (!refs.name || !styles) return;
// gradient / colour
if (styles.colors && styles.colors.length) {
var cols = styles.colors.map(intToHex);
refs.name.style.backgroundImage = "linear-gradient(90deg, " +
(cols.length === 1 ? cols[0] + "," + cols[0] : cols.join(", ")) + ")";
refs.name.classList.add("is-gradient");
}
// custom display-name font (local @font-face; no network load needed)
var fam = styles.font_id && FONT_BY_ID[styles.font_id];
if (fam) { refs.name.style.fontFamily = fam; }
}
// 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 });
}
// custom-status emoji → CDN url (custom emoji) or null (unicode emoji)
function customEmojiUrl(e) {
if (!e || !e.id) return null;
return proxyImg("https://cdn.discordapp.com/emojis/" + e.id + (e.animated ? ".gif" : ".png") + "?size=32");
}
// Render the Discord custom status (activity type 4): emoji + text.
function renderCustomStatus(refs, activities) {
if (!refs.custom) return;
var c = (activities || []).find(function (a) { return a && a.type === 4; });
var text = c && c.state ? String(c.state) : "";
var emoji = c && c.emoji;
if (!text && !(emoji && (emoji.id || emoji.name))) {
refs.custom.hidden = true;
refs.custom.innerHTML = "";
return;
}
var eu = emoji ? customEmojiUrl(emoji) : null;
var emojiHtml = eu
? ''
: (emoji && emoji.name ? '' + esc(emoji.name) + "" : "");
refs.custom.innerHTML = emojiHtml + (text ? '' + esc(text) + "" : "");
refs.custom.hidden = false;
}
// 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("");
}
// Self-hosted badge list → small icon row. The self-hosted API ships a
// ready-made `icon_url`; fall back to building it from `icon` if absent.
function renderSelfBadges(badges) {
if (!Array.isArray(badges)) return "";
return badges.map(function (b) {
var url = b.icon_url || ("https://cdn.discordapp.com/badge-icons/" + esc(b.icon) + ".png");
var img = '
';
return b.link
? '' + img + ""
: img;
}).join("");
}
// Self-hosted clan tag → guild badge + tag. Shape: {tag, badge_url}.
function renderSelfClan(refs, clan) {
if (!refs.tag) return;
if (clan && clan.tag) {
var b = clan.badge_url ? proxyImg(clan.badge_url) : null;
refs.tag.innerHTML = (b ? '
' : "") +
'' + esc(clan.tag) + "";
refs.tag.hidden = false;
} else {
refs.tag.hidden = true;
}
}
// 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 =
'