Duelingbook Deck Cost Tracker (Userscript)

Displays custom point costs on cards in Duelingbook's deck editor and search results.

// ==UserScript==
// @name         Duelingbook Deck Cost Tracker (Userscript)
// @namespace    zayn-db-costs
// @version      1.5
// @description  Displays custom point costs on cards in Duelingbook's deck editor and search results.
// @match        https://www.duelingbook.com/*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(function () {
  'use strict';

  const MAX_COST = 100;
  const DEFAULT_COST = 0;

  const cardCosts = {
    "abyss dweller": 100,
    "adamancipator risen - dragite": 20,
    "agido the ancient sentinel": 50,
    "albion the sanctifire dragon": 33,
    "allure of darkness": 5,
    "amorphactor pain, the imagination dracoverlord": 100,
    "ancient gear advance": 33,
    "ancient gear statue": 33,
    "and the band played on": 100,
    "angel o7": 100,
    "anti-spell fragrance": 100,
    "appointer of the red lotus": 50,
    "arahime the manifested mikanko": 33,
    "arcana force xxi - the world": 100,
    "archlord kristya": 100,
    "archnemeses eschatos": 100,
    "archnemeses protos": 100,
    "artifact scythe": 100,
    "asceticism of the six samurai": 10,
    "ash blossom & joyous spring": 15,
    "assault synchron": 5,
    "astral kuriboh": 3,
    "atlantean dragoons": 40,
    "azamina ilia silvia": 20,
    "azamina mu rcielago": 33,
    "bahamut shark": 81,
    "baronne de fleur": 85,
    "barrier of the voiceless voice": 33,
    "barrier statue of the abyss": 60,
    "barrier statue of the drought": 60,
    "barrier statue of the heavens": 60,
    "barrier statue of the inferno": 60,
    "barrier statue of the stormwinds": 60,
    "barrier statue of the torrent": 60,
    "beatrice, lady of the eternal": 100,
    "big welcome labrynth": 33,
    "blackwing - boreastorm the wicked wind": 20,
    "blackwing - chinook the snow blast": 5,
    "blackwing - zephyros the elite": 15,
    "blaster, dragon ruler of infernos": 7,
    "blaze fenix, the burning bombardment bird": 70,
    "blazing cartesia, the virtuous": 3,
    "block dragon": 40,
    "bonfire": 33,
    "book of eclipse": 5,
    "book of moon": 7,
    "brain research lab": 100,
    "branded expulsion": 33,
    "branded fusion": 33,
    "branded lost": 66,
    "brilliant fusion": 33,
    "butterfly dagger - elma": 1,
    "bystial baldrake": 30,
    "bystial dis pater": 10,
    "bystial druiswurm": 30,
    "bystial magnamhut": 33,
    "bystial saronir": 20,
    "called by the grave": 20,
    "card destruction": 40,
    "card of demise": 40,
    "card of safe return": 40,
    "catapult turtle": 100,
    "centur-ion auxila": 33,
    "centur-ion primera": 5,
    "centur-ion trudea": 3,
    "chain strike": 50,
    "change of heart": 10,
    "chaofeng, phantom of the yang zing": 13,
    "chaos angel": 20,
    "chaos dragon levianeer": 10,
    "chaos ruler, the chaotic magical dragon": 50,
    "chaos space": 40,
    "charge of the light brigade": 33,
    "cold wave": 100,
    "confiscation": 100,
    'contact "c"': 100,
    "cornfield coatl": 33,
    "cosmic blazar dragon": 1,
    "creature swap": 1,
    "crimson dragon": 100,
    "crossout designator": 10,
    "crystron inclusion": 33,
    "crystron smiger": 5,
    "crystron sulfador": 3,
    "crystron thystvern": 5,
    "cyber angel benten": 40,
    "cyber dragon infinity": 20,
    "cyber jar": 33,
    "cyber-stein": 27,
    "d/d/d duo-dawn king kali yuga": 77,
    "d/d/d wave high king caesar": 20,
    "daigusto emeral": 1,
    "danger! bigfoot!": 3,
    "danger! chupacabra!": 3,
    "danger! dogman!": 3,
    "danger! mothman!": 3,
    "danger! nessie!": 7,
    "danger! ogopogo!": 3,
    "danger! thunderbird!": 3,
    "danger!? jackalope?": 7,
    "danger!? tsuchinoko?": 7,
    "dark end evaporation dragon": 5,
    "dark hole": 3,
    "dark world archives": 5,
    "deception of the sinful spoils": 33,
    "deck lockdown": 100,
    "deep sea aria": 33,
    "delinquent duo": 100,
    "denglong, first of the yang zing": 33,
    "denko sekka": 20,
    "destiny hero - destroyer phoenix enforcer": 20,
    "destiny hero - plasma": 20,
    "destructive daruma karma cannon": 5,
    "diabell, queen of the white forest": 25,
    "diabellstar the black witch": 20,
    "different dimension ground": 10,
    "dimension fusion": 45,
    "dimension shifter": 5,
    "dimensional barrier": 100,
    "dinomorphia intact": 1,
    "dinomorphia rexterm": 91,
    "dinowrestler pankratops": 10,
    "divine arsenal aa-zeus - sky thunder": 20,
    "diviner of the herald": 33,
    "djinn releaser of rituals": 100,
    "dogmatika ecclesia, the virtuous": 3,
    "domain of the true monarchs": 50,
    "dominus impulse": 30,
    "dominus purge": 10,
    "dracotail arthalion": 10,
    "dracotail faimena": 20,
    "dracotail mululu": 5,
    "dragon master magia": 100,
    "dragonic diagram": 33,
    "dragonmaid sheou": 20,
    "dragonmaid tidying": 10,
    "dragon's bind": 100,
    "dragon's light and darkness": 7,
    "droll & lock bird": 7,
    "drytron alpha thuban": 33,
    "drytron mu beta fafnir": 33,
    "duality": 10,
    "eclipse wyvern": 33,
    "effect veiler": 7,
    "el shaddoll apkallone": 10,
    "el shaddoll winda": 60,
    "elder entity norden": 91,
    "elder entity n'tss": 7,
    "elemental hero stratos": 3,
    "elzette, azamina of the white forest": 22,
    "emergency teleport": 40,
    "emergency!": 33,
    "eva": 1,
    "evenly matched": 10,
    "ext ryzeal": 25,
    "f.a. dawn dragster": 20,
    "fairy tail - snow": 85,
    "fiber jar": 30,
    "final countdown": 100,
    "fire formation - tenki": 40,
    "fire king courtier ulcanix": 20,
    "fire king high avatar kirin": 10,
    "fishborg blaster": 33,
    "floowandereeze & robina": 33,
    "floowandereeze and the advent of adventure": 33,
    "floowandereeze and the magnificent map": 33,
    "foolish burial": 33,
    "forbidden chalice": 5,
    "forbidden droplet": 10,
    "forbidden lance": 3,
    "fossil dig": 40,
    "fossil dyna pachycephalo": 100,
    "frightfur patchwork": 33,
    "fusion destiny": 33,
    "gallant granite": 33,
    "gateway of the six": 100,
    "gem-knight master diamond": 66,
    "ghost belle & haunted mansion": 5,
    "ghost meets girl - a masterful mayakashi shiranui saga": 100,
    "ghost mourner & moonlit chill": 7,
    "ghost ogre & snow rabbit": 5,
    "ghost sister & spooky dogwood": 3,
    "giant trunade": 40,
    "gigantic spright": 20,
    "gimmick puppet nightmare": 70,
    "give and take": 91,
    "gladiator beast gyzarus": 20,
    "gladiator beast tamer editor": 20,
    "gladiator proving ground": 33,
    "gladiator rejection": 20,
    "glow-up bulb": 21,
    "goblin biker grand entrance": 33,
    "gold sarcophagus": 10,
    "gozen match": 100,
    "graceful charity": 40,
    "grapha, dragon lord of dark world": 5,
    "grapha, dragon overlord of dark world": 5,
    "gruesome grave squirmer": 1,
    "guardian chimera": 33,
    "guiding quem, the virtuous": 3,
    "harpie's feather duster": 33,
    "harpie's feather storm": 100,
    "heart of the blue-eyes": 5,
    "heat wave": 100,
    "heavy storm": 25,
    "herald of the arc light": 50,
    "hot red dragon archfiend king calamity": 1,
    "hyper rank-up-magic utopiforce": 1,
    "ice ryzeal": 20,
    "ido the supreme magical force": 100,
    "imperial order": 100,
    "imsety, glory of horus": 33,
    "incoming machine!": 33,
    "incredible ecclesia, the virtuous": 3,
    "infernal flame banshee": 33,
    "infernity launcher": 88,
    "infinite impermanence": 15,
    "inspector boarder": 20,
    "instant fusion": 100,
    "interrupted kaiju slumber": 33,
    "into the void": 1,
    "invoked caliga": 90,
    "jet synchron": 1,
    "jowgen the spiritualist": 100,
    "junk speeder": 100,
    'k9-17 "ripper"': 20,
    "k9-17 izuna": 20,
    "k9-øø lupis": 5,
    "kaiser colosseum": 100,
    "kashtira arise-heart": 97,
    "kashtira fenrir": 30,
    "kashtira unicorn": 30,
    "kelbek the ancient vanguard": 50,
    "keldo the sacred protector": 1,
    "king of the feral imps": 33,
    "king's sarcophagus": 33,
    "knight armed dragon, the armored knight dragon": 3,
    "knightmare corruptor iblee": 100,
    "koa'ki meiru drago": 75,
    "koa'ki meiru guardian": 3,
    "koa'ki meiru overload": 3,
    "koa'ki meiru sandman": 3,
    "koa'ki meiru wall": 3,
    "lady labrynth of the silver castle": 33,
    "lady's dragonmaid": 20,
    "last turn": 75,
    "last will": 100,
    "lavalval chain": 80,
    "legendary fire king ponix": 10,
    "legendary lord six samurai - shi en": 10,
    "legendary six samurai - shi en": 10,
    "level eater": 100,
    "life equalizer": 100,
    "light and darkness dragonlord": 20,
    "light barrier": 1,
    "light end sublimation dragon": 5,
    "lightning storm": 40,
    "lonefire blossom": 33,
    "lose 1 turn": 100,
    "lubellion the searing dragon": 33,
    "lyrilusc - independent nightingale": 1,
    "magical explosion": 75,
    "magical mid-breaker field": 60,
    "magical scientist": 95,
    "magician of black chaos max": 100,
    "majesty's fiend": 100,
    "mansion of the dreadful dolls": 100,
    "masked hero dark law": 70,
    "mass driver": 100,
    "master peace, the true dracoslaying king": 33,
    'maxx "c"': 50,
    "meizen the battle ninja": 20,
    "mementomictlan tecuhtlica - creation king": 33,
    "mementotlan bone party": 33,
    "mementotlan twin dragon": 33,
    "metamorphosis": 10,
    "mikanko water arabesque": 10,
    "millennium ankh": 3,
    "mind drain": 100,
    "mind master": 1,
    "mirage of nightmare": 10,
    "mirrorjade the iceblade dragon": 33,
    "miscellaneousaurus": 75,
    "mistake": 100,
    "mitsurugi prayers": 60,
    "mitsurugi ritual": 60,
    "monster gate": 50,
    "monster reborn": 20,
    "morphing jar": 33,
    "morphtronic telefon": 55,
    "moulinglacia the elemental lord": 100,
    "mudora the sword oracle": 1,
    "mulcharmy fuwalos": 7,
    "mulcharmy meowls": 5,
    "mulcharmy purulia": 10,
    "m-x-saber invoker": 33,
    "mystic mine": 100,
    "nadir servant": 33,
    "naturia barkion": 10,
    "naturia beast": 50,
    "naturia exterio": 100,
    "necrovalley": 40,
    "neptabyss, the atlantean prince": 33,
    "nibiru, the primal being": 10,
    "nightmare apprentice": 33,
    "nightmare throne": 50,
    "number 1: infection buzzking": 85,
    "number 1: numeron gate ekam": 10,
    "number 100: numeron dragon": 1,
    "number 16: shock master": 100,
    "number 2: numeron gate dve": 10,
    "number 3: numeron gate trini": 10,
    "number 38: hope harbinger dragon titanic galaxy": 10,
    "number 4: numeron gate catvari": 10,
    "number 40: gimmick puppet of strings": 50,
    "number 41: bagooska the terribly tired tapir": 100,
    "number 43: manipulator of souls": 100,
    "number 59: crooked cook": 100,
    "number 75: bamboozling gossip shadow": 70,
    "number 86: heroic champion - rhongomyniad": 31,
    "number 89: diablosis the mind hacker": 85,
    "number 90: galaxy-eyes photon lord": 10,
    "number 95: galaxy-eyes dark matter dragon": 50,
    "number 97: draglubion": 100,
    "number c1: numeron chaos gate sunya": 10,
    "number c40: gimmick puppet of dark strings": 50,
    "number s0: utopic zexal": 100,
    "numeron calling": 30,
    "numeron network": 33,
    "obedience schooled": 40,
    "one day of peace": 11,
    "one for one": 91,
    "onomatopaira": 33,
    "original sinful spoils - snake-eye": 100,
    "outer entity azathot": 100,
    "painful choice": 95,
    "phantom knights' rank-up-magic force": 1,
    "phantom of yubel": 50,
    "pilgrim reaper": 50,
    "pot of desires": 20,
    "pot of greed": 30,
    "pot of prosperity": 40,
    "powersink stone": 100,
    "premature burial": 27,
    "preparation of rites": 5,
    "pre-preparation of rites": 10,
    "pressured planet wraitsoth": 33,
    "primeval planet perlereino": 50,
    "primite lordly lode": 33,
    "psychic end punisher": 20,
    "psy-framegear delta": 7,
    "psy-framegear epsilon": 7,
    "psy-framegear gamma": 15,
    "psy-framelord omega": 100,
    "purrely": 15,
    "purrely sleepy memory": 15,
    "purrelyly": 10,
    "quick launch": 33,
    "raidraptor - vanishing lanius": 5,
    "raigeki": 7,
    "rank-up-magic - the seventh one": 1,
    "rank-up-magic admiration of the thousands": 1,
    "rank-up-magic argent chaos force": 5,
    "rank-up-magic astral force": 1,
    "rank-up-magic barian's force": 1,
    "rank-up-magic cipher ascension": 1,
    "rank-up-magic doom double force": 1,
    "rank-up-magic limited barian's force": 1,
    "rank-up-magic magical force": 1,
    "rank-up-magic numeron force": 1,
    "rank-up-magic quick chaos": 1,
    "rank-up-magic raid force": 1,
    "rank-up-magic raptor's force": 1,
    "rank-up-magic revolution force": 1,
    "rank-up-magic skip force": 5,
    "rank-up-magic soul shave force": 5,
    "rank-up-magic zexal force": 1,
    "ra's disciple": 1,
    "reasoning": 50,
    "red reboot": 50,
    "red-eyes dark dragoon": 100,
    "redox, dragon ruler of boulders": 7,
    "regenesis": 33,
    "reinforcement of the army": 40,
    "rescue-ace air lifter": 10,
    "rescue-ace preventer": 10,
    "return from the different dimension": 50,
    "return of the dragon lords": 10,
    "rise rank-up-magic raidraptor's force": 1,
    "rite of aramesir": 5,
    "ritual beast tamer elder": 10,
    "rivalry of warlords": 100,
    "ronintoadin": 60,
    "royal decree": 10,
    "royal magical library": 100,
    "royal oppression": 100,
    "ryzeal detonator": 20,
    "ryzeal duo drive": 20,
    "sangen kaimen": 50,
    "sangen summoning": 100,
    "sauravis, the ancient and ascended": 5,
    "schwarzschild infinity dragon": 33,
    "secret village of the spellcasters": 100,
    "self-destruct button": 100,
    "sengenjin wakes from a millennium": 33,
    "set rotation": 33,
    "shaddoll schism": 10,
    "shien's dojo": 10,
    "shien's smoke signal": 33,
    "shooting riser dragon": 33,
    "sillva, warlord of dark world": 100,
    "sixth sense": 65,
    "skill drain": 100,
    "smoke grenade of the thief": 87,
    "snake-eye ash": 5,
    "snake-eyes poplar": 5,
    "snatch steal": 7,
    "snoww, unlight of dark world": 33,
    "solemn judgment": 7,
    "solemn strike": 5,
    "solemn warning": 5,
    "songs of the dominators": 10,
    "soul charge": 50,
    "soul drain": 100,
    "speedroid terrortop": 3,
    "spell canceller": 20,
    "spiritual beast tamer lara": 10,
    "spright starter": 20,
    "stand up centur-ion!": 5,
    "stardust sifr divine dragon": 1,
    "starliege seyfert": 33,
    "stray purrely street": 5,
    "substitoad": 60,
    "subterror guru": 5,
    "summon limit": 100,
    "super polymerization": 10,
    "super starslayer ty-phon - sky crisis": 10,
    "supreme king dragon starving venom": 1,
    "swap frog": 33,
    "sword ryzeal": 20,
    "swordsoul emergence": 33,
    "swordsoul grandmaster - chixiao": 33,
    "swordsoul of mo ye": 3,
    "swordsoul strategist longyuan": 5,
    "t.g. hyper librarian": 33,
    "tearlaments havnis": 50,
    "tearlaments kitkallos": 50,
    "tearlaments merrli": 50,
    "tearlaments reinoheart": 50,
    "tearlaments scheiren": 50,
    "telekinetic charging cell": 100,
    "tellarknight ptolemaeus": 100,
    "tempest, dragon ruler of storms": 7,
    "tenpai dragon chundra": 50,
    "tenpai dragon genroku": 25,
    "tenyi spirit - ashuna": 33,
    "terraforming": 33,
    "that grass looks greener": 50,
    "the black goat laughs": 10,
    "the bystial lubellion": 30,
    "the forceful sentry": 100,
    "the gates of dark world": 5,
    "the hidden city": 33,
    "the last warrior from another planet": 100,
    "the melody of awakening dragon": 33,
    "the monarchs erupt": 50,
    "the phantom knights' rank-up-magic launch": 1,
    "the tyrant neptune": 100,
    "the unstoppable exodia incarnate": 20,
    "the zombie vampire": 50,
    "there can be only one": 100,
    "thunder dragon colossus": 67,
    "thunder king rai-oh": 20,
    "tidal, dragon ruler of waterfalls": 7,
    "toadally awesome": 20,
    "tour guide from the underworld": 3,
    "trap dustshoot": 94,
    "traptrix rafflesia": 10,
    "triple tactics talent": 93,
    "triple tactics thrust": 13,
    "trishula, dragon of the ice barrier": 3,
    "true king of all calamities": 100,
    "tyrant's tirade": 100,
    "ultimaya tzolkin": 100,
    "union hangar": 33,
    "vanity's emptiness": 100,
    "vanity's fiend": 100,
    "vanity's ruler": 100,
    "varudras, the final bringer of the end times": 20,
    "virtual world kyubi - shenshen": 20,
    "virtual world mai-hime - lulu": 3,
    "wandering gryphon rider": 20,
    "wanted: seeker of sinful spoils": 33,
    "water enchantress of the temple": 5,
    "welcome labrynth": 33,
    "wind-up carrier zenmaity": 33,
    "wind-up hunter": 75,
    "wishes for eyes of blue": 33,
    "witch of the white forest": 33,
    "yaguramaru the armor ninja": 20,
    "zaborg the mega monarch": 80,
    "zoodiac barrage": 33,
    "zoodiac broadbull": 66,
    "zoodiac drident": 20,
    "zoodiac ratpier": 50
  };

  function ensureTotalDisplay() {
    let totalDisplay = document.getElementById('deck-cost-display');
    if (!totalDisplay) {
      totalDisplay = document.createElement('div');
      totalDisplay.id = 'deck-cost-display';
      totalDisplay.style.position = 'fixed';
      totalDisplay.style.top = '40px';
      totalDisplay.style.right = '10px';
      totalDisplay.style.zIndex = '9999';
      totalDisplay.style.fontSize = '18px';
      totalDisplay.style.fontWeight = 'bold';
      totalDisplay.style.padding = '6px 12px';
      totalDisplay.style.borderRadius = '6px';
      totalDisplay.style.background = 'rgba(0,0,0,0.7)';
      totalDisplay.style.color = 'white';
      document.body.appendChild(totalDisplay);
    }
    return totalDisplay;
  }

  function ensureToggleButton() {
    let btn = document.getElementById('deck-cost-toggle');
    if (!btn) {
      btn = document.createElement('button');
      btn.id = 'deck-cost-toggle';
      btn.textContent = 'Hide Costs';
      btn.style.position = 'fixed';
      btn.style.top = '10px';
      btn.style.right = '10px';
      btn.style.zIndex = '10000';
      btn.style.padding = '4px 10px';
      btn.style.borderRadius = '6px';
      btn.style.background = 'black';
      btn.style.color = 'white';
      btn.style.fontWeight = 'bold';
      btn.style.cursor = 'pointer';
      btn.addEventListener('click', () => {
        costsEnabled = !costsEnabled;
        btn.textContent = costsEnabled ? 'Hide Costs' : 'Show Costs';
        updateDeckCosts();
      });
      document.body.appendChild(btn);
    }
    return btn;
  }

  let costsEnabled = true;

  function getOrCreateLabel(cardEl, cardId) {
    let label = cardEl.querySelector('.cost-label');

    const pos = getComputedStyle(cardEl).position;
    if (pos === 'static') {
      cardEl.style.position = 'relative';
    }

    let bottom = '50px';
    let background = 'rgba(0,0,0,0)';
    let color = 'rgba(0.2,0.2,0.2,0)';

    if (cardId === 'search_card17' || cardId === 'search_card18' || cardId === 'search_card19') {
      bottom = '350px';
      background = 'rgba(0,0,0,0.3)';
      color = 'yellow';
    }

    if (!label) {
      label = document.createElement('div');
      label.className = 'cost-label';
      label.style.position = 'absolute';
      label.style.bottom = bottom;
      label.style.left = '100px';
      label.style.background = background;
      label.style.color = color;
      label.style.fontSize = '200px';
      label.style.padding = '2px 4px';
      label.style.borderRadius = '4px';
      label.style.textShadow = `
        -2px -2px 0 black,
         2px -2px 0 black,
        -2px  2px 0 black,
         2px  2px 0 black,
         0px  0px 4px black
      `;
      label.style.fontWeight = '9000';
      cardEl.appendChild(label);
    }
    return label;
  }

  function updateDeckCosts() {
    ensureToggleButton();

    const selectors = ['.deck_card', '.extra_card', '.side_card', '.search_card'];
    let total = 0;

    for (const selector of selectors) {
      const cards = document.querySelectorAll(selector);
      for (const cardEl of cards) {
        if (!cardEl || cardEl.style.display === 'none') continue;

        const rawName = cardEl.querySelector('.name_txt')?.textContent?.trim() ?? '';
        const cost = cardCosts[rawName.toLowerCase()] ?? DEFAULT_COST;

        if (selector !== '.search_card') {
          total += cost;
        }

        const label = getOrCreateLabel(cardEl, cardEl.id);
        label.textContent = (costsEnabled && cost > 0) ? String(cost) : '';

        const scale = cardEl.querySelector('.scale_left_txt');
        if (scale && scale.style && scale.style.display !== 'none') {
          label.textContent = costsEnabled ? 'X' : '';
        }

        const image = cardEl.querySelector('.card_color');
        if (image && image.src === 'https://images.duelingbook.com/card/link_front2.webp') {
          label.textContent = costsEnabled ? 'X' : '';
        }
      }
    }

    const display = ensureTotalDisplay();
    display.style.display = costsEnabled ? 'block' : 'none';
    display.textContent = `Deck Cost: ${total} / ${MAX_COST}`;
    display.style.color = total > MAX_COST ? 'red' : 'lightgreen';
  }

  let updateScheduled = false;
  const observer = new MutationObserver(() => {
    if (!updateScheduled) {
      updateScheduled = true;
      requestAnimationFrame(() => {
        updateDeckCosts();
        updateScheduled = false;
      });
    }
  });

  function start() {
    if (!document.body) {
      requestAnimationFrame(start);
      return;
    }
    observer.observe(document.body, { childList: true, subtree: true });
    updateDeckCosts();
  }

  start();
})();