diff --git a/assets/fonts/8Bit.woff2 b/assets/fonts/8Bit.woff2 new file mode 100644 index 0000000..46715a3 Binary files /dev/null and b/assets/fonts/8Bit.woff2 differ diff --git a/assets/fonts/Jellybean.woff2 b/assets/fonts/Jellybean.woff2 new file mode 100644 index 0000000..4f06439 Binary files /dev/null and b/assets/fonts/Jellybean.woff2 differ diff --git a/assets/fonts/Medieval.woff2 b/assets/fonts/Medieval.woff2 new file mode 100644 index 0000000..950b1da Binary files /dev/null and b/assets/fonts/Medieval.woff2 differ diff --git a/assets/fonts/Modern.woff2 b/assets/fonts/Modern.woff2 new file mode 100644 index 0000000..54050d7 Binary files /dev/null and b/assets/fonts/Modern.woff2 differ diff --git a/assets/fonts/Sakura.woff2 b/assets/fonts/Sakura.woff2 new file mode 100644 index 0000000..2cf76f1 Binary files /dev/null and b/assets/fonts/Sakura.woff2 differ diff --git a/assets/fonts/Tempo.woff2 b/assets/fonts/Tempo.woff2 new file mode 100644 index 0000000..ddb6414 Binary files /dev/null and b/assets/fonts/Tempo.woff2 differ diff --git a/assets/fonts/Vampyre.woff2 b/assets/fonts/Vampyre.woff2 new file mode 100644 index 0000000..b506326 Binary files /dev/null and b/assets/fonts/Vampyre.woff2 differ diff --git a/assets/fonts/gg sans.woff2 b/assets/fonts/gg sans.woff2 new file mode 100644 index 0000000..bbf20bd Binary files /dev/null and b/assets/fonts/gg sans.woff2 differ diff --git a/css/fonts.css b/css/fonts.css index 6bb1bf3..dced325 100644 --- a/css/fonts.css +++ b/css/fonts.css @@ -29,4 +29,50 @@ url('https://fonts.doughmination.co.uk/ComicCode-Bold_2022-05-24-152309_zqkm.woff') format('woff'); font-weight: 700; font-style: normal; +} + +/* ---- Discord display-name style fonts (look-alikes, in /assets/fonts) ------- + Applied to .pc-name from display_name_styles.font_id (see js/discord.js). + Discord font_id -> file (confirmed): 3 Cherry Bomb=Sakura, 4 Chicle=Jellybean, + 6 MuseoModerno=Modern, 7 Néo-Castel=Medieval, 8 Pixelify=8Bit, 10 Sinistre=Vampyre, + 11 Default=gg sans, 12 Zilla Slab=Tempo. (1/2/5/9 have no file -> site font.) */ +@font-face { + font-family: 'DDN 8Bit'; + src: url('/assets/fonts/8Bit.woff2') format('woff2'); + font-display: swap; +} +@font-face { + font-family: 'DDN Jellybean'; + src: url('/assets/fonts/Jellybean.woff2') format('woff2'); + font-display: swap; +} +@font-face { + font-family: 'DDN Medieval'; + src: url('/assets/fonts/Medieval.woff2') format('woff2'); + font-display: swap; +} +@font-face { + font-family: 'DDN Modern'; + src: url('/assets/fonts/Modern.woff2') format('woff2'); + font-display: swap; +} +@font-face { + font-family: 'DDN Sakura'; + src: url('/assets/fonts/Sakura.woff2') format('woff2'); + font-display: swap; +} +@font-face { + font-family: 'DDN Tempo'; + src: url('/assets/fonts/Tempo.woff2') format('woff2'); + font-display: swap; +} +@font-face { + font-family: 'DDN Vampyre'; + src: url('/assets/fonts/Vampyre.woff2') format('woff2'); + font-display: swap; +} +@font-face { + font-family: 'DDN gg sans'; + src: url('/assets/fonts/gg%20sans.woff2') format('woff2'); + font-display: swap; } \ No newline at end of file diff --git a/css/main.css b/css/main.css index 1ad83ce..8658ce0 100644 --- a/css/main.css +++ b/css/main.css @@ -3386,7 +3386,22 @@ a.pc-row:hover, } a.pc-wl-item:hover { background: var(--mantle); } .pc-wl-ic { width: 22px; height: 22px; border-radius: 5px; object-fit: cover; } +.pc-wl-text { display: flex; flex-direction: column; line-height: 1.2; min-width: 0; } .pc-wl-name { font-size: 0.8rem; } +.pc-wl-type { + font-size: 0.6rem; + text-transform: uppercase; + letter-spacing: 0.04em; + color: var(--subtext-0); +} +.pc-wl-price { + margin-left: auto; + padding-left: 0.5rem; + font-size: 0.72rem; + color: var(--subtext-1); + white-space: nowrap; +} +.pc-wl-item.is-owned { opacity: 0.5; } .pc-wl-empty { font-size: 0.78rem; color: var(--subtext-0); margin: 0; } /* Discord profile gradient (Catppuccin is the fallback) */ diff --git a/js/discord.js b/js/discord.js index 0a3cf04..6b4fb72 100644 --- a/js/discord.js +++ b/js/discord.js @@ -86,23 +86,60 @@ const pronounsEl = card.querySelector(".pc-pronouns"); // ---- wishlist (revealed by the star) ------------------------------------ + // Items come straight from the Doughmination Restful API (j.data.wishlist): + // each is a resolved Shop item { sku_id, type, name, static_image_url, + // animated_image_url, video_url, label, is_owned, price, visibility }. let wishlistItems = null; + const WL_TYPE_LABEL = { + avatar_decoration: "Decoration", + profile_effect: "Effect", + nameplate: "Nameplate", + bundle: "Bundle", + variants_group: "Variants", + external_sku: "Item" + }; + const CURRENCY_SYMBOL = { gbp: "£", usd: "$", eur: "€", aud: "A$", cad: "C$" }; + function fmtPrice(p) { + if (!p || typeof p.amount !== "number") return null; + const exp = typeof p.exponent === "number" ? p.exponent : 2; + const v = (p.amount / Math.pow(10, exp)).toFixed(exp); + const sym = CURRENCY_SYMBOL[(p.currency || "").toLowerCase()]; + return sym ? sym + v : v + " " + String(p.currency || "").toUpperCase(); + } + // Pick a thumbnail and decide whether the wsrv webp proxy is safe: avatar + // decorations and profile effects are animated APNGs the proxy mangles (the + // same reason the avatar decoration loads raw), so those go straight to the + // CDN; nameplates and the rest are static and proxy fine. + function wlImg(w) { + const url = w.static_image_url || w.animated_image_url; + if (!url) return null; + if (w.type === "avatar_decoration" || w.type === "profile_effect" || /avatar-decoration-presets/.test(url)) { + return url; + } + return proxyImg(url, { w: 64 }) || url; + } function renderWishlist() { if (!wishlistEl) return; - if (wishlistItems && wishlistItems.length) { - wishlistEl.innerHTML = '
Wishlist
' + - wishlistItems.map(function (w) { - const inner = - (w.icon ? '' : "") + - '' + esc(w.name || "") + ""; - return w.url - ? '' + inner + "" - : '' + inner + ""; - }).join(""); + const items = Array.isArray(wishlistItems) ? wishlistItems : []; + let body; + if (items.length) { + body = items.map(function (w) { + const ic = wlImg(w); + const typeLabel = WL_TYPE_LABEL[w.type] || ""; + const price = fmtPrice(w.price); + return '' + + (ic ? '' : "") + + '' + + '' + esc(w.name || "Collectible") + "" + + (typeLabel ? '' + esc(typeLabel) + "" : "") + + "" + + (price ? '' + esc(price) + "" : "") + + ""; + }).join(""); } else { - wishlistEl.innerHTML = '
Wishlist
' + - '

