// ==UserScript==
// @name Neopets Petpet Lab - Show All Petpets + Click to Zap
// @namespace https://www.neopets.com/
// @version 1.0.0
// @description Shows all Petpet Lab petpets at once and lets you click a petpet card to select and zap it.
// @author You
// @match https://www.neopets.com/labray/petpet.phtml*
// @match https://www.neopets.com/petpetlab.phtml*
// @match https://www.neopets.com/*petpet*lab*
// @grant none
// @downloadURL https://www.scriptneo.com/scripts/download.php?id=29
// @updateURL https://www.scriptneo.com/scripts/download.php?id=29
// ==/UserScript==
(function () {
"use strict";
const CONFIG = {
requireConfirmBeforeZap: true,
hideOriginalSelect: false,
autoScrollToGrid: false
};
function ready(fn) {
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", fn);
return;
}
fn();
}
function injectCss() {
const css = `
#np-ppl-helper {
max-width: 920px;
margin: 18px auto;
padding: 14px;
border: 2px solid #2d2418;
border-radius: 12px;
background: #fff8dc;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.18);
font-family: Arial, Helvetica, sans-serif;
color: #2d2418;
}
#np-ppl-helper h2 {
margin: 0 0 6px 0;
font-size: 20px;
line-height: 1.25;
color: #2d2418;
}
#np-ppl-helper .np-ppl-subtitle {
margin: 0 0 12px 0;
font-size: 13px;
color: #5b4a31;
}
#np-ppl-helper .np-ppl-status {
margin: 0 0 12px 0;
padding: 9px 10px;
border-radius: 8px;
background: #f6e8ad;
border: 1px solid #d7bd63;
font-size: 13px;
font-weight: bold;
}
#np-ppl-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
gap: 12px;
}
.np-ppl-card {
border: 2px solid #c9a646;
border-radius: 12px;
background: #fffdf2;
padding: 10px;
text-align: center;
cursor: pointer;
transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
min-height: 155px;
}
.np-ppl-card:hover {
transform: translateY(-2px);
border-color: #9b7424;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.22);
}
.np-ppl-card.np-ppl-selected {
border-color: #4b8f29;
background: #f0ffe9;
box-shadow: 0 0 0 3px rgba(75, 143, 41, 0.18);
}
.np-ppl-card img {
display: block;
max-width: 80px;
max-height: 80px;
width: auto;
height: auto;
margin: 0 auto 8px auto;
image-rendering: auto;
}
.np-ppl-name {
font-size: 14px;
font-weight: bold;
color: #2d2418;
margin-bottom: 4px;
word-break: break-word;
}
.np-ppl-pet {
font-size: 11px;
color: #775f3a;
margin-bottom: 6px;
word-break: break-word;
}
.np-ppl-sound {
font-size: 12px;
color: #4b3d28;
font-style: italic;
min-height: 16px;
}
.np-ppl-zap-label {
margin-top: 8px;
display: inline-block;
padding: 5px 8px;
border-radius: 999px;
background: #ffdf57;
border: 1px solid #c19922;
color: #2d2418;
font-size: 11px;
font-weight: bold;
}
.np-ppl-error {
max-width: 720px;
margin: 18px auto;
padding: 12px;
background: #ffe5e5;
border: 1px solid #b94444;
color: #631f1f;
border-radius: 8px;
font-weight: bold;
}
.np-ppl-original-visible .ppl-petpet {
display: inline-block !important;
vertical-align: top;
margin: 8px;
}
.np-ppl-original-visible .ppl-petpet.ppl-hidden {
display: inline-block !important;
}
`;
const style = document.createElement("style");
style.textContent = css;
document.head.appendChild(style);
}
function getLabContent() {
return document.querySelector("#PetPetLabContent") || document.body;
}
function getPetpetSelect() {
const selects = Array.from(document.querySelectorAll("select"));
return selects.find(function (select) {
return Array.from(select.options).some(function (option) {
return option.value && document.getElementById("PPL" + option.value);
});
}) || null;
}
function getZapButton() {
return document.querySelector("#PPLButton") || Array.from(document.querySelectorAll("button")).find(function (button) {
return /zap/i.test(button.textContent || "");
}) || null;
}
function normalizeText(value) {
return String(value || "").replace(/\s+/g, " ").trim();
}
function getPetNameFromId(petId, select) {
if (!select) {
return petId;
}
const option = Array.from(select.options).find(function (opt) {
return opt.value === petId;
});
return option ? normalizeText(option.textContent) : petId;
}
function getPetpetData(select) {
const petpetDivs = Array.from(document.querySelectorAll(".ppl-petpet[id^='PPL']"));
return petpetDivs.map(function (div) {
const petId = div.id.replace(/^PPL/, "");
const img = div.querySelector("img");
const sound = div.querySelector("p");
return {
id: petId,
petName: getPetNameFromId(petId, select),
petpetName: img ? normalizeText(img.getAttribute("alt")) : "Unknown Petpet",
imgSrc: img ? img.src : "",
sound: sound ? normalizeText(sound.textContent) : "",
originalNode: div
};
}).filter(function (petpet) {
return petpet.id && petpet.originalNode;
});
}
function dispatchNativeChange(element) {
element.dispatchEvent(new Event("change", {
bubbles: true,
cancelable: true
}));
}
function selectPetpetById(petId, select, statusBox) {
if (!select) {
setStatus(statusBox, "Could not find the Petpet dropdown.", true);
return false;
}
const option = Array.from(select.options).find(function (opt) {
return opt.value === petId;
});
if (!option) {
setStatus(statusBox, "Could not find this Petpet in the dropdown: " + petId, true);
return false;
}
select.value = petId;
dispatchNativeChange(select);
if (typeof window.selectPetpet === "function") {
try {
window.selectPetpet(select);
} catch (error) {
console.warn("Petpet Lab Helper: selectPetpet failed, continuing anyway.", error);
}
}
return true;
}
function clickZapButton(button, statusBox) {
if (!button) {
setStatus(statusBox, "Could not find the zap button.", true);
return false;
}
if (button.disabled) {
button.disabled = false;
}
button.click();
return true;
}
function setStatus(statusBox, message, isError) {
if (!statusBox) {
return;
}
statusBox.textContent = message;
statusBox.style.background = isError ? "#ffe5e5" : "#f6e8ad";
statusBox.style.borderColor = isError ? "#b94444" : "#d7bd63";
statusBox.style.color = isError ? "#631f1f" : "#2d2418";
}
function buildHelper(petpets, select, zapButton) {
const labContent = getLabContent();
const old = document.querySelector("#np-ppl-helper");
if (old) {
old.remove();
}
const helper = document.createElement("div");
helper.id = "np-ppl-helper";
const title = document.createElement("h2");
title.textContent = "Petpet Lab Quick Zap";
const subtitle = document.createElement("p");
subtitle.className = "np-ppl-subtitle";
subtitle.textContent = "All petpets are shown below. Click one to select it and zap it.";
const statusBox = document.createElement("div");
statusBox.className = "np-ppl-status";
statusBox.textContent = "Found " + petpets.length + " petpet" + (petpets.length === 1 ? "" : "s") + ".";
const grid = document.createElement("div");
grid.id = "np-ppl-grid";
petpets.forEach(function (petpet) {
const card = document.createElement("button");
card.type = "button";
card.className = "np-ppl-card";
card.dataset.petpetId = petpet.id;
card.title = "Click to zap " + petpet.petpetName + " attached to " + petpet.petName;
const img = document.createElement("img");
img.src = petpet.imgSrc;
img.alt = petpet.petpetName;
const petpetName = document.createElement("div");
petpetName.className = "np-ppl-name";
petpetName.textContent = petpet.petpetName;
const petName = document.createElement("div");
petName.className = "np-ppl-pet";
petName.textContent = "Pet: " + petpet.petName;
const sound = document.createElement("div");
sound.className = "np-ppl-sound";
sound.textContent = petpet.sound;
const zapLabel = document.createElement("div");
zapLabel.className = "np-ppl-zap-label";
zapLabel.textContent = "Click to Zap";
card.appendChild(img);
card.appendChild(petpetName);
card.appendChild(petName);
card.appendChild(sound);
card.appendChild(zapLabel);
card.addEventListener("click", function () {
Array.from(grid.querySelectorAll(".np-ppl-card")).forEach(function (otherCard) {
otherCard.classList.remove("np-ppl-selected");
});
card.classList.add("np-ppl-selected");
const selected = selectPetpetById(petpet.id, select, statusBox);
if (!selected) {
return;
}
setStatus(statusBox, "Selected " + petpet.petpetName + " attached to " + petpet.petName + ".", false);
if (CONFIG.requireConfirmBeforeZap) {
const ok = window.confirm(
"Zap this Petpet?\n\n" +
"Pet: " + petpet.petName + "\n" +
"Petpet: " + petpet.petpetName + "\n\n" +
"This will use your Petpet Lab zap."
);
if (!ok) {
setStatus(statusBox, "Cancelled. " + petpet.petpetName + " is selected but was not zapped.", false);
return;
}
}
setStatus(statusBox, "Zapping " + petpet.petpetName + " attached to " + petpet.petName + "...", false);
clickZapButton(zapButton, statusBox);
});
grid.appendChild(card);
});
helper.appendChild(title);
helper.appendChild(subtitle);
helper.appendChild(statusBox);
helper.appendChild(grid);
const zapsText = document.querySelector(".ppl-zaps");
if (zapsText && zapsText.parentNode) {
zapsText.insertAdjacentElement("afterend", helper);
} else {
labContent.insertBefore(helper, labContent.firstChild);
}
if (CONFIG.autoScrollToGrid) {
helper.scrollIntoView({
behavior: "smooth",
block: "start"
});
}
}
function showOriginalPetpets() {
document.documentElement.classList.add("np-ppl-original-visible");
Array.from(document.querySelectorAll(".ppl-petpet.ppl-hidden")).forEach(function (petpet) {
petpet.classList.remove("ppl-hidden");
petpet.style.display = "inline-block";
});
}
function hideOriginalSelectIfNeeded(select) {
if (!CONFIG.hideOriginalSelect || !select) {
return;
}
select.style.display = "none";
}
function showError(message) {
const error = document.createElement("div");
error.className = "np-ppl-error";
error.textContent = message;
const labContent = getLabContent();
labContent.insertBefore(error, labContent.firstChild);
}
function init() {
injectCss();
const select = getPetpetSelect();
const zapButton = getZapButton();
const petpets = getPetpetData(select);
if (!petpets.length) {
showError("Petpet Lab Helper could not find any petpets on this page.");
return;
}
showOriginalPetpets();
hideOriginalSelectIfNeeded(select);
buildHelper(petpets, select, zapButton);
}
ready(init);
})();