(function presence() {
  "use strict";

  // ---- who are we showing? ------------------------------------------------
  function valid(id) { return typeof id === "string" && /^\d{5,25}$/.test(id); }
  function resolveUserId() {
    const path = location.pathname.match(/\/api\/(\d{5,25})(?:[\/?#]|$)/);
    if (path) return path[1];
    const qs = new URLSearchParams(location.search);
    const q = qs.get("u") || qs.get("id") || qs.get("user");
    if (valid(q)) return q;
    if (/^#\d{5,25}$/.test(location.hash)) return location.hash.slice(1);
    const script = document.currentScript || document.querySelector("script[data-user]");
    if (script && script.dataset && valid(script.dataset.user)) return script.dataset.user;
    const m = document.getElementById("now-playing");
    if (m && m.dataset && valid(m.dataset.user)) return m.dataset.user;
    if (valid(window.NOW_PLAYING_USER_ID)) return window.NOW_PLAYING_USER_ID;
    return null;
  }

  const DISCORD_USER_ID = resolveUserId();
  const mount = document.getElementById("now-playing");
  if (!mount || !DISCORD_USER_ID) return;

  // ---- theme: only on standalone api pages (homepage uses data-flavor) ----
  if (!document.documentElement.getAttribute("data-flavor")) {
    const t = new URLSearchParams(location.search).get("theme");
    const themes = ["mocha", "macchiato", "frappe", "latte"];
    if (!document.documentElement.getAttribute("data-theme")) {
      document.documentElement.setAttribute("data-theme", themes.indexOf(t) >= 0 ? t : "mocha");
    }
  }

  // ---- build the card -----------------------------------------------------
  const card = document.createElement("div");
  card.id = "now-playing";
  card.className = "presence-card";
  card.hidden = true;
  card.innerHTML =
    '<img class="pc-banner" alt="" referrerpolicy="no-referrer" hidden>' +
    '<div class="pc-head">' +
      '<span class="pc-avatar">' +
        '<img class="pc-av-img" alt="" referrerpolicy="no-referrer" crossorigin="anonymous">' +
        '<img class="pc-av-deco" alt="" aria-hidden="true" hidden>' +
        '<span class="pc-status" aria-hidden="true"></span>' +
      '</span>' +
      '<span class="pc-id">' +
        '<span class="pc-name-row">' +
          '<span class="pc-name"></span>' +
          '<span class="pc-tag" hidden></span>' +
        '</span>' +
        '<span class="pc-sub-row">' +
          '<span class="pc-user"></span>' +
          '<span class="pc-status-text"></span>' +
          '<span class="pc-pronouns" hidden></span>' +
          '<span class="pc-platforms" aria-hidden="true"></span>' +
        '</span>' +
        '<span class="pc-meta" hidden></span>' +
        '<span class="pc-badges" aria-hidden="true"></span>' +
      '</span>' +
      '<button class="pc-star" type="button" aria-label="show wishlist" title="wishlist">★</button>' +
    '</div>' +
    '<div class="pc-bio" hidden></div>' +
    '<div class="pc-connections" hidden></div>' +
    '<div class="pc-sections"></div>' +
    '<div class="pc-wishlist" id="pc-wishlist"></div>';
  mount.replaceWith(card);

  const avImg = card.querySelector(".pc-av-img");
  const avDeco = card.querySelector(".pc-av-deco");
  const nameEl = card.querySelector(".pc-name");
  const tagEl = card.querySelector(".pc-tag");
  const userEl = card.querySelector(".pc-user");
  const platformsEl = card.querySelector(".pc-platforms");
  const statusTextEl = card.querySelector(".pc-status-text");
  const STATUS_TITLE = { online: "Online", idle: "Idle", dnd: "Do Not Disturb", offline: "Offline" };
  const metaEl = card.querySelector(".pc-meta");
  const badgesEl = card.querySelector(".pc-badges");
  const sections = card.querySelector(".pc-sections");
  const idEl = card.querySelector(".pc-id");
  const starBtn = card.querySelector(".pc-star");
  const wishlistEl = card.querySelector(".pc-wishlist");
  const bannerEl = card.querySelector(".pc-banner");
  const bioEl = card.querySelector(".pc-bio");
  const connectionsEl = card.querySelector(".pc-connections");
  const pronounsEl = card.querySelector(".pc-pronouns");

  // ---- wishlist (revealed by the star) ------------------------------------
  let wishlistItems = null;
  function renderWishlist() {
    if (!wishlistEl) return;
    if (wishlistItems && wishlistItems.length) {
      wishlistEl.innerHTML = '<div class="pc-wishlist-title">Wishlist</div>' +
        wishlistItems.map(function (w) {
          const inner =
            (w.icon ? '<img class="pc-wl-ic" src="' + esc(w.icon) + '" alt="">' : "") +
            '<span class="pc-wl-name">' + esc(w.name || "") + "</span>";
          return w.url
            ? '<a class="pc-wl-item" href="' + esc(w.url) + '" target="_blank" rel="noopener">' + inner + "</a>"
            : '<span class="pc-wl-item">' + inner + "</span>";
        }).join("");
    } else {
      wishlistEl.innerHTML = '<div class="pc-wishlist-title">Wishlist</div>' +
        '<p class="pc-wl-empty">coming soon ✨</p>';
    }
  }
  if (starBtn) {
    starBtn.addEventListener("click", function (e) {
      e.stopPropagation();
      const open = card.classList.toggle("show-wishlist");
      starBtn.classList.toggle("on", open);
      starBtn.setAttribute("aria-expanded", open ? "true" : "false");
      if (open) renderWishlist();
    });
  }

  let latest = null;
  let customNode = null;
  let ticker = null;
  let ws = null;
  let heartbeat = null;
  let reconnectDelay = 1000;

  // ---- small helpers ------------------------------------------------------
  function fmt(ms) {
    const total = Math.max(0, Math.floor(ms / 1000));
    const m = Math.floor(total / 60);
    const s = total % 60;
    return `${m}:${String(s).padStart(2, "0")}`;
  }
  function elapsedStr(start) {
    const s = Math.max(0, Math.floor((Date.now() - start) / 1000));
    const h = Math.floor(s / 3600);
    const m = Math.floor((s % 3600) / 60);
    return h ? `${h}h ${m}m` : `${m}m`;
  }
  function clamp(n, lo, hi) { return Math.min(Math.max(n, lo), hi); }

  // pages.gay is a static host with no server-side compute, so we can't run
  // our own proxy. wsrv.nl is a free, cookieless image CDN: it re-serves the
  // image with no Set-Cookie (killing the third-party __cf_bm cookie) and can
  // convert to WebP on the fly. `opts` lets callers request a resize.
  function proxyImg(url, opts) {
    if (!url) return url;
    if (!/^https:\/\/(cdn|media)\.discordapp\.(com|net)\//.test(url)) return url;
    const src = url.replace(/^https:\/\//, "");
    let 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) return proxyImg("https://cdn.discordapp.com/embed/avatars/0.png");
    const ext = String(u.avatar).startsWith("a_") ? "gif" : "png";
    return proxyImg(`https://cdn.discordapp.com/avatars/${u.id}/${u.avatar}.${ext}?size=128`, { w: 80 });
  }
  function emojiUrl(e) {
    if (!e || !e.id) return null;
    return proxyImg(`https://cdn.discordapp.com/emojis/${e.id}.${e.animated ? "gif" : "png"}?size=32`);
  }
  function assetUrl(appId, asset) {
    if (!asset) return null;
    if (String(asset).startsWith("mp:")) return proxyImg("https://media.discordapp.net/" + asset.slice(3));
    return proxyImg(`https://cdn.discordapp.com/app-assets/${appId}/${asset}.png`);
  }
  function esc(str) {
    return String(str == null ? "" : str)
      .replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
  }
  function intToHex(n) {
    return "#" + (Number(n) >>> 0).toString(16).padStart(6, "0").slice(-6);
  }
  function guildBadgeUrl(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`);
  }
  const PLATFORM_ICONS = {
    desktop: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="13" rx="1.5"/><path d="M8 21h8M12 17v4"/></svg>',
    mobile: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="7" y="2" width="10" height="20" rx="2.5"/><path d="M11 18h2"/></svg>',
    web: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c2.5 2.5 2.5 15 0 18M12 3c-2.5 2.5-2.5 15 0 18"/></svg>'
  };
  function platformIcons(d) {
    let html = "";
    if (d.active_on_discord_desktop) html += '<span class="pc-plat" title="Desktop">' + PLATFORM_ICONS.desktop + "</span>";
    if (d.active_on_discord_mobile) html += '<span class="pc-plat" title="Mobile">' + PLATFORM_ICONS.mobile + "</span>";
    if (d.active_on_discord_web || d.active_on_discord_embedded) html += '<span class="pc-plat" title="Web">' + PLATFORM_ICONS.web + "</span>";
    return html;
  }
  const BADGE_FLAGS = [
    [1 << 0, "Discord Staff", "5e74e9b61934fc1f67c65515d1f7e60d"],
    [1 << 1, "Partnered Server Owner", "3f9748e53446a137a052f3454e2de41e"],
    [1 << 2, "HypeSquad Events", "bf01d1073931f921909045f3a39fd264"],
    [1 << 3, "Bug Hunter", "2717692c7dca7289b35297368a940dd0"],
    [1 << 6, "HypeSquad Bravery", "8a88d63823d8a71cd5e390baa45efa02"],
    [1 << 7, "HypeSquad Brilliance", "011940fd013da3f7fb926e4a1cd2e618"],
    [1 << 8, "HypeSquad Balance", "3aa41de486fa12454c3761e8e223442e"],
    [1 << 9, "Early Supporter", "7060786766c9c840eb3019e725d2b358"],
    [1 << 14, "Bug Hunter Gold", "848f79194d4be5ff5f81505cbd0ce1e6"],
    [1 << 17, "Early Verified Bot Developer", "6df5892e0f35b051f8b61eace34f4967"],
    [1 << 18, "Moderator Programs Alumni", "fee1624003e2fee35cb398e125dc479b"],
    [1 << 22, "Active Developer", "6bdc42827a38498929a4920da12695d9"]
  ];
  function renderBadges(flags) {
    flags = Number(flags) || 0;
    let html = "";
    for (const [bit, name, hash] of BADGE_FLAGS) {
      if (flags & bit) {
        html += '<img class="pc-badge" src="' + proxyImg("https://cdn.discordapp.com/badge-icons/" + hash + ".png") +
          '" alt="' + esc(name) + '" title="' + esc(name) + '" onerror="this.remove()">';
      }
    }
    return html;
  }

  // Richer badges via dstn.to — Nitro, boosts, quests, orbs… everything
  // Discord actually shows, which public_flags (0 for most) can't give.
  let dstnBadges = null;
  let lastFlags = 0;
  function renderDstnBadges() {
    return dstnBadges.map(function (b) {
      const img = '<img class="pc-badge" src="' + proxyImg("https://cdn.discordapp.com/badge-icons/" + esc(b.icon) + ".png") +
        '" alt="' + esc(b.description || b.id) + '" title="' + esc(b.description || b.id) + '" onerror="this.remove()">';
      return b.link
        ? '<a class="pc-badge-link" tabindex="-1" href="' + esc(b.link) + '" target="_blank" rel="noopener">' + img + "</a>"
        : img;
    }).join("");
  }
  function paintBadges() {
    if (!badgesEl) return;
    badgesEl.innerHTML = (dstnBadges && dstnBadges.length) ? renderDstnBadges() : renderBadges(lastFlags);
  }
  function rgbTriplet(n) {
    n = Number(n) >>> 0;
    return ((n >> 16) & 255) + ", " + ((n >> 8) & 255) + ", " + (n & 255);
  }
  function applyProfileGradient(colors) {
    if (!colors || colors.length < 2) return;
    card.style.setProperty("--pc-grad-1-rgb", rgbTriplet(colors[0]));
    card.style.setProperty("--pc-grad-2-rgb", rgbTriplet(colors[1]));
    card.classList.add("has-profile-grad");
  }
  // ---- banner / bio / connected accounts (extras for the /discord page) ---
  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).
    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;
    if (url) {
      bannerEl.src = url;
      bannerEl.hidden = false;
      bannerEl.onerror = function () { bannerEl.hidden = true; card.classList.remove("has-banner"); };
      card.classList.add("has-banner");
    } else if (fallbackColor) {
      bannerEl.hidden = true;
      card.style.setProperty("--pc-banner-color", fallbackColor);
      card.classList.add("has-banner-color");
    }
  }
  function renderBio(text) {
    if (!bioEl) return;
    if (text && String(text).trim()) {
      bioEl.textContent = String(text).trim();
      bioEl.hidden = false;
    } else {
      bioEl.hidden = true;
    }
  }
  // Best-effort profile links for the common connection types.
  const CONNECTION_URLS = {
    github: function (n) { return "https://github.com/" + n; },
    twitter: function (n) { return "https://twitter.com/" + n; },
    twitch: function (n) { return "https://twitch.tv/" + n; },
    youtube: function (n, id) { return "https://youtube.com/channel/" + id; },
    spotify: function (n, id) { return "https://open.spotify.com/user/" + id; },
    steam: function (n, id) { return "https://steamcommunity.com/profiles/" + id; },
    reddit: function (n) { return "https://reddit.com/user/" + n; },
    instagram: function (n) { return "https://instagram.com/" + n; },
    domain: function (n) { return "https://" + n; },
    bluesky: function (n) { return "https://bsky.app/profile/" + n; }
  };
  function renderConnections(accounts) {
    if (!connectionsEl) return;
    const list = (accounts || []).filter(function (a) { return a && a.name; });
    if (!list.length) { connectionsEl.hidden = true; return; }
    connectionsEl.innerHTML = list.map(function (a) {
      const maker = CONNECTION_URLS[a.type];
      const url = maker ? maker(a.name, a.id) : null;
      const inner = '<span class="pc-conn-type">' + esc(a.type) + "</span>" +
        '<span class="pc-conn-name">' + esc(a.name) + "</span>" +
        (a.verified ? '<span class="pc-conn-check" title="Verified">✓</span>' : "");
      return url
        ? '<a class="pc-conn" href="' + esc(url) + '" target="_blank" rel="noopener">' + inner + "</a>"
        : '<span class="pc-conn">' + inner + "</span>";
    }).join("");
    connectionsEl.hidden = false;
  }

  function loadDstn() {
    fetch("https://dcdn.dstn.to/profile/" + DISCORD_USER_ID)
      .then(function (r) { return r.ok ? r.json() : null; })
      .then(function (j) {
        if (!j) return;
        if (Array.isArray(j.badges)) { dstnBadges = j.badges; paintBadges(); }
        if (j.user_profile && Array.isArray(j.user_profile.theme_colors)) {
          applyProfileGradient(j.user_profile.theme_colors);
        }
        const u = j.user || {};
        const prof = j.user_profile || {};
        applyBanner(
          bannerUrl(u.id || DISCORD_USER_ID, prof.banner || u.banner),
          u.banner_color || (typeof u.accent_color === "number" ? intToHex(u.accent_color) : null)
        );
        renderBio(prof.bio || u.bio);
        renderConnections(j.connected_accounts);
        if (pronounsEl) {
          const pr = prof.pronouns || u.pronouns;
          if (pr) { pronounsEl.textContent = pr; pronounsEl.hidden = false; }
          else pronounsEl.hidden = true;
        }
      })
      .catch(function () {});
  }

  // ---- album-art → Catppuccin accent --------------------------------------
  const ACCENT_VARS = [
    "rosewater", "flamingo", "pink", "mauve", "red", "maroon", "peach",
    "yellow", "green", "teal", "sky", "saphire", "blue", "lavender",
  ];
  function hexToRgb(hex) {
    hex = hex.trim().replace("#", "");
    if (hex.length === 3) hex = hex.split("").map((c) => c + c).join("");
    const n = parseInt(hex, 16);
    return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
  }
  function getThemePalette() {
    const cs = getComputedStyle(document.documentElement);
    const pal = [];
    for (const name of ACCENT_VARS) {
      const v = cs.getPropertyValue("--" + name).trim();
      if (v.startsWith("#")) { const [r, g, b] = hexToRgb(v); pal.push({ r, g, b }); }
    }
    return pal;
  }
  function nearestAccent(r, g, b) {
    const pal = getThemePalette();
    let best = null, bestD = Infinity;
    for (const c of pal) {
      const rm = (r + c.r) / 2, dr = r - c.r, dg = g - c.g, db = b - c.b;
      const d = (2 + rm / 256) * dr * dr + 4 * dg * dg + (2 + (255 - rm) / 256) * db * db;
      if (d < bestD) { bestD = d; best = c; }
    }
    return best;
  }
  let lastArtUrl = null;
  function applyAccent(url) {
    if (!url || url === lastArtUrl) return;
    lastArtUrl = url;
    const img = new Image();
    img.crossOrigin = "anonymous";
    img.referrerPolicy = "no-referrer";
    img.onload = () => {
      try {
        const c = document.createElement("canvas");
        c.width = c.height = 16;
        const ctx = c.getContext("2d", { willReadFrequently: true });
        ctx.drawImage(img, 0, 0, 16, 16);
        const { data } = ctx.getImageData(0, 0, 16, 16);
        let r = 0, g = 0, b = 0, count = 0;
        for (let i = 0; i < data.length; i += 4) {
          if (data[i + 3] < 125) continue;
          const lum = 0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2];
          if (lum < 24 || lum > 235) continue;
          r += data[i]; g += data[i + 1]; b += data[i + 2]; count++;
        }
        if (!count) { resetAccent(); return; }
        r = Math.round(r / count); g = Math.round(g / count); b = Math.round(b / count);
        const near = nearestAccent(r, g, b);
        const rgb = near ? `${near.r}, ${near.g}, ${near.b}` : `${r}, ${g}, ${b}`;
        card.style.setProperty("--np-accent", rgb);
        card.classList.add("has-accent");
        document.documentElement.style.setProperty("--accent-rgb", rgb);
      } catch (e) { resetAccent(); }
    };
    img.onerror = resetAccent;
    img.src = url;
  }
  function resetAccent() {
    lastArtUrl = null;
    card.classList.remove("has-accent");
    card.style.removeProperty("--np-accent");
    document.documentElement.style.removeProperty("--accent-rgb");
  }

  // ---- section (row) builders --------------------------------------------
  function rowText(kind, title, sub, extra) {
    return (
      '<span class="pc-row-text">' +
        '<span class="pc-row-kind">' + esc(kind) + "</span>" +
        '<span class="pc-row-title">' + esc(title) + "</span>" +
        '<span class="pc-row-sub">' + esc(sub) + "</span>" +
        (extra || "") +
      "</span>"
    );
  }

  function customRow(a) {
    const row = document.createElement("div");
    row.className = "pc-row pc-custom";
    const eu = emojiUrl(a.emoji);
    row.innerHTML =
      (eu ? '<img class="pc-emoji" src="' + eu + '" alt="">'
          : '<span class="pc-row-ic pc-dot" aria-hidden="true"></span>') +
      '<span class="pc-custom-text">' + esc(a.state || "") + "</span>";
    return row;
  }

  function spotifyRow(s) {
    const row = document.createElement("a");
    row.className = "pc-row pc-spotify";
    row.target = "_blank";
    row.rel = "noopener";
    row.href = s.track_id ? "https://open.spotify.com/track/" + s.track_id : "https://open.spotify.com/";
    if (s.album) row.title = (s.song || "") + " — " + s.album;
    if (s.timestamps && s.timestamps.start) row.dataset.start = s.timestamps.start;
    if (s.timestamps && s.timestamps.end) row.dataset.end = s.timestamps.end;
    row.innerHTML =
      (s.album_art_url ? '<img class="pc-art" src="' + esc(s.album_art_url) + '" alt="">' : "") +
      rowText("Listening to Spotify", s.song || "", s.artist || "",
        '<span class="pc-progress" aria-hidden="true">' +
          '<span class="pc-bar"><span class="pc-fill"></span></span>' +
          '<span class="pc-times"><span class="pc-cur">0:00</span><span class="pc-dur">0:00</span></span>' +
        "</span>");
    return row;
  }

  // Generic activity row (type 0). Discord presence exposes no link for
  // games or apps, so this renders as a non-clickable card.
  function activityRow(a) {
    const isCode = /visual studio code|vscode/i.test(a.name || "");
    const row = document.createElement("div");
    row.className = "pc-row pc-row--stack " + (isCode ? "pc-dev" : "pc-game");
    if (a.timestamps && a.timestamps.start) row.dataset.elapsedStart = a.timestamps.start;

    const large = a.assets && a.assets.large_image && assetUrl(a.application_id, a.assets.large_image);
    const small = a.assets && a.assets.small_image && assetUrl(a.application_id, a.assets.small_image);
    const iconHtml = large
      ? '<span class="pc-ic-wrap">' +
          '<img class="pc-row-ic-img" src="' + esc(large) + '" alt="">' +
          (small ? '<img class="pc-ic-badge" src="' + esc(small) + '" alt="" title="' + esc(a.assets.small_text || "") + '" onerror="this.remove()">' : "") +
        "</span>"
      : '<span class="pc-row-ic pc-dot" aria-hidden="true"></span>';

    let kind = isCode ? "Coding" : "Playing " + (a.name || "");
    if (a.party && a.party.size && a.party.size.length === 2 && a.party.size[1]) {
      kind += " · " + a.party.size[0] + " of " + a.party.size[1];
    }

    const main = document.createElement("div");
    main.className = "pc-row-link";
    main.innerHTML = iconHtml +
      rowText(kind, a.details || (isCode ? "" : a.name) || "",
              a.state || (a.assets && a.assets.large_text) || "",
              '<span class="pc-row-elapsed"></span>');
    row.appendChild(main);

    // Discord only exposes button *labels* (not URLs) via presence, so these
    // are shown as plain (non-clickable) chips.
    if (a.buttons && a.buttons.length) {
      const bwrap = document.createElement("div");
      bwrap.className = "pc-buttons";
      a.buttons.forEach(function (label) {
        const b = document.createElement("span");
        b.className = "pc-btn";
        b.textContent = typeof label === "string" ? label : (label && label.label) || "Open";
        bwrap.appendChild(b);
      });
      row.appendChild(bwrap);
    }
    return row;
  }

  function streamRow(a) {
    const hasUrl = !!a.url;
    const row = document.createElement(hasUrl ? "a" : "div");
    row.className = "pc-row pc-stream";
    if (hasUrl) {
      row.target = "_blank";
      row.rel = "noopener";
      row.href = a.url;
    }
    const platform = (a.url && /twitch/i.test(a.url)) ? "Twitch"
                   : (a.url && /youtube/i.test(a.url)) ? "YouTube" : "Live";
    row.innerHTML =
      '<span class="pc-row-ic pc-dot" aria-hidden="true"></span>' +
      rowText("Streaming on " + platform, a.details || a.name || "", a.state || "");
    return row;
  }

  // ---- render -------------------------------------------------------------
  function render(d) {
    if (!d) return;
    latest = d;

    const u = d.discord_user || {};
    const status = d.discord_status || "offline";
    card.dataset.status = status;
    if (statusTextEl) statusTextEl.textContent = STATUS_TITLE[status] || "Offline";

    avImg.src = avatarUrl(u);
    const deco = u.avatar_decoration_data;
    if (deco && deco.asset) {
      avDeco.src = proxyImg(`https://cdn.discordapp.com/avatar-decoration-presets/${deco.asset}.png?size=160`);
      avDeco.hidden = false;
    } else {
      avDeco.hidden = true;
    }
    nameEl.textContent = u.display_name || u.global_name || u.username || "Discord User";
    userEl.textContent = u.username ? "@" + u.username : "";

    const styles = u.display_name_styles;
    if (styles && styles.colors && styles.colors.length) {
      const cols = styles.colors.map(intToHex);
      nameEl.style.backgroundImage = "linear-gradient(90deg, " + (cols.length === 1 ? cols[0] + "," + cols[0] : cols.join(", ")) + ")";
      nameEl.classList.add("is-gradient");
    } else {
      nameEl.style.backgroundImage = "";
      nameEl.classList.remove("is-gradient");
    }

    const pg = u.primary_guild;
    if (pg && pg.tag && pg.identity_enabled) {
      const badge = guildBadgeUrl(pg);
      tagEl.innerHTML = (badge ? '<img class="pc-tag-badge" src="' + badge + '" alt="" onerror="this.remove()">' : "") +
        '<span class="pc-tag-text">' + esc(pg.tag) + "</span>";
      tagEl.hidden = false;
    } else {
      tagEl.hidden = true;
    }

    platformsEl.innerHTML = platformIcons(d);

    lastFlags = u.public_flags || 0;
    paintBadges();

    const loc = d.kv && d.kv.location;
    if (loc) {
      metaEl.innerHTML = '<span class="pc-pin" aria-hidden="true">📍</span>' + esc(loc);
      metaEl.hidden = false;
    } else {
      metaEl.hidden = true;
    }

    const acts = d.activities || [];

    sections.innerHTML = "";

    // The custom status renders in the identity column, directly under the
    // name, so its thought-bubble tail rises to the username (Discord-style)
    // — rather than down in the activity list.
    if (customNode) { customNode.remove(); customNode = null; }
    const custom = acts.find((a) => a.type === 4);
    if (custom && (custom.state || (custom.emoji && custom.emoji.id))) {
      customNode = customRow(custom);
      idEl.appendChild(customNode);
    }
    card.classList.toggle("has-custom", !!customNode);

    if (d.listening_to_spotify && d.spotify) {
      sections.appendChild(spotifyRow(d.spotify));
      applyAccent(d.spotify.album_art_url);
    } else {
      resetAccent();
    }

    acts.filter((a) => a.type === 0).forEach((a) => sections.appendChild(activityRow(a)));
    acts.filter((a) => a.type === 1).forEach((a) => sections.appendChild(streamRow(a)));

    card.classList.toggle("has-sections", sections.children.length > 0);
    updateTimes();
    if (sections.querySelector("[data-start], [data-elapsed-start]")) startTicker();
    else stopTicker();

    card.hidden = false;
  }

  // ---- time tickers (progress bar + elapsed labels) -----------------------
  function updateTimes() {
    const sp = sections.querySelector(".pc-spotify[data-start][data-end]");
    if (sp) {
      const start = +sp.dataset.start, end = +sp.dataset.end;
      if (end > start) {
        const elapsed = clamp(Date.now() - start, 0, end - start);
        const fill = sp.querySelector(".pc-fill");
        const cur = sp.querySelector(".pc-cur");
        const dur = sp.querySelector(".pc-dur");
        if (fill) fill.style.width = clamp((elapsed / (end - start)) * 100, 0, 100) + "%";
        if (cur) cur.textContent = fmt(elapsed);
        if (dur) dur.textContent = fmt(end - start);
      }
    }
    sections.querySelectorAll("[data-elapsed-start]").forEach((row) => {
      const lbl = row.querySelector(".pc-row-elapsed");
      if (lbl) lbl.textContent = elapsedStr(+row.dataset.elapsedStart);
    });
  }
  function startTicker() { if (!ticker) ticker = setInterval(updateTimes, 1000); }
  function stopTicker() { if (ticker) { clearInterval(ticker); ticker = null; } }

  // ---- Lanyard websocket --------------------------------------------------
  function connect() {
    ws = new WebSocket("wss://api.lanyard.rest/socket");

    ws.addEventListener("message", (evt) => {
      let msg;
      try { msg = JSON.parse(evt.data); } catch (e) { return; }

      if (msg.op === 1) {
        const interval = (msg.d && msg.d.heartbeat_interval) || 30000;
        if (heartbeat) clearInterval(heartbeat);
        heartbeat = setInterval(() => {
          if (ws && ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ op: 3 }));
        }, interval);
        ws.send(JSON.stringify({ op: 2, d: { subscribe_to_id: DISCORD_USER_ID } }));
        return;
      }

      if (msg.op === 0) {
        const d = msg.t === "INIT_STATE" ? (msg.d && msg.d[DISCORD_USER_ID]) || msg.d : msg.d;
        render(d);
      }
    });

    ws.addEventListener("open", () => { reconnectDelay = 1000; });
    ws.addEventListener("close", () => {
      if (heartbeat) { clearInterval(heartbeat); heartbeat = null; }
      stopTicker();
      setTimeout(connect, reconnectDelay);
      reconnectDelay = Math.min(reconnectDelay * 2, 30000);
    });
    ws.addEventListener("error", () => { try { ws.close(); } catch (e) {} });
  }

  connect();
  loadDstn();

  document.addEventListener("visibilitychange", () => {
    if (!document.hidden && latest) updateTimes();
  });
})();