/*!
 * Discord HypeSquad Switcher — by discord.dog
 * Runs entirely in your browser. No server, no tracking, no install.
 * Source: https://discord.dog/tools/hypesquad-switcher/src
 * Tool:   https://discord.dog/tools/hypesquad-switcher
 *
 * What it does:
 *   1. Reads your Discord auth token from the running Discord client
 *      (the same token your browser already uses on this page).
 *   2. Calls Discord's own HypeSquad endpoint (/api/v9/hypesquad/online)
 *      to switch your house — the exact API the Discord settings page uses.
 *   3. Shows the result. Nothing leaves your machine.
 */
(function () {
  "use strict";

  /* ----- idempotent re-open ----- */
  if (window.__discordDogHypesquad) {
    try { window.__discordDogHypesquad.toggle(); } catch (_) {}
    return;
  }

  /* ----- domain check ----- */
  var host = location.hostname;
  var ALLOWED = ["discord.com", "canary.discord.com", "ptb.discord.com"];
  if (ALLOWED.indexOf(host) === -1) {
    alert(
      "Discord HypeSquad Switcher only works on discord.com.\n\n" +
      "Open Discord in your browser (https://discord.com/channels/@me), " +
      "then click the bookmarklet from there."
    );
    return;
  }

  /* ----- constants ----- */
  var API = "/api/v9";
  var BADGE_CDN = "https://cdn.discordapp.com/badge-icons";

  // public_flags bit positions for each house
  var FLAG_BRAVERY    = 1 << 6;   //  64
  var FLAG_BRILLIANCE = 1 << 7;   // 128
  var FLAG_BALANCE    = 1 << 8;   // 256

  // Discord-published badge icon hashes (verified against the live CDN).
  var HOUSES = [
    {
      id: 1,
      key: "bravery",
      name: "Bravery",
      tagline: "Daring & spirited",
      color: "#9c84ef",
      gradient: "linear-gradient(135deg,#9c84ef 0%,#5865f2 100%)",
      flag: FLAG_BRAVERY,
      icon: BADGE_CDN + "/8a88d63823d8a71cd5e390baa45efa02.png",
    },
    {
      id: 2,
      key: "brilliance",
      name: "Brilliance",
      tagline: "Inquisitive & sharp",
      color: "#f47b67",
      gradient: "linear-gradient(135deg,#f47b67 0%,#ed4245 100%)",
      flag: FLAG_BRILLIANCE,
      icon: BADGE_CDN + "/011940fd013da3f7fb926e4a1cd2e618.png",
    },
    {
      id: 3,
      key: "balance",
      name: "Balance",
      tagline: "Calm & considerate",
      color: "#45ddc0",
      gradient: "linear-gradient(135deg,#45ddc0 0%,#23a55a 100%)",
      flag: FLAG_BALANCE,
      icon: BADGE_CDN + "/3aa41de486fa12454c3761e8e223442e.png",
    },
  ];

  /* ----- token grab ----- */
  // Discord's client-side bundle exposes a getToken() helper inside a webpack
  // module. We push a fake module so webpack hands us the __webpack_require__
  // function, then walk its loaded-module cache and look for getToken().
  // This technique has worked for years; if Discord ever obfuscates further
  // we degrade to manual paste.
  function grabToken() {
    try {
      var chunkName = "webpackChunkdiscord_app";
      var chunk = window[chunkName];
      if (!chunk || typeof chunk.push !== "function") return null;

      var req = null;
      var sentinel = "_dd_hs_" + Math.random().toString(36).slice(2);
      chunk.push([[sentinel], {}, function (r) { req = r; }]);

      if (!req || !req.c) return null;
      var cache = req.c;

      for (var k in cache) {
        if (!Object.prototype.hasOwnProperty.call(cache, k)) continue;
        var mod = cache[k];
        if (!mod || !mod.exports) continue;
        var ex = mod.exports;
        var candidates = [ex.default, ex.Z, ex.ZP, ex];
        for (var i = 0; i < candidates.length; i++) {
          var c = candidates[i];
          if (c && typeof c.getToken === "function") {
            try {
              var t = c.getToken();
              if (typeof t === "string" && t.length > 20) return t;
            } catch (_) {}
          }
        }
      }
    } catch (e) {
      console.warn("[hypesquad-switcher] token auto-grab failed:", e);
    }
    return null;
  }

  // Fallback A: Discord patches the main window's Storage.prototype.getItem
  // to hide the token. A same-origin iframe gets its own fresh Storage
  // prototype that Discord never touched — invoking the iframe's
  // Storage.prototype.getItem on the parent's localStorage bypasses the patch.
  // Token is stored JSON-encoded so strip surrounding quotes.
  function grabTokenFromIframe() {
    var iframe = null;
    try {
      iframe = document.createElement("iframe");
      iframe.style.display = "none";
      document.body.appendChild(iframe);
      var win = iframe.contentWindow;
      if (!win) return null;

      var raw = null;
      // Use the iframe's untouched Storage.prototype.getItem on parent storage
      if (win.Storage && win.Storage.prototype && typeof win.Storage.prototype.getItem === "function") {
        try { raw = win.Storage.prototype.getItem.call(window.localStorage, "token"); } catch (_) {}
      }
      // Also try the iframe's own localStorage (same underlying data store)
      if (typeof raw !== "string" && win.localStorage) {
        try { raw = win.localStorage.getItem("token") || win.localStorage.token; } catch (_) {}
      }

      if (typeof raw === "string") {
        if (raw.charAt(0) === '"' && raw.charAt(raw.length - 1) === '"') {
          raw = raw.slice(1, -1);
        }
        if (raw.length > 20) return raw;
      }
    } catch (e) {
      console.warn("[hypesquad-switcher] iframe token grab failed:", e);
    } finally {
      if (iframe && iframe.parentNode) iframe.parentNode.removeChild(iframe);
    }
    return null;
  }

  // Fallback B: hook fetch + XMLHttpRequest to read the Authorization header
  // off the next Discord API call. Discord makes API calls constantly
  // (presence pings, read state, etc.) so this almost always catches one
  // within a couple of seconds.
  function grabTokenFromNetwork(timeoutMs) {
    return new Promise(function (resolve) {
      var done = false;
      var origFetch = window.fetch;
      var origXhrSet = XMLHttpRequest.prototype.setRequestHeader;
      var timer = setTimeout(function () { finish(null); }, timeoutMs || 8000);

      function finish(token) {
        if (done) return;
        done = true;
        try { window.fetch = origFetch; } catch (_) {}
        try { XMLHttpRequest.prototype.setRequestHeader = origXhrSet; } catch (_) {}
        clearTimeout(timer);
        resolve(token);
      }

      function consider(val) {
        if (done || typeof val !== "string") return;
        // User tokens have no "Bot " / "Bearer " prefix; reject anything that does
        if (/^(?:Bot|Bearer)\s+/i.test(val)) return;
        if (val.length < 30 || val.length > 200) return;
        if (!/^[A-Za-z0-9_\-\.]+$/.test(val)) return;
        finish(val);
      }

      function readHeader(headers) {
        if (!headers) return null;
        try {
          if (typeof headers.get === "function") {
            return headers.get("Authorization") || headers.get("authorization");
          }
          return headers.Authorization || headers.authorization || null;
        } catch (_) { return null; }
      }

      window.fetch = function (input, init) {
        try {
          if (init) consider(readHeader(init.headers));
          if (!done && input && typeof input === "object") consider(readHeader(input.headers));
        } catch (_) {}
        return origFetch.apply(this, arguments);
      };

      XMLHttpRequest.prototype.setRequestHeader = function (name, value) {
        try {
          if (typeof name === "string" && name.toLowerCase() === "authorization") {
            consider(value);
          }
        } catch (_) {}
        return origXhrSet.apply(this, arguments);
      };
    });
  }

  /* ----- Discord API helpers ----- */
  function api(method, path, token, body) {
    var opts = {
      method: method,
      credentials: "include",
      headers: {
        "Authorization": token,
        "Content-Type": "application/json",
      },
    };
    if (body !== undefined) opts.body = JSON.stringify(body);
    return fetch(API + path, opts);
  }

  function fetchMe(token) {
    return api("GET", "/users/@me", token).then(function (r) {
      if (!r.ok) throw new Error("Discord returned " + r.status + " for /users/@me");
      return r.json();
    });
  }

  function setHouse(token, houseId) {
    return api("POST", "/hypesquad/online", token, { house_id: houseId })
      .then(function (r) {
        // 204 = success; 4xx = error with body
        if (r.status === 204) return { ok: true };
        return r.text().then(function (t) {
          return { ok: false, status: r.status, body: t };
        });
      });
  }

  function clearHouse(token) {
    return api("DELETE", "/hypesquad/online", token).then(function (r) {
      if (r.status === 204) return { ok: true };
      return r.text().then(function (t) {
        return { ok: false, status: r.status, body: t };
      });
    });
  }

  function currentHouseFromFlags(flags) {
    flags = flags || 0;
    if (flags & FLAG_BRAVERY)    return 1;
    if (flags & FLAG_BRILLIANCE) return 2;
    if (flags & FLAG_BALANCE)    return 3;
    return 0; // no house
  }

  /* ----- shadow-DOM UI ----- */
  function buildUI() {
    var hostEl = document.createElement("div");
    hostEl.id = "discord-dog-hypesquad-switcher";
    hostEl.style.cssText = [
      "all: initial",
      "position: fixed",
      "top: 80px",
      "right: 32px",
      "z-index: 2147483647",
      "width: 0",
      "height: 0",
    ].join(";");
    document.body.appendChild(hostEl);

    var root = hostEl.attachShadow({ mode: "closed" });

    var css = `
      :host { all: initial; }
      * { box-sizing: border-box; font-family: "gg sans","Helvetica Neue",Helvetica,Arial,sans-serif; }
      .panel {
        position: fixed;
        top: 80px;
        right: 32px;
        width: 340px;
        background: #232428;
        color: #f2f3f5;
        border: 1px solid #1e1f22;
        border-radius: 12px;
        box-shadow: 0 16px 48px rgba(0,0,0,.6), 0 0 0 1px rgba(255,255,255,.04) inset;
        font-size: 14px;
        overflow: hidden;
        user-select: none;
      }
      .header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        padding: 12px 14px;
        background: linear-gradient(180deg,#2b2d31 0%,#232428 100%);
        cursor: move;
        border-bottom: 1px solid #1e1f22;
      }
      .brand { display: flex; align-items: center; gap: 8px; }
      .brand-dot {
        width: 8px; height: 8px; border-radius: 50%;
        background: linear-gradient(135deg,#5865f2,#9c84ef);
      }
      .brand-text { font-weight: 600; font-size: 13px; letter-spacing: .2px; }
      .brand-sub { color: #b5bac1; font-size: 11px; margin-left: 4px; }
      .close {
        width: 26px; height: 26px;
        display: grid; place-items: center;
        border-radius: 6px;
        background: transparent;
        color: #b5bac1;
        border: none;
        cursor: pointer;
        transition: background 120ms, color 120ms;
      }
      .close:hover { background: #35373c; color: #f2f3f5; }
      .body { padding: 12px 14px 14px; }
      .user {
        display: flex; align-items: center; gap: 10px;
        padding: 8px 10px;
        background: #1e1f22;
        border-radius: 8px;
        margin-bottom: 12px;
      }
      .avatar {
        width: 32px; height: 32px;
        border-radius: 50%;
        background: #2b2d31;
        flex-shrink: 0;
      }
      .user-meta { min-width: 0; flex: 1; }
      .user-name {
        font-size: 13px; font-weight: 600;
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
      }
      .user-sub { font-size: 11px; color: #b5bac1; }
      .houses {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 8px;
      }
      .house {
        position: relative;
        padding: 12px 10px;
        background: #1e1f22;
        border: 1.5px solid transparent;
        border-radius: 8px;
        cursor: pointer;
        text-align: center;
        transition: transform 120ms, border-color 120ms, background 120ms;
      }
      .house:hover { background: #2b2d31; transform: translateY(-1px); }
      .house.active { border-color: var(--hc); background: #2b2d31; }
      .house.disabled { opacity: .6; cursor: progress; }
      .house img { width: 36px; height: 36px; display: block; margin: 0 auto 6px; }
      .house-name { font-weight: 600; font-size: 12px; }
      .house-sub { font-size: 10px; color: #b5bac1; margin-top: 2px; }
      .house .badge-check {
        position: absolute;
        top: 6px; right: 6px;
        width: 16px; height: 16px;
        background: var(--hc);
        color: #fff;
        border-radius: 50%;
        font-size: 10px;
        display: grid; place-items: center;
      }
      .remove {
        margin-top: 10px;
        width: 100%;
        padding: 9px 10px;
        background: transparent;
        color: #b5bac1;
        border: 1px solid #3f4147;
        border-radius: 8px;
        cursor: pointer;
        font-size: 12px;
        font-weight: 600;
        transition: background 120ms, color 120ms;
      }
      .remove:hover { background: #ed4245; color: #fff; border-color: #ed4245; }
      .remove.active { border-color: #ed4245; color: #ed4245; }
      .remove:disabled { opacity: .6; cursor: progress; }

      .token-row {
        margin-top: 14px;
        padding-top: 12px;
        border-top: 1px solid #1e1f22;
      }
      .token-label {
        font-size: 10px; text-transform: uppercase; letter-spacing: .6px;
        color: #b5bac1; margin-bottom: 6px;
      }
      .token-box {
        display: flex; align-items: center; gap: 6px;
        padding: 6px 8px;
        background: #1e1f22;
        border-radius: 6px;
        font-family: ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
        font-size: 11px;
      }
      .token-value {
        flex: 1; min-width: 0;
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
        color: #f2f3f5;
      }
      .token-value.blurred { filter: blur(5px); user-select: none; }
      .icon-btn {
        width: 24px; height: 24px;
        display: grid; place-items: center;
        border: none;
        background: transparent;
        color: #b5bac1;
        border-radius: 4px;
        cursor: pointer;
        flex-shrink: 0;
      }
      .icon-btn:hover { background: #35373c; color: #f2f3f5; }
      .footer {
        margin-top: 12px;
        font-size: 10px;
        color: #6d6f78;
        text-align: center;
        line-height: 1.6;
      }
      .footer a { color: #5865f2; text-decoration: none; }
      .footer a:hover { text-decoration: underline; }

      .status {
        margin-top: 10px;
        padding: 8px 10px;
        border-radius: 6px;
        font-size: 12px;
        display: none;
      }
      .status.show { display: block; }
      .status.ok { background: rgba(35,165,90,.15); color: #57f287; }
      .status.err { background: rgba(237,66,69,.15); color: #fa6669; }

      .paste {
        padding: 10px;
        background: #1e1f22;
        border-radius: 8px;
        margin-bottom: 12px;
      }
      .paste-msg { font-size: 12px; color: #b5bac1; margin-bottom: 8px; line-height: 1.4; }
      .paste-msg code {
        display: block;
        margin-top: 6px;
        padding: 6px 8px;
        background: #111214;
        border: 1px solid #1e1f22;
        border-radius: 4px;
        font-family: ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
        font-size: 9px;
        line-height: 1.35;
        word-break: break-all;
        color: #d4d7dc;
      }
      .bookmarklet {
        display: inline-block;
        padding: 1px 6px;
        background: #5865f2;
        color: #fff !important;
        border-radius: 4px;
        font-weight: 600;
        text-decoration: none !important;
        cursor: grab;
      }
      .bookmarklet:active { cursor: grabbing; }

      .detecting {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        padding: 24px 16px;
        text-align: center;
      }
      .detecting .spinner { margin-bottom: 12px; }
      .detecting-msg { font-size: 13px; font-weight: 600; color: #f2f3f5; margin-bottom: 4px; }
      .detecting-sub { font-size: 11px; color: #b5bac1; line-height: 1.4; }
      .paste input {
        width: 100%;
        padding: 8px;
        background: #111214;
        color: #f2f3f5;
        border: 1px solid #3f4147;
        border-radius: 6px;
        font-family: ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
        font-size: 11px;
        outline: none;
      }
      .paste input:focus { border-color: #5865f2; }
      .paste button {
        margin-top: 8px;
        width: 100%;
        padding: 8px;
        background: #5865f2;
        color: #fff;
        border: none;
        border-radius: 6px;
        font-size: 12px;
        font-weight: 600;
        cursor: pointer;
      }
      .paste button:hover { background: #4752c4; }

      .spinner {
        width: 18px; height: 18px;
        border: 2px solid #3f4147;
        border-top-color: #5865f2;
        border-radius: 50%;
        animation: spin 700ms linear infinite;
      }
      @keyframes spin { to { transform: rotate(360deg); } }
      .loading {
        display: flex; align-items: center; gap: 10px;
        padding: 16px;
        color: #b5bac1;
        font-size: 13px;
      }
    `;

    var style = document.createElement("style");
    style.textContent = css;
    root.appendChild(style);

    var panel = document.createElement("div");
    panel.className = "panel";
    panel.innerHTML =
      '<div class="header" data-role="drag">' +
        '<div class="brand">' +
          '<span class="brand-dot"></span>' +
          '<span class="brand-text">HypeSquad Switcher</span>' +
          '<span class="brand-sub">discord.dog</span>' +
        '</div>' +
        '<button class="close" title="Close" data-role="close" aria-label="Close">' +
          svgX() +
        '</button>' +
      '</div>' +
      '<div class="body" data-role="body">' +
        '<div class="loading"><div class="spinner"></div><span>Reading your Discord session…</span></div>' +
      '</div>';
    root.appendChild(panel);

    enableDrag(panel, panel.querySelector('[data-role="drag"]'));

    return { hostEl: hostEl, root: root, panel: panel };
  }

  function svgX() {
    return '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M6 6l12 12M18 6l-12 12"/></svg>';
  }
  function svgEye(open) {
    if (open) {
      return '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>';
    }
    return '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.94 10.94 0 0 1 12 20c-7 0-11-8-11-8a19.7 19.7 0 0 1 4.22-5.15M9.9 4.24A10.94 10.94 0 0 1 12 4c7 0 11 8 11 8a19.55 19.55 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>';
  }
  function svgCopy() {
    return '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
  }
  function svgCheck() {
    return '<svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg>';
  }

  function enableDrag(panel, handle) {
    var dx = 0, dy = 0, sx = 0, sy = 0, dragging = false;
    handle.addEventListener("mousedown", function (e) {
      if (e.target && e.target.closest && e.target.closest('[data-role="close"]')) return;
      dragging = true;
      var rect = panel.getBoundingClientRect();
      sx = e.clientX; sy = e.clientY;
      dx = rect.left; dy = rect.top;
      panel.style.right = "auto";
      panel.style.left = dx + "px";
      panel.style.top = dy + "px";
      e.preventDefault();
    });
    document.addEventListener("mousemove", function (e) {
      if (!dragging) return;
      var nx = dx + (e.clientX - sx);
      var ny = dy + (e.clientY - sy);
      var maxX = window.innerWidth  - panel.offsetWidth  - 4;
      var maxY = window.innerHeight - panel.offsetHeight - 4;
      panel.style.left = Math.max(4, Math.min(nx, maxX)) + "px";
      panel.style.top  = Math.max(4, Math.min(ny, maxY)) + "px";
    });
    document.addEventListener("mouseup", function () { dragging = false; });
  }

  /* ----- main flow ----- */
  var ui = buildUI();
  var state = { token: null, me: null, currentHouseId: 0 };

  ui.root.querySelector('[data-role="close"]').addEventListener("click", destroy);

  function destroy() {
    try { ui.hostEl.remove(); } catch (_) {}
    delete window.__discordDogHypesquad;
  }

  function toggle() {
    if (!ui.hostEl.isConnected) document.body.appendChild(ui.hostEl);
  }
  window.__discordDogHypesquad = { toggle: toggle, destroy: destroy };

  function setBody(html) {
    ui.root.querySelector('[data-role="body"]').innerHTML = html;
  }

  function escapeHtml(s) {
    return String(s).replace(/[&<>"']/g, function (c) {
      return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c];
    });
  }

  function avatarURL(me) {
    if (!me) return "";
    if (me.avatar) {
      var ext = me.avatar.indexOf("a_") === 0 ? "gif" : "png";
      return "https://cdn.discordapp.com/avatars/" + me.id + "/" + me.avatar + "." + ext + "?size=64";
    }
    // default avatar
    var idx = me.discriminator && me.discriminator !== "0"
      ? Number(me.discriminator) % 5
      : Number((BigInt(me.id) >> 22n) % 6n);
    return "https://cdn.discordapp.com/embed/avatars/" + idx + ".png";
  }

  function renderMain() {
    var me = state.me;
    var cur = state.currentHouseId;

    var houseCards = HOUSES.map(function (h) {
      var active = (cur === h.id);
      var check = active ? '<span class="badge-check">' + svgCheck() + '</span>' : "";
      return (
        '<div class="house' + (active ? ' active' : '') + '" data-house="' + h.id + '" style="--hc:' + h.color + '">' +
          check +
          '<img alt="' + escapeHtml(h.name) + '" src="' + h.icon + '" loading="lazy" />' +
          '<div class="house-name">' + escapeHtml(h.name) + '</div>' +
          '<div class="house-sub">' + escapeHtml(h.tagline) + '</div>' +
        '</div>'
      );
    }).join("");

    var displayName = me.global_name || me.username || "Discord user";
    var sub = me.username ? '@' + escapeHtml(me.username) : escapeHtml(me.id);

    setBody(
      '<div class="user">' +
        '<img class="avatar" alt="" src="' + avatarURL(me) + '"/>' +
        '<div class="user-meta">' +
          '<div class="user-name">' + escapeHtml(displayName) + '</div>' +
          '<div class="user-sub">' + sub + '</div>' +
        '</div>' +
      '</div>' +
      '<div class="houses">' + houseCards + '</div>' +
      '<button class="remove' + (cur === 0 ? ' active' : '') + '" data-house="0">' +
        (cur === 0 ? 'No house (current)' : 'Remove HypeSquad badge') +
      '</button>' +
      '<div class="status" data-role="status"></div>' +
      '<div class="token-row">' +
        '<div class="token-label">Your token</div>' +
        '<div class="token-box">' +
          '<span class="token-value blurred" data-role="token">' + escapeHtml(state.token) + '</span>' +
          '<button class="icon-btn" data-role="eye" title="Show / hide" aria-label="Show or hide token">' + svgEye(false) + '</button>' +
          '<button class="icon-btn" data-role="copy" title="Copy" aria-label="Copy token">' + svgCopy() + '</button>' +
        '</div>' +
      '</div>' +
      '<div class="footer">' +
        'Built by <a href="https://discord.dog" target="_blank" rel="noopener">Discord.dog</a> &middot; ' +
        'Runs in browser &middot; ' +
        '<a href="https://discord.dog/tools/hypesquad-switcher/src" target="_blank" rel="noopener">Source code</a>' +
      '</div>'
    );

    wireMainEvents();
  }

  function wireMainEvents() {
    var root = ui.root;
    var houses = root.querySelectorAll(".house, .remove");
    houses.forEach(function (el) {
      el.addEventListener("click", function () {
        var id = Number(el.getAttribute("data-house"));
        switchHouse(id, el);
      });
    });

    var tokenEl = root.querySelector('[data-role="token"]');
    var eyeBtn  = root.querySelector('[data-role="eye"]');
    var copyBtn = root.querySelector('[data-role="copy"]');

    var revealed = false;
    eyeBtn.addEventListener("click", function () {
      revealed = !revealed;
      tokenEl.classList.toggle("blurred", !revealed);
      eyeBtn.innerHTML = svgEye(revealed);
    });

    copyBtn.addEventListener("click", function () {
      navigator.clipboard.writeText(state.token).then(function () {
        copyBtn.style.color = "#57f287";
        setTimeout(function () { copyBtn.style.color = ""; }, 1200);
      }).catch(function () {
        // fallback selection
        var range = document.createRange();
        range.selectNode(tokenEl);
        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
      });
    });
  }

  function showStatus(msg, kind) {
    var s = ui.root.querySelector('[data-role="status"]');
    if (!s) return;
    s.className = "status show " + (kind || "ok");
    s.textContent = msg;
    clearTimeout(showStatus._t);
    showStatus._t = setTimeout(function () {
      s.className = "status";
    }, 4000);
  }

  function switchHouse(houseId, btn) {
    if (state.currentHouseId === houseId) return;
    var nodes = ui.root.querySelectorAll(".house, .remove");
    nodes.forEach(function (n) { n.classList.add("disabled"); });
    if (btn) btn.classList.add("disabled");

    var promise = houseId === 0 ? clearHouse(state.token) : setHouse(state.token, houseId);
    promise.then(function (r) {
      if (r.ok) {
        state.currentHouseId = houseId;
        renderMain();
        var name = houseId === 0 ? "No house" : HOUSES[houseId - 1].name;
        showStatus("Switched to " + name + ".", "ok");
      } else {
        showStatus("Discord rejected the change (HTTP " + r.status + "). " + (r.body || ""), "err");
        nodes.forEach(function (n) { n.classList.remove("disabled"); });
      }
    }).catch(function (e) {
      showStatus("Request failed: " + (e && e.message ? e.message : e), "err");
      nodes.forEach(function (n) { n.classList.remove("disabled"); });
    });
  }

  function renderPaste(reason) {
    setBody(
      '<div class="paste">' +
        '<div class="paste-msg">' + escapeHtml(reason || "Couldn't read your token automatically.") +
          ' Paste it manually below. It never leaves your browser.</div>' +
        '<input type="password" placeholder="Paste your Discord token" data-role="paste-input" autocomplete="off" />' +
        '<button data-role="paste-btn">Continue</button>' +
        '<div class="paste-msg" style="margin-top:8px;font-size:10px;">' +
          'To find your token, drag ' +
          '<a class="bookmarklet" href="javascript:(function()%7Blocation.reload()%3Bvar%20i%20%3D%20document.createElement(&#39;iframe&#39;)%3Bdocument.body.appendChild(i)%3Balert(i.contentWindow.localStorage.token)%7D)()">Grab&nbsp;Token</a>' +
          ' to your bookmarks bar, then click it on discord.com to get your token in a popup. Or paste this in DevTools (F12) Console:' +
          '<code>(function(){location.reload();var i=document.createElement("iframe");document.body.appendChild(i);alert(i.contentWindow.localStorage.token)})()</code>' +
        '</div>' +
      '</div>' +
      '<div class="footer">' +
        'Built by <a href="https://discord.dog" target="_blank" rel="noopener">Discord.dog</a> &middot; ' +
        'Runs in browser &middot; ' +
        '<a href="https://discord.dog/tools/hypesquad-switcher/src" target="_blank" rel="noopener">Source code</a>' +
      '</div>'
    );
    var input = ui.root.querySelector('[data-role="paste-input"]');
    var btn = ui.root.querySelector('[data-role="paste-btn"]');
    function submit() {
      var v = input.value.trim();
      if (!v) return;
      bootstrap(v);
    }
    btn.addEventListener("click", submit);
    input.addEventListener("keydown", function (e) {
      if (e.key === "Enter") submit();
    });
    input.focus();
  }

  function renderDetecting() {
    setBody(
      '<div class="detecting">' +
        '<div class="spinner"></div>' +
        '<div class="detecting-msg">Detecting your token&hellip;</div>' +
        '<div class="detecting-sub">Keep using Discord normally. This usually takes a second.</div>' +
      '</div>'
    );
  }

  function useToken(token) {
    state.token = token;
    fetchMe(token).then(function (me) {
      state.me = me;
      state.currentHouseId = currentHouseFromFlags(me.public_flags);
      renderMain();
    }).catch(function (e) {
      state.token = null;
      renderPaste("That token didn't work: " + (e && e.message ? e.message : e));
    });
  }

  function bootstrap(tokenOverride) {
    var token = tokenOverride || grabToken() || grabTokenFromIframe();
    if (token) { useToken(token); return; }

    // Hook the network and wait for Discord's next API call
    renderDetecting();
    grabTokenFromNetwork(8000).then(function (t) {
      if (t) {
        useToken(t);
      } else {
        renderPaste("Couldn't read your token automatically (Discord may have updated their bundle).");
      }
    });
  }

  bootstrap();
})();