Rework Friends: Add in some extra components for friends

This commit is contained in:
Clove 2026-06-16 10:42:05 +01:00
parent 414eddea42
commit ec5c39f855
7 changed files with 108 additions and 27 deletions

BIN
assets/alts/butterfly.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 527 KiB

BIN
assets/alts/clove.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

BIN
assets/alts/mrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

BIN
assets/alts/uzi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

View File

@ -2039,6 +2039,34 @@ body:has(.friends-wrap) .hub {
padding-top: 0.1rem;
}
.fc-name-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.15rem 0.3rem;
min-width: 0;
}
/* name claims the full first line (so a clan tag never truncates it); the
tag chip wraps onto its own line just beneath when there isn't room */
.fc-name-row .fc-name { flex: 1 1 100%; min-width: 0; }
/* Discord server (clan) tag chip */
.fc-tag {
display: inline-flex;
align-items: center;
gap: 0.15rem;
flex-shrink: 0;
padding: 0.05rem 0.3rem;
border-radius: 5px;
background: var(--surface-2);
font-size: 0.56rem;
font-weight: 700;
letter-spacing: 0.02em;
color: var(--text);
}
.fc-tag[hidden] { display: none; }
.fc-tag-badge { width: 12px; height: 12px; display: block; }
.fc-name {
font-size: 0.95rem;
font-weight: 700;
@ -2076,6 +2104,35 @@ a.fc-name:hover { color: rgb(var(--accent-rgb)); }
.fc-badge { width: 16px; height: 16px; display: block; }
.fc-badge-link { display: inline-flex; line-height: 0; }
/* ---- Alts (active + dead) ---- */
.fc-name.active-alt::before { content: "🎭 "; }
.fc-name.dead-alt::before { content: "💀 "; }
/* dead alts: banned accounts — greyed out, name struck through, no status */
.friend-card.tier-dead-alt .fc-pfp {
filter: grayscale(1) brightness(0.6);
}
.friend-card.tier-dead-alt .fc-name {
color: var(--overlay-1);
text-decoration: line-through;
}
.friend-card.tier-dead-alt .fc-status { display: none; }
/* the "struck off" diagonal slash across the avatar */
.friend-card.tier-dead-alt .fc-avatar::after {
content: "";
position: absolute;
left: 50%;
top: 50%;
width: 132%;
height: 4px;
background: var(--red);
border-radius: 2px;
transform: translate(-50%, -50%) rotate(-45deg);
box-shadow: 0 0 0 2px var(--surface-0);
pointer-events: none;
}
/* =====================================================================
* MUSIC PAGE (/music) merged in from music.css.
* Hero classes are .mnp-* to avoid colliding with the .np-* now-playing
@ -3365,7 +3422,10 @@ body:has(.presence-stage) {
.presence-stage .presence-card.has-banner-color .pc-av-img {
width: 120px;
height: 120px;
border: 5px solid var(--surface-0);
/* solid dark plate + ring so the banner sits clearly BEHIND the avatar
(the PFP itself is partly transparent) */
background: var(--crust);
border: 6px solid var(--crust);
}
.presence-stage .pc-av-deco,
.presence-stage .presence-card.has-banner .pc-av-deco,

View File

@ -1,26 +1,6 @@
(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",
@ -53,6 +33,16 @@
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: "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 }
]
}
];
@ -89,8 +79,12 @@
}
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 });
// 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)
@ -107,6 +101,23 @@
}).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 ? '<img class="fc-tag-badge" src="' + b + '" alt="" onerror="this.remove()">' : "") +
'<span class="fc-tag-text">' + esc(pg.tag) + "</span>";
refs.tag.hidden = false;
} else {
refs.tag.hidden = true;
}
}
// ---- build one card -------------------------------------------------
function buildCard(m) {
var card = document.createElement("article");
@ -124,7 +135,10 @@
'<span class="fc-status" title="not connected to Lanyard"></span>' +
'</span>' +
'<span class="fc-id">' +
'<span class="fc-name-row">' +
'<' + nameTag + ' class="fc-name' + (m.tier ? " " + m.tier : "") + '"' + nameAttrs + '>' + esc(m.name) + '</' + nameTag + '>' +
'<span class="fc-tag" hidden></span>' +
'</span>' +
'<span class="fc-user"></span>' +
'<span class="fc-badges"></span>' +
'</span>' +
@ -135,6 +149,7 @@
pfp: card.querySelector(".fc-pfp"),
statusDot: card.querySelector(".fc-status"),
user: card.querySelector(".fc-user"),
tag: card.querySelector(".fc-tag"),
badges: card.querySelector(".fc-badges"),
banner: card.querySelector(".fc-banner")
};
@ -163,6 +178,7 @@
refs.statusDot.title = STATUS_TITLE[status] || "Offline";
if (u.avatar) refs.pfp.src = avatarUrl(u);
if (u.username) refs.user.textContent = "@" + u.username;
renderClanTag(refs, u.primary_guild);
return true;
})
.catch(function () {
@ -228,7 +244,8 @@
group.members.forEach(function (m) {
var refs = buildCard(m);
grid.appendChild(refs.el);
if (m.discordId) {
// dead alts are banned/retired — never pull live data for them
if (m.discordId && m.tier !== "dead-alt") {
liveMembers.push({ m: m, refs: refs });
refreshMember(m, refs);
}

View File

@ -237,8 +237,12 @@
// ---- banner / bio / connected accounts (extras for the /discord page) ---
function bannerUrl(id, hash) {
if (!id || !hash) return null;
const ext = String(hash).startsWith("a_") ? "gif" : "png";
return proxyImg("https://cdn.discordapp.com/banners/" + id + "/" + hash + "." + ext + "?size=600", { w: 600 });
// 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).
const animated = String(hash).startsWith("a_");
const url = "https://cdn.discordapp.com/banners/" + id + "/" + hash + (animated ? "" : ".png") + "?size=600";
return proxyImg(url, { w: 600 });
}
function applyBanner(url, fallbackColor) {
if (!bannerEl) return;