// ==UserScript==
// @name Twitch Poké Ball Helper (Ultimate Search Tab UI – Unified Font)
// @namespace http://tampermonkey.net/
// @version 5.13
// @description Twitch Poké Ball Helper with a three-column grid for Catch/Shop and an ultra-stylized Search tab that features a full-width Pokémon info card with advanced typography (all using Roboto), animated stat bars, detailed type relations, and a refined overall look.
// @author
// @match https://www.twitch.tv/*
// @icon https://static.twitchcdn.net/assets/favicon-32-e29e246c157142c94346.png
// @grant none
// ==/UserScript==
(function () {
'use strict';
class PokeballHelper {
constructor() {
// Define balls for the Catch tab (using !pokecatch)
this.catchBalls = {
check: { command: '!pokecheck', tooltip: 'Poke Check', image: 'https://cdn.discordapp.com/attachments/1095453488684744786/1343838706724896848/5c2d24739a206a1df3d19e60c801c494.png?ex=67bebad2&is=67bd6952&hm=6a86c6c6e6cc0e095accb89a7883ebb0b9c63d894600e4be6d29e0eadca4643b&' },
poke: { command: '!pokecatch pokeball', tooltip: 'Poke Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/poke_ball.png' },
great: { command: '!pokecatch greatball', tooltip: 'Great Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/great_ball.png' },
ultra: { command: '!pokecatch ultraball', tooltip: 'Ultra Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/ultra_ball.png' },
master: { command: '!pokecatch masterball', tooltip: 'Master Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/master_ball.png' },
premier: { command: '!pokecatch premierball', tooltip: 'Premier Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/premier_ball.png' },
cherish: { command: '!pokecatch cherishball', tooltip: 'Cherish Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/cherish_ball.png' },
greatCherish: { command: '!pokecatch greatcherishball', tooltip: 'Great Cherish Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/great_cherish_ball.png' },
ultraCherish: { command: '!pokecatch ultracherishball', tooltip: 'Ultra Cherish Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/ultra_cherish_ball.png' },
heavy: { command: '!pokecatch heavyball', tooltip: 'Heavy Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/heavy_ball.png' },
feather: { command: '!pokecatch featherball', tooltip: 'Feather Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/feather_ball.png' },
timer: { command: '!pokecatch timerball', tooltip: 'Timer Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/timer_ball.png' },
quick: { command: '!pokecatch quickball', tooltip: 'Quick Ball', image: 'https://www.shareicon.net/data/512x512/2016/12/13/863562_quick_512x512.png' },
nest: { command: '!pokecatch nestball', tooltip: 'Nest Ball', image: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/items/nest-ball.png' },
fast: { command: '!pokecatch fastball', tooltip: 'Fast Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/fast_ball.png' },
heal: { command: '!pokecatch healball', tooltip: 'Heal Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/heal_ball.png' },
repeat: { command: '!pokecatch repeatball', tooltip: 'Repeat Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/repeat_ball.png' },
friend: { command: '!pokecatch friendball', tooltip: 'Friend Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/friend_ball.png' },
frozen: { command: '!pokecatch frozenball', tooltip: 'Frozen Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/frozen_ball.png' },
night: { command: '!pokecatch nightball', tooltip: 'Night Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/night_ball.png' },
phantom: { command: '!pokecatch phantomball', tooltip: 'Phantom Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/phantom_ball.png' },
cipher: { command: '!pokecatch cipherball', tooltip: 'Cipher Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/cipher_ball.png' },
magnet: { command: '!pokecatch magnetball', tooltip: 'Magnet Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/magnet_ball.png' },
net: { command: '!pokecatch netball', tooltip: 'Net Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/net_ball.png' },
luxury: { command: '!pokecatch luxuryball', tooltip: 'Luxury Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/luxury_ball.png' },
stone: { command: '!pokecatch stoneball', tooltip: 'Stone Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/stone_ball.png' },
level: { command: '!pokecatch levelball', tooltip: 'Level Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/level_ball.png' },
clone: { command: '!pokecatch cloneball', tooltip: 'Clone Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/clone_ball.png' },
sun: { command: '!pokecatch sunball', tooltip: 'Sun Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/sun_ball.png' },
fantasy: { command: '!pokecatch fantasyball', tooltip: 'Fantasy Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/fantasy_ball.png' },
mach: { command: '!pokecatch machball', tooltip: 'Mach Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/mach_ball.png' },
dive: { command: '!pokecatch diveball', tooltip: 'Dive Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/dive_ball.png' }
};
// Define balls for the Shop tab (you can expand this list if needed)
this.shopBalls = {
pokeball: { command: '!pokeshop pokeball', tooltip: 'Poke Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/poke_ball.png' },
great: { command: '!pokeshop greatball', tooltip: 'Great Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/great_ball.png' },
ultra: { command: '!pokeshop ultraball', tooltip: 'Ultra Ball', image: 'https://poketwitch.bframework.de/static/twitchextension/items/ball/ultra_ball.png' }
};
// Set default tab mode to 'catch'
this.currentTab = 'catch';
this.init();
}
init() {
this.setupStyles();
this.waitForChat().then(() => {
this.createInterface();
this.addEventListeners();
this.renderGrid();
});
}
setupStyles() {
const style = document.createElement('style');
style.textContent = `
/* Import Roboto font for a modern look */
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
:root {
--background-dark: #18181b;
--background-darker: #2e2e35;
--card-background: #1f1f26;
--border-color: #3e3e45;
--highlight-color: #76c7c0;
--highlight-gradient: linear-gradient(90deg, var(--highlight-color), #4db6ac);
--text-light: #ffffff;
--text-muted: #ccc;
--font-family: 'Roboto', sans-serif;
}
/* Global font rule for the entire widget */
.pball-container, .pball-container * {
font-family: var(--font-family) !important;
}
/* Global styles for the widget */
.pball-container {
position: fixed;
bottom: 80px;
right: 20px;
z-index: 10000;
}
.pball-button {
cursor: pointer;
width: 50px;
height: 50px;
border-radius: 50%;
border: 2px solid var(--border-color);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
transition: transform 0.2s ease, box-shadow 0.2s ease;
background: var(--background-dark);
}
.pball-button:hover {
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.pball-panel {
position: absolute;
bottom: calc(100% + 10px);
right: 0;
background: rgba(24, 24, 27, 0.97);
width: 280px;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
visibility: hidden;
opacity: 0;
transform: translateY(10px);
transition: opacity 0.2s ease, transform 0.2s ease;
pointer-events: none;
}
.pball-panel.active {
visibility: visible;
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.pball-tabs {
display: flex;
background: var(--background-darker);
border-bottom: 1px solid var(--border-color);
}
.pball-tab {
flex: 1;
text-align: center;
padding: 8px;
font-size: 16px;
cursor: pointer;
color: var(--text-muted);
transition: background 0.2s ease;
}
.pball-tab.active,
.pball-tab:hover {
background: var(--border-color);
color: var(--text-light);
}
.pball-search-container {
position: relative;
width: calc(100% - 20px);
margin: 10px;
}
.pball-search {
width: 100%;
padding: 6px 30px 6px 8px;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--background-dark);
color: var(--text-light);
font-size: 15px;
outline: none;
}
.pball-search::placeholder {
color: #777;
}
.pball-clear-btn {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
background: transparent;
border: none;
color: var(--text-muted);
font-size: 16px;
cursor: pointer;
display: none;
}
.pball-grid {
display: grid;
gap: 10px;
padding: 10px;
max-height: 240px;
overflow-y: auto;
}
.pball-grid.ball-items {
grid-template-columns: repeat(3, 1fr);
}
.pball-grid.search-results {
grid-template-columns: 1fr;
}
.pball-grid::-webkit-scrollbar {
width: 8px;
}
.pball-grid::-webkit-scrollbar-track {
background: var(--background-dark);
border-radius: 4px;
}
.pball-grid::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 4px;
}
.pball-grid::-webkit-scrollbar-thumb:hover {
background: #4b4b56;
}
.moves-section::-webkit-scrollbar {
width: 8px;
}
.moves-section::-webkit-scrollbar-track {
background: var(--background-dark);
border-radius: 4px;
}
.moves-section::-webkit-scrollbar-thumb {
background: var(--border-color);
border-radius: 4px;
}
.moves-section::-webkit-scrollbar-thumb:hover {
background: #4b4b56;
}
.pball-item {
display: flex;
flex-direction: column;
align-items: center;
cursor: default;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.pball-item:hover {
transform: translateY(-4px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
}
.pball-item img {
width: 40px;
height: 40px;
}
.pball-label {
margin-top: 4px;
font-size: 14px;
font-weight: 600;
color: var(--text-light);
text-align: center;
}
/* SEARCH TAB - Enhanced Pokémon Info Card */
.poke-card {
width: 100%;
background: var(--card-background);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 16px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
color: var(--text-light);
display: flex;
flex-direction: column;
gap: 16px;
animation: fadeIn 0.5s ease;
box-sizing: border-box;
}
@keyframes fadeIn {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
.poke-card header {
display: flex;
align-items: center;
gap: 16px;
border-bottom: 1px solid var(--border-color);
padding-bottom: 8px;
}
.poke-card header img {
width: 100px;
height: 100px;
border-radius: 8px;
background: var(--background-dark);
object-fit: contain;
}
.poke-card h2 {
margin: 0;
font-size: 32px;
font-weight: 700;
}
.poke-card p {
margin: 4px 0;
font-size: 16px;
}
.section {
border-top: 1px solid var(--border-color);
padding-top: 8px;
}
.section h3 {
margin: 8px 0;
font-size: 20px;
font-weight: 700;
color: var(--text-light);
border-bottom: 1px solid var(--border-color);
padding-bottom: 4px;
}
/* Refined Stats */
.stat {
display: flex;
flex-direction: column;
margin-bottom: 8px;
}
.stat-label {
font-size: 16px;
margin-bottom: 4px;
color: #ddd;
}
.stat-bar {
width: 100%;
background: var(--background-dark);
border: 1px solid var(--border-color);
border-radius: 4px;
height: 18px;
overflow: hidden;
position: relative;
}
.stat-fill {
background: var(--highlight-gradient);
height: 100%;
width: 0;
transition: width 0.5s ease;
border-radius: 4px;
position: relative;
}
.stat-value {
position: absolute;
right: 6px;
top: 50%;
transform: translateY(-50%);
font-size: 14px;
color: var(--text-light);
font-weight: bold;
}
/* Moves Section */
.moves-section {
max-height: 150px;
overflow-y: auto;
font-size: 15px;
color: var(--text-muted);
margin-top: 8px;
padding-right: 4px;
}
.moves-section ul {
list-style: none;
padding: 0;
margin: 0;
}
.moves-section li {
margin-bottom: 4px;
}
/* Type Damage Relations */
.type-relations {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 8px;
}
.type-box {
background: var(--background-dark);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 6px;
font-size: 13px;
color: var(--text-light);
transition: transform 0.2s ease;
}
.type-box:hover {
transform: scale(1.02);
}
.type-box strong {
display: block;
margin-bottom: 4px;
font-size: 14px;
}
/* Spinner */
.spinner {
margin: 20px auto;
border: 4px solid var(--border-color);
border-top: 4px solid var(--highlight-color);
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
}
async waitForChat() {
return new Promise((resolve) => {
if (document.querySelector('[data-test-selector="chat-input"]')) {
return resolve();
}
const observer = new MutationObserver(() => {
if (document.querySelector('[data-test-selector="chat-input"]')) {
observer.disconnect();
resolve();
}
});
observer.observe(document.body, { childList: true, subtree: true });
});
}
createInterface() {
this.container = document.createElement('div');
this.container.className = 'pball-container';
this.button = this.createMainButton();
this.panel = this.createPanel();
this.container.append(this.button, this.panel);
document.body.appendChild(this.container);
}
createMainButton() {
const button = document.createElement('img');
button.className = 'pball-button';
button.src = this.catchBalls.poke.image;
return button;
}
createPanel() {
const panel = document.createElement('div');
panel.className = 'pball-panel';
const tabsContainer = document.createElement('div');
tabsContainer.className = 'pball-tabs';
const catchTab = document.createElement('div');
catchTab.className = 'pball-tab active';
catchTab.textContent = 'Catch';
catchTab.dataset.tab = 'catch';
tabsContainer.appendChild(catchTab);
const shopTab = document.createElement('div');
shopTab.className = 'pball-tab';
shopTab.textContent = 'Shop';
shopTab.dataset.tab = 'shop';
tabsContainer.appendChild(shopTab);
const searchTab = document.createElement('div');
searchTab.className = 'pball-tab';
searchTab.textContent = 'Search';
searchTab.dataset.tab = 'search';
tabsContainer.appendChild(searchTab);
const searchContainer = document.createElement('div');
searchContainer.className = 'pball-search-container';
this.searchInput = document.createElement('input');
this.searchInput.type = 'text';
this.searchInput.className = 'pball-search';
this.searchInput.placeholder = 'Search...';
this.clearBtn = document.createElement('button');
this.clearBtn.className = 'pball-clear-btn';
this.clearBtn.textContent = '×';
searchContainer.append(this.searchInput, this.clearBtn);
this.gridContainer = document.createElement('div');
this.gridContainer.className = 'pball-grid';
panel.append(tabsContainer, searchContainer, this.gridContainer);
return panel;
}
renderGrid() {
if (this.currentTab === 'search') {
this.gridContainer.classList.remove('ball-items');
this.gridContainer.classList.add('search-results');
this.renderPokemonSearch();
} else {
this.gridContainer.classList.remove('search-results');
this.gridContainer.classList.add('ball-items');
this.gridContainer.innerHTML = '';
const balls = this.currentTab === 'catch' ? this.catchBalls : this.shopBalls;
Object.entries(balls).forEach(([key, ball]) => {
const item = document.createElement('div');
item.className = 'pball-item';
item.dataset.label = ball.tooltip.toLowerCase();
const img = document.createElement('img');
img.src = ball.image;
img.dataset.ballType = ball.command;
img.draggable = true;
const label = document.createElement('div');
label.className = 'pball-label';
label.textContent = ball.tooltip;
item.append(img, label);
this.gridContainer.appendChild(item);
});
this.filterGrid();
}
}
renderPokemonSearch() {
this.gridContainer.innerHTML = '';
const info = document.createElement('div');
info.style.padding = '10px';
info.style.color = 'var(--text-light)';
info.textContent = 'Enter a Pokémon name and press Enter to search.';
this.gridContainer.appendChild(info);
}
addEventListeners() {
this.button.addEventListener('click', (e) => {
e.stopPropagation();
this.panel.classList.toggle('active');
if (this.panel.classList.contains('active')) {
this.searchInput.focus();
}
});
document.addEventListener('click', (e) => {
if (!this.container.contains(e.target)) {
this.panel.classList.remove('active');
}
});
this.panel.addEventListener('dragstart', (e) => {
const ballImg = e.target.closest('.pball-item img');
if (ballImg) {
e.dataTransfer.setData('text/plain', ballImg.dataset.ballType);
}
});
const chatInput = this.getChatInput();
if (chatInput) {
chatInput.addEventListener('dragover', (e) => e.preventDefault());
chatInput.addEventListener('drop', (e) => {
e.preventDefault();
const ballType = e.dataTransfer.getData('text/plain');
this.insertCommand(ballType);
});
}
const tabs = this.panel.querySelectorAll('.pball-tab');
tabs.forEach(tab => {
tab.addEventListener('click', (e) => {
e.stopPropagation();
this.changeTab(tab.dataset.tab);
});
});
this.searchInput.addEventListener('input', () => {
if (this.currentTab !== 'search') {
this.filterGrid();
}
this.clearBtn.style.display = this.searchInput.value.trim() ? 'block' : 'none';
});
this.clearBtn.addEventListener('click', () => {
this.searchInput.value = '';
this.clearBtn.style.display = 'none';
if (this.currentTab !== 'search') {
this.filterGrid();
}
});
this.searchInput.addEventListener('keydown', (e) => {
if (this.currentTab === 'search' && e.key === 'Enter') {
this.searchPokemon(this.searchInput.value.trim());
}
});
}
changeTab(tabName) {
this.currentTab = tabName;
const tabs = this.panel.querySelectorAll('.pball-tab');
tabs.forEach(tab => {
tab.classList.toggle('active', tab.dataset.tab === tabName);
});
this.searchInput.placeholder = (tabName === 'search') ? 'Search Pokémon...' : 'Search...';
this.searchInput.value = '';
this.clearBtn.style.display = 'none';
this.renderGrid();
}
filterGrid() {
const query = this.searchInput.value.trim().toLowerCase();
const items = this.gridContainer.querySelectorAll('.pball-item');
items.forEach(item => {
if (!query || item.dataset.label.includes(query)) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
});
}
getChatInput() {
return document.querySelector('[data-a-target="chat-input"]');
}
insertCommand(ballType) {
const chatInput = this.getChatInput();
if (!chatInput) return;
chatInput.focus();
this.clearChatInput();
this.insertText(ballType);
this.triggerInputEvent(chatInput);
}
clearChatInput() {
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(this.getChatInput());
selection.removeAllRanges();
selection.addRange(range);
document.execCommand('delete');
}
insertText(text) {
document.execCommand('insertText', false, text);
}
triggerInputEvent(element) {
element.dispatchEvent(new Event('input', { bubbles: true, composed: true }));
}
searchPokemon(name) {
if (!name) return;
this.gridContainer.innerHTML = '<div class="spinner"></div>';
fetch(`https://pokeapi.co/api/v2/pokemon/${name.toLowerCase()}`)
.then(response => {
if (!response.ok) { throw new Error("Pokémon not found"); }
return response.json();
})
.then(data => {
this.displayPokemonData(data);
})
.catch(err => {
this.gridContainer.innerHTML = `<div style="padding:10px; color: var(--text-light);">${err.message}</div>`;
});
}
displayPokemonData(data) {
this.gridContainer.innerHTML = '';
const card = document.createElement('div');
card.className = 'poke-card';
// Header
const header = document.createElement('header');
const img = document.createElement('img');
img.src = (data.sprites.other && data.sprites.other['official-artwork'] &&
data.sprites.other['official-artwork'].front_default)
|| data.sprites.front_default || '';
header.appendChild(img);
const title = document.createElement('h2');
title.textContent = `${data.name.charAt(0).toUpperCase() + data.name.slice(1)} (ID: ${data.id})`;
header.appendChild(title);
card.appendChild(header);
// Calculate total stats
const totalStats = data.stats.reduce((sum, stat) => sum + stat.base_stat, 0);
// Basic Info with Total Stats added above Height
const basicInfo = document.createElement('div');
basicInfo.className = 'section';
basicInfo.innerHTML = `
<h3>Basic Info</h3>
<p><strong>Total Stats:</strong> ${totalStats}</p>
<p><strong>Height:</strong> ${data.height}</p>
<p><strong>Weight:</strong> ${data.weight}</p>
<p><strong>Base Exp:</strong> ${data.base_experience}</p>
<p><strong>Species:</strong> ${data.species.name}</p>
`;
card.appendChild(basicInfo);
// Abilities
const abilitiesSection = document.createElement('div');
abilitiesSection.className = 'section';
abilitiesSection.innerHTML = `<h3>Abilities</h3>`;
const abilitiesList = document.createElement('ul');
data.abilities.forEach(a => {
const li = document.createElement('li');
li.textContent = `${a.ability.name}${a.is_hidden ? ' (Hidden)' : ''}`;
abilitiesList.appendChild(li);
});
abilitiesSection.appendChild(abilitiesList);
card.appendChild(abilitiesSection);
// Stats
const statsSection = document.createElement('div');
statsSection.className = 'section';
statsSection.innerHTML = `<h3>Stats</h3>`;
data.stats.forEach(stat => {
const statContainer = document.createElement('div');
statContainer.className = 'stat';
const label = document.createElement('div');
label.className = 'stat-label';
label.textContent = `${stat.stat.name.toUpperCase()}: ${stat.base_stat}`;
statContainer.appendChild(label);
const bar = document.createElement('div');
bar.className = 'stat-bar';
const fill = document.createElement('div');
fill.className = 'stat-fill';
const percentage = Math.min(100, (stat.base_stat / 255) * 100);
fill.style.width = `${percentage}%`;
const statValue = document.createElement('span');
statValue.className = 'stat-value';
statValue.textContent = stat.base_stat;
fill.appendChild(statValue);
bar.appendChild(fill);
statContainer.appendChild(bar);
statsSection.appendChild(statContainer);
});
card.appendChild(statsSection);
// Types
const typesSection = document.createElement('div');
typesSection.className = 'section';
typesSection.innerHTML = `<h3>Types</h3>`;
const typesList = document.createElement('ul');
data.types.forEach(typeInfo => {
const li = document.createElement('li');
li.textContent = typeInfo.type.name;
typesList.appendChild(li);
});
typesSection.appendChild(typesList);
card.appendChild(typesSection);
// Type Damage Relations
const typeRelationsSection = document.createElement('div');
typeRelationsSection.className = 'section';
typeRelationsSection.innerHTML = `<h3>Type Damage Relations</h3>`;
const typeRelationsBox = document.createElement('div');
typeRelationsBox.className = 'type-relations';
data.types.forEach(typeInfo => {
const typeBox = document.createElement('div');
typeBox.className = 'type-box';
typeBox.innerHTML = `<strong>${typeInfo.type.name.toUpperCase()}</strong>`;
fetch(typeInfo.type.url)
.then(res => res.json())
.then(typeData => {
const strengths = typeData.damage_relations.double_damage_to.map(d => d.name).join(', ') || "None";
const weaknesses = typeData.damage_relations.double_damage_from.map(d => d.name).join(', ') || "None";
const details = document.createElement('div');
details.innerHTML = `<p><strong>Strengths:</strong> ${strengths}</p><p><strong>Weaknesses:</strong> ${weaknesses}</p>`;
typeBox.appendChild(details);
})
.catch(() => {
const errMsg = document.createElement('div');
errMsg.textContent = "Error loading type data";
typeBox.appendChild(errMsg);
});
typeRelationsBox.appendChild(typeBox);
});
typeRelationsSection.appendChild(typeRelationsBox);
card.appendChild(typeRelationsSection);
// Moves
const movesSection = document.createElement('div');
movesSection.className = 'section moves-section';
movesSection.innerHTML = `<h3>Moves</h3>`;
const movesList = document.createElement('ul');
data.moves.forEach(moveInfo => {
const li = document.createElement('li');
li.textContent = moveInfo.move.name;
movesList.appendChild(li);
});
movesSection.appendChild(movesList);
card.appendChild(movesSection);
// Held Items
if (data.held_items.length) {
const itemsSection = document.createElement('div');
itemsSection.className = 'section';
itemsSection.innerHTML = `<h3>Held Items</h3>`;
const itemsList = document.createElement('ul');
data.held_items.forEach(itemInfo => {
const li = document.createElement('li');
li.textContent = itemInfo.item.name;
itemsList.appendChild(li);
});
itemsSection.appendChild(itemsList);
card.appendChild(itemsSection);
}
// Forms
if (data.forms.length) {
const formsSection = document.createElement('div');
formsSection.className = 'section';
formsSection.innerHTML = `<h3>Forms</h3>`;
const formsList = document.createElement('ul');
data.forms.forEach(form => {
const li = document.createElement('li');
li.textContent = form.name;
formsList.appendChild(li);
});
formsSection.appendChild(formsList);
card.appendChild(formsSection);
}
this.gridContainer.innerHTML = '';
this.gridContainer.appendChild(card);
}
}
new PokeballHelper();
})();