coming soon ✨

'; + body = '

nothing on the wishlist yet ✨

'; } + wishlistEl.innerHTML = '
Wishlist
' + body; } if (starBtn) { starBtn.addEventListener("click", function (e) { @@ -476,6 +513,21 @@ return row; } + // Discord display-name fonts (display_name_styles.font_id) -> our @font-face + // families in css/fonts.css. Only ids we have a look-alike for are mapped; + // any other id falls back to the card's normal font. (Comments = Discord's + // underlying font; "verify" = best-guess pairing with your file names.) + const NAME_FONTS = { + 3: "'DDN Sakura', cursive", // 3 CHERRY_BOMB + 4: "'DDN Jellybean', cursive", // 4 CHICLE + 6: "'DDN Modern', sans-serif", // 6 MUSEO_MODERNO + 7: "'DDN Medieval', serif", // 7 NEO_CASTEL + 8: "'DDN 8Bit', monospace", // 8 PIXELIFY + 10: "'DDN Vampyre', serif", // 10 SINISTRE + 11: "'DDN gg sans', sans-serif", // 11 DEFAULT (Discord's normal font) + 12: "'DDN Tempo', serif", // 12 ZILLA_SLAB + }; + // ---- render ------------------------------------------------------------- function render(d) { if (!d) return; @@ -508,6 +560,9 @@ nameEl.style.backgroundImage = ""; nameEl.classList.remove("is-gradient"); } + // Custom display-name font from Discord's font_id (falls back to the + // card's normal font when there's no style or no look-alike for that id). + nameEl.style.fontFamily = (styles && NAME_FONTS[styles.font_id]) || ""; const pg = u.primary_guild; if (pg && pg.tag && pg.identity_enabled) { @@ -613,7 +668,10 @@ primary_guild: (clan && clan.tag) ? { tag: clan.tag, identity_enabled: true, badge: clan.badge, identity_guild_id: clan.guild_id } : null, - public_flags: 0 + // carry the Nitro name styling through so render() can apply the + // gradient + custom font (font_id) — without this it never reaches it + display_name_styles: u.display_name_styles || null, + public_flags: u.public_flags || 0 }, discord_status: p.status || (p.online ? "online" : "offline"), activities: p.activities || [], @@ -649,6 +707,10 @@ if (u.pronouns) { pronounsEl.textContent = u.pronouns; pronounsEl.hidden = false; } else pronounsEl.hidden = true; } + // wishlist: resolved Shop collectibles (null when the API couldn't load it). + // Keep the panel live if it's already open when fresh data arrives. + wishlistItems = Array.isArray(j.data.wishlist) ? j.data.wishlist : null; + if (card.classList.contains("show-wishlist")) renderWishlist(); } function loadSelfHosted() { diff --git a/js/friends.js b/js/friends.js index c6e7684..1ba0d6d 100644 --- a/js/friends.js +++ b/js/friends.js @@ -86,29 +86,20 @@ } // 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's 8 display-name fonts -> closest free Google Fonts (approximations; - // Discord's own faces are proprietary). font_id 1 = gg sans = our default. + // 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 = { - 2: '"Poppins", sans-serif', // Tempo - 3: '"Klee One", cursive', // Sakura - 4: '"Baloo 2", cursive', // Jellybean - 5: '"Montserrat", sans-serif', // Modern - 6: '"MedievalSharp", cursive', // Medieval - 7: '"Press Start 2P", monospace', // 8Bit - 8: '"Pirata One", system-ui' // Vampyre + 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 }; - var GFONTS_HREF = "https://fonts.googleapis.com/css2?" + - "family=Baloo+2:wght@600&family=Klee+One:wght@600&family=MedievalSharp&" + - "family=Montserrat:wght@700&family=Pirata+One&family=Poppins:wght@600&" + - "family=Press+Start+2P&display=swap"; - var _fontsInjected = false; - function ensureFonts() { - if (_fontsInjected) return; - _fontsInjected = true; - var l = document.createElement("link"); - l.rel = "stylesheet"; l.href = GFONTS_HREF; - document.head.appendChild(l); - } function applyNameStyle(refs, styles) { if (!refs.name || !styles) return; // gradient / colour @@ -118,9 +109,9 @@ (cols.length === 1 ? cols[0] + "," + cols[0] : cols.join(", ")) + ")"; refs.name.classList.add("is-gradient"); } - // font face (lazy-load the Google Fonts sheet only when one is used) + // custom display-name font (local @font-face; no network load needed) var fam = styles.font_id && FONT_BY_ID[styles.font_id]; - if (fam) { ensureFonts(); refs.name.style.fontFamily = fam; } + 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. diff --git a/readme.md b/readme.md index a4d1e7e..151f543 100644 --- a/readme.md +++ b/readme.md @@ -10,7 +10,7 @@ I made this cause I kept losing track of everything, and yeah... ## License -This repository is licenced under MIT, meaning if you wish to copy it, that's fine, if it breaks, don't blame me. See the LICENSE for more details! +This repository is licenced under ESAL-1.5, please review the terms before copying this code. ## Codeowners and Contributors All code is owned and created by myself, Clove Twilight. \ No newline at end of file