Initializing
Liveweave
Web
expand_more
home
Home
data_object
CSS Explorer
arrow_outward
Palette
Color Explorer
arrow_outward
Polyline
Graphics Editor
arrow_outward
outbox_alt
Generative AI
arrow_outward
frame_source
Python Playground
New
arrow_outward
build
Tools
expand_more
restart_alt
Load "Hello Weaver!"
post_add
Generate Lorem ipsum...
code
Format HTML
code_blocks
Format CSS
data_object
Format JavaScript
library_add
Library
expand_more
A
Algolia JS
Animate CSS
Apex Charts JS
B
Bulma CSS
Bootstrap
C
Chart JS
Chartist
Create JS
D
D3
Dojo
F
Foundation
Fullpage JS
G
Granim JS
Google Charts
H
Halfmoon
J
jQuery
M
Materialize
Moment JS
Masonry JS
Milligram CSS
P
Pure CSS
Primer CSS
Popper JS
Pattern CSS
Picnic CSS
R
React JS
Raphael JS
Raisin CSS
S
Semantic UI
Skeleton CSS
Spectre CSS
Tachyons CSS
T
Tailwind
Three JS
U
UI Kit
Vis JS
W
Water CSS
download
Download
expand_more
developer_mode
Download as HTML
folder_zip
Download as .ZIP
cloud_upload
Save
account_circle
Login
settings
Settings
expand_more
14
px
Live mode
Night mode
Line number
Mini map
Word wrap
sync_alt
Reset Settings
smart_display
Run
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Epic Pokemon Explorer</title> <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700;900&display=swap" rel="stylesheet"> <style> :root { --primary-color: #FFDE00; --secondary-color: #3B4CCA; --accent-color: #0075BE; --text-color: #222; --background-color: #eef2f7; --card-background: #ffffff; --border-color: #ddd; --shadow-color: rgba(0, 0, 0, 0.25); --border-radius-base: 16px; --padding-base: 24px; --margin-base: 40px; --font-family: 'Roboto', sans-serif; --max-stat-value: 255; --pokemon-theme-background-start: #d3d3a4; --pokemon-theme-background-end: #a8a878; --pokemon-theme-text: #333; --pokemon-theme-stat-bar: #a8a878; --pokemon-theme-detail: #6d6d4e; --pokemon-theme-accent: #4d4d33; --pokemon-theme-tag-text: #333; --stat-bar-gradient-start: color-mix(in srgb, var(--pokemon-theme-stat-bar) 60%, white 40%); --stat-bar-gradient-end: var(--pokemon-theme-stat-bar); --pattern-dot-color: rgba(0, 0, 0, 0.1); --pattern-dot-size: 3px; --pattern-dot-spacing: 18px; --primary-color-rgb: 255, 222, 0; } *, *::before, *::after { box-sizing: border-box; } body { font-family: var(--font-family); background: linear-gradient(to bottom right, var(--background-color), #cdd9e7); color: var(--text-color); display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; margin: 0; padding: var(--margin-base) var(--padding-base); line-height: 1.6; overflow-y: auto; overflow-x: hidden; position: relative; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .pokemon-container { background: linear-gradient(to bottom right, var(--pokemon-theme-background-start), var(--pokemon-theme-background-end)); border: 1px solid rgba(255, 255, 255, 0.3); border-radius: var(--border-radius-base); box-shadow: 0 15px 40px var(--shadow-color); padding: var(--padding-base); display: flex; flex-direction: column; align-items: center; max-width: 700px; width: 100%; text-align: center; overflow: hidden; position: relative; opacity: 0; transform: translateY(60px); transition: box-shadow 0.3s ease, transform 0.3s ease; will-change: transform, opacity, box-shadow; z-index: 1; } .pokemon-container:hover { box-shadow: 0 20px 45px rgba(0, 0, 0, 0.3); transform: translateY(55px); } .pokemon-container::before { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 0; opacity: 0.3; background-image: radial-gradient(circle, var(--pattern-dot-color) var(--pattern-dot-size), transparent var(--pattern-dot-size)), radial-gradient(circle, var(--pattern-dot-color) var(--pattern-dot-size), transparent var(--pattern-dot-size)); background-size: var(--pattern-dot-spacing) var(--pattern-dot-spacing); background-position: 0 0, calc(var(--pattern-dot-spacing)/2) calc(var(--pattern-dot-spacing)/2); } .pokemon-container.no-pattern::before { display: none; } .pokemon-container.error-state { background: linear-gradient(to bottom right, var(--background-color), var(--border-color)); color: var(--text-color); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15); } .loading-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.98); display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 100; opacity: 1; transition: opacity 0.6s ease; pointer-events: auto; color: var(--secondary-color); font-size: 1.4rem; font-weight: bold; text-shadow: 0 1px 3px rgba(0,0,0,0.1); } .loading-overlay.hidden { opacity: 0; pointer-events: none; } .loader { border: 10px solid #f3f3f3; border-top: 10px solid var(--accent-color); border-radius: 50%; width: 70px; height: 70px; animation: spin 1s cubic-bezier(0.5, 0.1, 0.5, 0.9) infinite; margin-bottom: 30px; box-shadow: 0 0 10px rgba(0,0,0,0.1); } @keyframes spin { 0%{transform:rotate(0deg)}100%{transform:rotate(360deg)} } .pokemon-navigation { display: flex; justify-content: space-between; width: 100%; margin-bottom: 16px; gap: var(--padding-base); z-index: 5; } .nav-button { flex: 1; background-image: linear-gradient(to right, var(--secondary-color), var(--accent-color)); color: white; border: none; border-radius: calc(var(--border-radius-base)/2); padding: 14px 24px; cursor: pointer; font-size: 1.2rem; font-weight: bold; transition: background-color 0.2s ease, transform 0.1s ease, box-shadow 0.2s ease; box-shadow: 0 4px 9px rgba(0,0,0,0.25); text-transform: uppercase; letter-spacing: 0.8px; position: relative; z-index: 1; } .nav-button:disabled { background-color: #ccc; background-image: none; cursor: not-allowed; box-shadow: none; opacity: 0.6; } .nav-button:not(:disabled):hover { background-image: linear-gradient(to right, var(--accent-color), #005a9e); box-shadow: 0 6px 12px rgba(0,0,0,0.3); } .nav-button:not(:disabled):active { transform: scale(0.98); box-shadow: 0 3px 6px rgba(0,0,0,0.2); } .nav-button:not(:disabled):focus-visible { outline: 5px solid var(--primary-color); outline-offset: 6px; } .progress-indicator { font-size: 1rem; color: var(--pokemon-theme-detail); margin-bottom: var(--padding-base); text-transform: uppercase; letter-spacing: 0.5px; z-index: 5; } .pokemon-display-area { position: relative; width: 100%; display: flex; flex-direction: column; align-items: center; min-height: 350px; margin-bottom: var(--padding-base); z-index: 5; } .pokemon-display-wrapper { position: relative; width: 100%; height: 300px; margin-bottom: 16px; } .pokemon-display { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); width: 280px; height: 280px; display: flex; justify-content: center; align-items: center; user-select: none; cursor: default; will-change: transform, opacity; z-index: 10; opacity: 0; pointer-events: none; } .pokemon-display:focus { outline: none; } .pokemon-image { display: block; width: 100%; height: 100%; object-fit: contain; filter: drop-shadow(10px 12px 25px var(--shadow-color)); will-change: transform, opacity, filter; opacity: 0; transform-origin: center center; } .pokemon-image-placeholder { display: flex; background: #E5E7EB; border: 3px dashed #9CA3AF; border-radius: var(--border-radius-base); width: 100%; height: 100%; align-items: center; justify-content: center; font-size: 1.1rem; color: #6B7280; padding: var(--padding-base); opacity: 0; transform-origin: center center; } .sprite-selector-container { width: 100%; max-width: 300px; margin: 0 auto 0; z-index: 6; opacity: 0; transform: translateY(20px); will-change: opacity, transform; text-align: left; } .sprite-selector-container label { display: block; font-size: 1rem; font-weight: bold; color: var(--pokemon-theme-accent); margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px; } /* Search functionality styles */ .search-container { width: 100%; margin-bottom: var(--padding-base); display: flex; justify-content: center; gap: 10px; opacity: 0; transform: translateY(20px); will-change: opacity, transform; text-align: center; } .search-input { padding: 10px 15px; font-size: 1.1rem; font-family: var(--font-family); border: 2px solid var(--border-color); border-radius: calc(var(--border-radius-base)/3); width: 200px; max-width: 70%; transition: border-color 0.2s ease, box-shadow 0.2s ease; } .search-input:focus-visible { border-color: var(--accent-color); outline: 3px solid var(--primary-color); outline-offset: 3px; box-shadow: 0 0 8px rgba(var(--primary-color-rgb), 0.4); } .search-button.nav-button { flex: none; min-width: auto; } .action-message { position: absolute; bottom: 0; left: 50%; transform: translateX(-50%); background-color: var(--primary-color); color: var(--text-color); padding: 10px 20px; border-radius: 25px; font-size: 1.1rem; opacity: 0; pointer-events: none; white-space: nowrap; z-index: 10; box-shadow: 0 4px 10px rgba(0,0,0,0.25); display: none; transition: background-color 0.3s ease, color 0.3s ease, box-shadow 0.3s ease; } .pokemon-info { width: 100%; opacity: 0; transform: translateY(30px); text-align: left; will-change: transform, opacity; color: var(--pokemon-theme-text); position: relative; z-index: 5; } .pokemon-info h2 { color: var(--pokemon-theme-accent); margin: 0 0 12px 0; font-size: 3.2rem; text-transform: capitalize; font-weight: 900; text-align: center; line-height: 1.1; text-shadow: 1px 1px 3px rgba(0,0,0,0.1); } .pokemon-description { margin: 0 0 20px 0; font-size: 1.15rem; color: var(--pokemon-theme-detail); text-align: center; font-style: italic; max-width: 90%; margin-left: auto; margin-right: auto; } .type-tag-container { display: flex; justify-content: center; gap: 10px; margin: 15px 0 25px 0; flex-wrap: wrap; } .type-tag { display: inline-block; padding: 8px 18px; border-radius: 30px; font-size: 1.05rem; text-transform: capitalize; font-weight: bold; box-shadow: 0 2px 6px rgba(0,0,0,0.2); transition: transform 0.2s ease, box-shadow 0.2s ease; min-width: 90px; text-align: center; border: 1px solid rgba(255,255,255,0.5); will-change: transform, box-shadow; } .type-tag:hover { transform: translateY(-3px); box-shadow: 0 4px 8px rgba(0,0,0,0.25); } .pokemon-details { width: 100%; margin-top: var(--padding-base); padding-top: var(--padding-base); border-top: 2px dashed rgba(255,255,255,0.5); display: grid; grid-template-columns: repeat(auto-fit, minmax(160px,1fr)); gap: 20px 30px; text-align: center; color: var(--pokemon-theme-detail); font-size: 1.1rem; opacity: 0; transform: translateY(30px); will-change: transform, opacity; } .detail-item { display: flex; flex-direction: column; align-items: center; } .detail-label { font-size: 1rem; color: var(--pokemon-theme-accent); margin-bottom: 6px; font-weight: bold; text-transform: uppercase; letter-spacing: 0.5px; } .detail-value { font-size: 1.2rem; font-weight: bold; color: var(--pokemon-theme-text); } .detail-item.full-width { grid-column: 1 / -1; margin-top: 15px; } .abilities-list { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; justify-content: center; gap: 8px 15px; } .ability-item { background-color: rgba(255,255,255,0.4); color: var(--pokemon-theme-text); padding: 6px 15px; border-radius: 20px; font-size: 1rem; text-transform: capitalize; font-weight: 500; box-shadow: 0 1px 4px rgba(0,0,0,0.1); transition: background-color 0.2s ease; } .ability-item:hover { background-color: rgba(255,255,255,0.6); } .pokemon-stats { margin-top: var(--padding-base); padding-top: var(--padding-base); border-top: 2px dashed rgba(255,255,255,0.5); width: 100%; display: grid; grid-template-columns: repeat(auto-fit, minmax(140px,1fr)); gap: 20px 40px; text-align: left; opacity: 0; transform: translateY(30px); will-change: transform, opacity; } .stat-item { display: flex; flex-direction: column; align-items: flex-start; width: 100%; } .stat-label { font-size: 1rem; color: var(--pokemon-theme-accent); margin-bottom: 6px; font-weight: bold; text-transform: uppercase; letter-spacing: 0.5px; } .stat-value-bar { display: flex; align-items: center; width: 100%; gap: 12px; } .stat-value { font-size: 1.15rem; color: var(--pokemon-theme-text); font-weight: bold; min-width: 40px; text-align: right; } .stat-bar-container { flex-grow: 1; height: 14px; background-color: rgba(255,255,255,0.4); border-radius: 7px; overflow: hidden; box-shadow: inset 0 1px 4px rgba(0,0,0,0.15); border: 1px solid rgba(255,255,255,0.5); } .stat-bar { height: 100%; background: linear-gradient(to right, var(--stat-bar-gradient-start), var(--stat-bar-gradient-end)); width: 0%; border-radius: 7px; will-change: width; } @media (max-width:768px) { body { padding: calc(var(--margin-base)/1.5) calc(var(--padding-base)/1.5); } .pokemon-container { padding: var(--padding-base); margin-bottom: calc(var(--margin-base)/2); } .pokemon-navigation { gap: calc(var(--padding-base)/2); } .nav-button { padding: 12px 20px; font-size: 1.1rem; } .pokemon-display-area { min-height:300px; margin-bottom:calc(var(--padding-base)*0.8); } .pokemon-display-wrapper { height:250px; margin-bottom:12px; } .pokemon-display { width:220px; height:220px; } .pokemon-info h2 { font-size:2.8rem; } .pokemon-description { font-size:1.05rem; } .type-tag { font-size:1rem; padding:7px 16px; } .pokemon-details, .pokemon-stats { grid-template-columns: repeat(auto-fit,minmax(140px,1fr)); gap:15px 25px; } .detail-label, .stat-label { font-size:0.95rem; } .detail-value, .stat-value { font-size:1.1rem; } .ability-item { font-size:0.95rem; } } @media (max-width:576px) { body { padding: calc(var(--margin-base)/2) calc(var(--padding-base)/2); } .pokemon-navigation { flex-direction: column; gap:12px; margin-bottom:var(--padding-base); } .nav-button { padding:12px 18px; font-size:1rem; } .pokemon-display-area { min-height:250px; margin-bottom:calc(var(--padding-base)*0.6); } .pokemon-display-wrapper { height:200px; margin-bottom:10px; } .pokemon-display { width:180px; height:180px; } .pokemon-info h2 { font-size:2.4rem; } .pokemon-description { font-size:1rem; } .type-tag { font-size:0.9rem; padding:6px 14px; } .pokemon-details, .pokemon-stats { grid-template-columns:1fr; gap:12px; } .detail-value-bar { gap:8px; } .stat-value { min-width:30px; } .sprite-selector-container { max-width:none; padding:0 10px; } body, .pokemon-container { padding-left:16px; padding-right:16px; } } </style> </head> <body> <div class="loading-overlay" aria-live="polite" aria-label="Loading Pokemon data"> <div class="loader"></div> Fetching Pokemon Data... </div> <div class="pokemon-container" role="region" aria-label="Pokemon Display and Navigation"> <!-- Search Feature --> <div class="search-container"> <input type="text" id="search-input" class="search-input" placeholder="Search by name or #" aria-label="Search by Pokemon name or number"> <button class="nav-button search-button" id="search-button" aria-label="Search Pokemon">Search</button> </div> <div class="pokemon-navigation"> <button class="nav-button" id="prev-pokemon" aria-label="Previous Pokemon">Previous</button> <button class="nav-button" id="random-pokemon" aria-label="Random Pokemon">Random</button> <button class="nav-button" id="next-pokemon" aria-label="Next Pokemon">Next</button> <button class="nav-button" id="toggle-autoplay" aria-label="Toggle autoplay">Play</button> </div> <div class="progress-indicator" id="progress-indicator" aria-live="polite" aria-label="Current Pokemon progress">#000 / 000</div> <div class="pokemon-display-area"> <div class="pokemon-display-wrapper"> <div class="pokemon-display" aria-label="Pokemon Sprite Display"> <img class="pokemon-image" src="" alt="" aria-hidden="true"> <div class="pokemon-image-placeholder" aria-hidden="false">Loading...</div> </div> </div> <div class="sprite-selector-container"> <label for="sprite-select">Select Sprite View:</label> <select id="sprite-select" class="sprite-selector" aria-label="Select Pokemon Sprite View"></select> </div> <span class="action-message" aria-live="polite"></span> </div> <div class="pokemon-info"> <h2 class="pokemon-name"></h2> <p class="pokemon-description"></p> <div class="type-tag-container"></div> <div class="pokemon-details"> <div class="detail-item"> <span class="detail-label">Height</span> <strong class="detail-value pokemon-height"></strong> </div> <div class="detail-item"> <span class="detail-label">Weight</span> <strong class="detail-value pokemon-weight"></strong> </div> <div class="detail-item full-width"> <span class="detail-label">Abilities</span> <ul class="abilities-list pokemon-abilities"></ul> </div> </div> <div class="pokemon-stats"> <div class="stat-item"> <span class="stat-label">HP</span> <div class="stat-value-bar"> <strong class="stat-value stat-hp"></strong> <div class="stat-bar-container"><div class="stat-bar hp-bar"></div></div> </div> </div> <div class="stat-item"> <span class="stat-label">Attack</span> <div class="stat-value-bar"> <strong class="stat-value stat-attack"></strong> <div class="stat-bar-container"><div class="stat-bar attack-bar"></div></div> </div> </div> <div class="stat-item"> <span class="stat-label">Defense</span> <div class="stat-value-bar"> <strong class="stat-value stat-defense"></strong> <div class="stat-bar-container"><div class="stat-bar defense-bar"></div></div> </div> </div> <div class="stat-item"> <span class="stat-label">Sp. Atk</span> <div class="stat-value-bar"> <strong class="stat-value stat-special-attack"></strong> <div class="stat-bar-container"><div class="stat-bar special-attack-bar"></div></div> </div> </div> <div class="stat-item"> <span class="stat-label">Sp. Def</span> <div class="stat-value-bar"> <strong class="stat-value stat-special-defense"></strong> <div class="stat-bar-container"><div class="stat-bar special-defense-bar"></div></div> </div> </div> <div class="stat-item"> <span class="stat-label">Speed</span> <div class="stat-value-bar"> <strong class="stat-value stat-speed"></strong> <div class="stat-bar-container"><div class="stat-bar speed-bar"></div></div> </div> </div> </div> </div> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script> <script> /** * Epic Pokemon Explorer Experience * Creative Enhancements & Refactoring: * - Added Random Pokemon navigation button with keyboard left/right arrows and 'R' key shortcuts. * - Added progress indicator displaying current index and total count (#001 / 100). * - Added Search functionality: search by name or number with input field. * - Added Autoplay slideshow feature with Play/Pause button and 'P' key shortcut. */ (function() { const POKEMON_API_BASE_URL = 'https://pokeapi.co/api/v2/'; const POKEMON_LIMIT = 100; const MAX_BASE_STAT = 255; const spriteKeys = [ 'other.official-artwork.front_default', 'other.official-artwork.front_shiny', 'front_default', 'back_default', 'front_shiny', 'back_shiny', 'other.showdown.front_default','other.showdown.back_default', 'other.showdown.front_shiny','other.showdown.back_shiny' ]; const typeThemeMap = { /* existing theme definitions... */ normal: { bgStart:'#d3d3a4', bgEnd:'#a8a878', text:'#333', statBar:'#a8a878', detail:'#6d6d4e', accent:'#4d4d33', pattern:'rgba(0,0,0,0.1)', tagText:'#333' }, fire: { bgStart:'#feb05a', bgEnd:'#f08030', text:'#fff', statBar:'#f08030', detail:'#c12205', accent:'#9b1a05', pattern:'rgba(255,255,255,0.2)', tagText:'#fff' }, water: { bgStart:'#9ac0ff', bgEnd:'#6890f0', text:'#fff', statBar:'#6890f0', detail:'#0531c7', accent:'#04259e', pattern:'rgba(255,255,255,0.2)', tagText:'#fff' }, electric:{ bgStart:'#ffe880', bgEnd:'#f8d030', text:'#222', statBar:'#f8d030', detail:'#b8a12b', accent:'#8f7e22', pattern:'rgba(0,0,0,0.15)', tagText:'#333' }, grass: { bgStart:'#bfe09f', bgEnd:'#78c850', text:'#fff', statBar:'#78c850', detail:'#4e8234', accent:'#3b6628', pattern:'rgba(255,255,255,0.2)', tagText:'#fff' }, ice: { bgStart:'#cde8e8', bgEnd:'#98d8d8', text:'#222', statBar:'#98d8d8', detail:'#658eb9', accent:'#4f7691', pattern:'rgba(0,0,0,0.15)', tagText:'#333' }, fighting:{ bgStart:'#e06860', bgEnd:'#c03028', text:'#fff', statBar:'#c03028', detail:'#9b221e', accent:'#771a17', pattern:'rgba(255,255,255,0.25)', tagText:'#fff' }, poison: { bgStart:'#c868c8', bgEnd:'#a040a0', text:'#fff', statBar:'#a040a0', detail:'#682a68', accent:'#502050', pattern:'rgba(255,255,255,0.25)', tagText:'#fff' }, ground: { bgStart:'#f8e0a8', bgEnd:'#e0c068', text:'#222', statBar:'#e0c068', detail:'#926c32', accent:'#705426', pattern:'rgba(0,0,0,0.1)', tagText:'#333' }, flying: { bgStart:'#c8b8ff', bgEnd:'#a890f0', text:'#fff', statBar:'#a890f0', detail:'#6d5e91', accent:'#574a71', pattern:'rgba(255,255,255,0.2)', tagText:'#fff' }, psychic: { bgStart:'#ffa0c8', bgEnd:'#f85888', text:'#fff', statBar:'#f85888', detail:'#a13959', accent:'#7e2c45', pattern:'rgba(255,255,255,0.2)', tagText:'#fff' }, bug: { bgStart:'#c8d860', bgEnd:'#a8b820', text:'#fff', statBar:'#a8b820', detail:'#6d7815', accent:'#555d11', pattern:'rgba(255,255,255,0.2)', tagText:'#fff' }, rock: { bgStart:'#d8c868', bgEnd:'#b8a038', text:'#222', statBar:'#b8a038', detail:'#786824', accent:'#5c4f1c', pattern:'rgba(0,0,0,0.15)', tagText:'#333' }, ghost: { bgStart:'#9880b8', bgEnd:'#705898', text:'#fff', statBar:'#705898', detail:'#4c3c62', accent:'#3a2d4a', pattern:'rgba(255,255,255,0.25)', tagText:'#fff' }, dragon: { bgStart:'#9868ff', bgEnd:'#7038f8', text:'#fff', statBar:'#7038f8', detail:'#4c1d95', accent:'#3a1670', pattern:'rgba(255,255,255,0.3)', tagText:'#fff' }, steel: { bgStart:'#d8d8e8', bgEnd:'#b8b8d0', text:'#222', statBar:'#b8b8d0', detail:'#787887', accent:'#5c5c6b', pattern:'rgba(0,0,0,0.15)', tagText:'#333' }, fairy: { bgStart:'#ffc8e0', bgEnd:'#ee99ac', text:'#222', statBar:'#ee99ac', detail:'#9b6470', accent:'#7d4f58', pattern:'rgba(0,0,0,0.1)', tagText:'#333' }, dark: { bgStart:'#988070', bgEnd:'#705848', text:'#fff', statBar:'#705848', detail:'#4c3c30', accent:'#3a2d24', pattern:'rgba(255,255,255,0.25)', tagText:'#fff' } }; let allPokemonData = [], currentPokemonIndex = 0; let mainTransitionTimeline = null, spriteSwapTimeline = null; let isFetchingData = false; // New feature elements const loadingOverlay = document.querySelector('.loading-overlay'); const pokemonContainer = document.querySelector('.pokemon-container'); const searchContainer = document.querySelector('.search-container'); const pokemonDisplay = document.querySelector('.pokemon-display'); const pokemonImage = pokemonDisplay.querySelector('.pokemon-image'); const placeholder = pokemonDisplay.querySelector('.pokemon-image-placeholder'); const actionMessage = document.querySelector('.action-message'); const spriteSelector = document.getElementById('sprite-select'); const pokemonInfo = document.querySelector('.pokemon-info'); const pokemonNameElement = pokemonInfo.querySelector('.pokemon-name'); const pokemonDescription = pokemonInfo.querySelector('.pokemon-description'); const typeTagContainer = pokemonInfo.querySelector('.type-tag-container'); const pokemonDetailsElem = pokemonInfo.querySelector('.pokemon-details'); const pokemonAbilities = pokemonDetailsElem.querySelector('.pokemon-abilities'); const pokemonHeightElem = pokemonDetailsElem.querySelector('.pokemon-height'); const pokemonWeightElem = pokemonDetailsElem.querySelector('.pokemon-weight'); const pokemonStatsElem = pokemonInfo.querySelector('.pokemon-stats'); const statElements = { hp: pokemonStatsElem.querySelector('.stat-hp'), attack: pokemonStatsElem.querySelector('.stat-attack'), defense: pokemonStatsElem.querySelector('.stat-defense'), 'special-attack': pokemonStatsElem.querySelector('.stat-special-attack'), 'special-defense': pokemonStatsElem.querySelector('.stat-special-defense'), speed: pokemonStatsElem.querySelector('.stat-speed') }; const statBarElements = { hp: pokemonStatsElem.querySelector('.hp-bar'), attack: pokemonStatsElem.querySelector('.attack-bar'), defense: pokemonStatsElem.querySelector('.defense-bar'), 'special-attack': pokemonStatsElem.querySelector('.special-attack-bar'), 'special-defense': pokemonStatsElem.querySelector('.special-defense-bar'), speed: pokemonStatsElem.querySelector('.speed-bar') }; const prevButton = document.getElementById('prev-pokemon'); const nextButton = document.getElementById('next-pokemon'); const randomButton = document.getElementById('random-pokemon'); const toggleAutoplayButton= document.getElementById('toggle-autoplay'); const progressIndicator = document.getElementById('progress-indicator'); const searchInput = document.getElementById('search-input'); const searchButton = document.getElementById('search-button'); let autoplayInterval = null; const autoplayDelay = 3000; let isAutoplaying = false; function pad(num, size) { return String(num).padStart(size, '0'); } function updateProgress() { const idx = currentPokemonIndex + 1; const total = allPokemonData.length; progressIndicator.textContent = `#${pad(idx, 3)} / ${pad(total, 3)}`; } function formatSpriteKey(key) { let label = key.replace(/other\./gi, '') .replace(/official-artwork/gi, 'Official Artwork') .replace(/showdown/gi, 'Showdown') .replace(/[._-]/g, ' '); return label.split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ').trim(); } function getTypeTheme(types) { const t = types[0]?.toLowerCase() || 'normal'; return typeThemeMap[t] || typeThemeMap.normal; } function getTypeTagTheme(type) { const theme = typeThemeMap[type.toLowerCase()]; if (theme) { const textColor = theme.tagText || (isLightColor(theme.bgEnd) ? '#333' : '#fff'); return { background: theme.bgEnd, text: textColor }; } return { background: '#ddd', text: '#333' }; } function isLightColor(color) { const d = document.createElement('div'); d.style.color = color; d.style.display = 'none'; document.body.appendChild(d); const rgb = getComputedStyle(d).color; document.body.removeChild(d); const m = rgb.match(/\d+/g); if (!m) return false; const [r,g,b] = m.map(Number).map(v => { v /= 255; return v <= 0.03928 ? v/12.92 : Math.pow((v+0.055)/1.055,2.4); }); const L = 0.2126*r + 0.7152*g + 0.0722*b; return L > 0.4; } function getSpriteUrl(obj, key) { if (!obj || !key) return null; return key.split('.').reduce((cur, part) => cur?.[part] ?? null, obj) || null; } function getAvailableSprites(pokemon) { const arr = []; spriteKeys.forEach(key => { const url = getSpriteUrl(pokemon.sprites, key); if (url) arr.push({key, url}); }); const unique = arr.filter((s,i,self) => self.findIndex(x=>x.url===s.url)===i); if (!unique.length) unique.push({key:'no_sprites',url:null}); return unique; } async function fetchPokemonData() { if (isFetchingData) return; isFetchingData = true; showLoadingOverlay(); try { const list = await fetch(`${POKEMON_API_BASE_URL}pokemon?limit=${POKEMON_LIMIT}`); if (!list.ok) throw new Error('List fetch error'); const { results } = await list.json(); const details = await Promise.all(results.map(async ent => { try { const d = await fetch(ent.url); if (!d.ok) throw new Error(); const detail = await d.json(); const sp = await fetch(detail.species.url); if (!sp.ok) throw new Error(); const species = await sp.json(); const eng = species.flavor_text_entries.find(e=>e.language.name==='en'&&['scarlet','violet','sword','shield','sun','moon','ultra-sun','ultra-moon','x','y'].some(v=>e.version.name.includes(v))) || species.flavor_text_entries.find(e=>e.language.name==='en'); const desc = eng?.flavor_text.replace(/[\n\f]/g,' ')||'No description available.'; const abilities = detail.abilities.map(a=>a.ability.name.replace('-',' ')+(a.is_hidden?' (hidden)':'')); const stats = detail.stats.reduce((a,s)=>(a[s.stat.name.replace('-','_')]=s.base_stat,a),{}); return { id: detail.id, name: detail.name, description: desc, types: detail.types.map(t=>t.type.name), height: detail.height, weight: detail.weight, abilities, sprites: detail.sprites, stats }; } catch { return null; } })); allPokemonData = details.filter(x=>x); if (!allPokemonData.length) throw new Error('No data'); hideLoadingOverlay(); updatePokemonDisplay(); } catch(e) { console.error(e); hideLoadingOverlay(); pokemonContainer.innerHTML = `<p style="color:var(--text-color);font-weight:bold;text-align:center;padding:var(--padding-base);opacity:1;transform:none;"> Error loading Pokemon data.<br>Please check your connection or try again later.</p>`; applyTheme(typeThemeMap.normal); pokemonContainer.classList.add('no-pattern','error-state'); [prevButton,nextButton,randomButton,spriteSelector,toggleAutoplayButton,searchButton].forEach(b=>b.disabled=true); searchInput.disabled = true; gsap.set(spriteSelector.closest('.sprite-selector-container'),{display:'none'}); gsap.set(searchContainer,{display:'none'}); gsap.set([pokemonDisplay,pokemonInfo],{display:'none'}); gsap.set(pokemonContainer,{opacity:1,y:0}); } finally { isFetchingData = false; } } function showLoadingOverlay() { if (loadingOverlay.classList.contains('hidden')||gsap.getTweensOf(loadingOverlay).length){ loadingOverlay.classList.remove('hidden'); gsap.to(loadingOverlay,{opacity:1,duration:0.3,ease:"power2.out"}); } } function hideLoadingOverlay() { gsap.to(loadingOverlay,{ opacity:0,duration:0.6,delay:0.3,ease:"power3.inOut", onComplete:()=>loadingOverlay.classList.add('hidden') }); } function showActionMessage(msg,isErr=false) { gsap.killTweensOf(actionMessage); actionMessage.textContent=msg; gsap.set(actionMessage,{display:'block',opacity:0,y:20,scale:0.8}); const theme = allPokemonData[currentPokemonIndex]?getTypeTheme(allPokemonData[currentPokemonIndex].types):typeThemeMap.normal; const bg = isErr? '#EF5350': theme.accent; const fg = isLightColor(bg)? '#333':'#fff'; gsap.set(actionMessage,{backgroundColor:bg,color:fg}); gsap.timeline() .to(actionMessage,{opacity:1,y:0,scale:1,duration:0.5,ease:"back.out(1.8)",delay:0.1}) .to(actionMessage,{opacity:0,y:-10,duration:0.6,delay:isErr?3:1.5,ease:"power2.in",onComplete:()=>gsap.set(actionMessage,{display:'none'})}); } function showError(msg){ showActionMessage(msg,true) } function applyTheme(theme) { const r=document.documentElement.style; r.setProperty('--pokemon-theme-background-start',theme.bgStart); r.setProperty('--pokemon-theme-background-end',theme.bgEnd); r.setProperty('--pokemon-theme-text',theme.text); r.setProperty('--pokemon-theme-stat-bar',theme.statBar); r.setProperty('--stat-bar-gradient-start',`color-mix(in srgb, ${theme.statBar} 60%, white 40%)`); r.setProperty('--stat-bar-gradient-end',theme.statBar); r.setProperty('--pokemon-theme-detail',theme.detail); r.setProperty('--pokemon-theme-accent',theme.accent); r.setProperty('--pattern-dot-color',theme.pattern); const arrow = encodeURIComponent(isLightColor(theme.accent)?'#333':'#fff'); r.setProperty('--select-arrow-svg',`url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="${arrow}" d="M7 10l5 5 5-5z"/></svg>')`); pokemonContainer.classList.remove('no-pattern','error-state'); } function updatePokemonDisplay() { if (mainTransitionTimeline?.isActive()||!allPokemonData.length) return; // disable controls during transition [prevButton,nextButton,randomButton,toggleAutoplayButton,spriteSelector,searchButton].forEach(b=>b.disabled=true); searchInput.disabled = true; gsap.set([pokemonDisplay,pokemonInfo,spriteSelector.closest('.sprite-selector-container'),searchContainer],{display:''}); const p = allPokemonData[currentPokemonIndex]; if (!p) { showError("Could not display Pokemon data."); [prevButton,nextButton,randomButton,toggleAutoplayButton,searchButton].forEach(b=>b.disabled=false); searchInput.disabled = false; return; } if (mainTransitionTimeline) mainTransitionTimeline.kill(); const td=1.0, sd=0.05; mainTransitionTimeline = gsap.timeline({ paused:true, onComplete:()=>{ [prevButton,nextButton,randomButton,toggleAutoplayButton,spriteSelector,searchButton].forEach(b=>b.disabled=false); searchInput.disabled = false; const av = getAvailableSprites(p); if(av.length>1){ spriteSelector.disabled=false; gsap.set(spriteSelector.closest('.sprite-selector-container'),{display:''}); } else { spriteSelector.disabled=true; gsap.set(spriteSelector.closest('.sprite-selector-container'),{display:'none'}); if(av[0].key==='no_sprites') showActionMessage("No sprites available for this Pokemon."); } } }); mainTransitionTimeline.to([pokemonDisplay,pokemonInfo,spriteSelector.closest('.sprite-selector-container'),searchContainer],{ opacity:0,y:40,duration:td*0.6,ease:"power2.in" },0).to(pokemonDetailsElem, {opacity:0,y:40,duration:td*0.5,ease:"power2.in"},0.1) .add(()=>{ Object.values(statBarElements).forEach(el=>{ gsap.to(el,{width:'0%',duration:td*0.3,ease:"power1.in"},0.2); }); },0) .add(()=>{ pokemonNameElement.textContent = p.name; pokemonDescription.textContent = p.description; typeTagContainer.innerHTML = ''; const theme = getTypeTheme(p.types); applyTheme(theme); p.types.forEach(t=>{ const sp = document.createElement('span'); sp.classList.add('type-tag'); sp.textContent = t; const tt = getTypeTagTheme(t); sp.style.backgroundColor = tt.background; sp.style.color = tt.text; typeTagContainer.appendChild(sp); }); const h = p.height, w = p.weight; if(h!=null){ const m=(h*0.1).toFixed(1), ti=Math.round(h*0.1*39.3701), ft=Math.floor(ti/12), in_=ti%12; pokemonHeightElem.textContent = `${m} m (${ft}' ${in_}")`; } else pokemonHeightElem.textContent='?'; if(w!=null){ const kg=(w*0.1).toFixed(1), lbs=(w*0.1*2.20462).toFixed(1); pokemonWeightElem.textContent = `${kg} kg (${lbs} lbs)`; } else pokemonWeightElem.textContent='?'; pokemonAbilities.innerHTML = ''; if(p.abilities.length) p.abilities.forEach(ab=>{ const li=document.createElement('li'); li.classList.add('ability-item'); li.textContent=ab; pokemonAbilities.appendChild(li); }); else { const li=document.createElement('li'); li.classList.add('ability-item'); li.textContent='None'; pokemonAbilities.appendChild(li); } Object.entries(statElements).forEach(([name,el])=>{ const sk = name.replace('-','_'); el.textContent = p.stats[sk]||0; }); populateSpriteSelector(p); const av = getAvailableSprites(p); const first = av.find(s=>s.url); if(first) { spriteSelector.value=first.key; loadSprite(first.url,p,first.key); } else { showLoadingPlaceholder("No Sprites Found"); loadSprite(null,p,'no_sprites'); } updateProgress(); }, td*0.4) .addLabel("contentIn", td*0.5) .add(()=>{ gsap.to([pokemonDisplay,searchContainer],{opacity:1,y:0,duration:0.7,ease:"power3.out"}); const av = getAvailableSprites(p); if(av.length>1){ gsap.to(spriteSelector.closest('.sprite-selector-container'),{opacity:1,y:0,duration:0.7,ease:"power3.out"},"<+=0.1"); } else { gsap.set(spriteSelector.closest('.sprite-selector-container'),{opacity:0,y:20,display:'none'}); } },"contentIn") .to(pokemonInfo,{opacity:1,y:0,duration:0.9,ease:"power3.out"},"contentIn+=0.2") .to(pokemonDetailsElem,{opacity:1,y:0,duration:0.9,ease:"power3.out"},"contentIn+=0.3") .to(pokemonStatsElem,{opacity:1,y:0,duration:0.9,ease:"power3.out"},"contentIn+=0.4") .add(()=>{ Object.entries(statBarElements).forEach(([name,bar],i)=>{ const sk = name.replace('-','_'); const val = p.stats[sk]||0; const pct = Math.max(0,Math.min(100,(val/MAX_BASE_STAT)*100)); gsap.to(bar,{width:`${pct}%`,duration:1.2,ease:"power2.out",delay:i*sd}); }); },"contentIn+=0.4") .play(); } function populateSpriteSelector(pokemon){ spriteSelector.innerHTML=''; const av=getAvailableSprites(pokemon); if(av.length===1&&av[0].key==='no_sprites'){ const opt=document.createElement('option'); opt.value='no_sprites'; opt.textContent='No Sprites Available'; spriteSelector.appendChild(opt); spriteSelector.disabled=true; gsap.set(spriteSelector.closest('.sprite-selector-container'),{display:'none'}); return; } av.forEach(({key,url})=>{ if(url){ const o=document.createElement('option'); o.value=key; o.textContent=formatSpriteKey(key); spriteSelector.appendChild(o); } }); spriteSelector.disabled=false; gsap.set(spriteSelector.closest('.sprite-selector-container'),{display:''}); } function loadSprite(url,pokemon,key){ if(spriteSwapTimeline?.isActive()) spriteSwapTimeline.kill(); spriteSwapTimeline = gsap.timeline({paused:true}); const current = gsap.getProperty(pokemonImage,'opacity')>0?pokemonImage:placeholder; if(url){ gsap.set(placeholder,{display:'none',opacity:0,ariaHidden:'true'}); gsap.set(pokemonImage,{display:'block',opacity:0,scale:0.9,filter:'blur(5px)',ariaHidden:'false'}); pokemonImage.src=url; pokemonImage.alt=`Sprite of ${pokemon.name} (${formatSpriteKey(key)})`; const imgLoad=new Promise((res,rej)=>{ pokemonImage.onload=res; pokemonImage.onerror=rej; if(pokemonImage.complete&&pokemonImage.naturalHeight>0) res(); }); spriteSwapTimeline.add(async()=>{ try{ await imgLoad; gsap.to(current,{opacity:0,scale:0.9,filter:'blur(5px)',duration:0.4,ease:"power2.in"}); gsap.to(pokemonImage,{opacity:1,scale:1,filter:'blur(0px)',duration:0.7,ease:"power3.out",delay:0.2}); pokemonImage.onload=null; pokemonImage.onerror=null; }catch{ loadSprite(null,pokemon,'error'); pokemonImage.onload=null; pokemonImage.onerror=null; if(spriteSwapTimeline.isActive()) spriteSwapTimeline.progress(1); } }); } else { showLoadingPlaceholder(key==='no_sprites'?"No Sprites Found":"Sprite Failed"); gsap.to(current,{opacity:0,scale:0.9,filter:'blur(5px)',duration:0.4,ease:"power2.in"}); gsap.to(placeholder,{display:'flex',opacity:1,scale:1,filter:'blur(0px)',duration:0.7,ease:"power3.out",delay:0.2}); } spriteSwapTimeline.play(); } function showLoadingPlaceholder(msg='Loading...'){ gsap.set(pokemonImage,{display:'none',opacity:0,ariaHidden:'true'}); gsap.set(placeholder,{display:'flex',opacity:1,scale:1,filter:'blur(0px)',backgroundColor:'#E5E7EB',color:'#6B7280',ariaHidden:'false'}); placeholder.textContent=msg; } // Autoplay controls function startAutoplay() { if (isAutoplaying) return; isAutoplaying = true; toggleAutoplayButton.textContent = 'Pause'; autoplayInterval = setInterval(showNextPokemon, autoplayDelay); showActionMessage('Autoplay started'); } function stopAutoplay() { if (!isAutoplaying) return; isAutoplaying = false; toggleAutoplayButton.textContent = 'Play'; clearInterval(autoplayInterval); showActionMessage('Autoplay paused'); } function toggleAutoplay() { if (isAutoplaying) stopAutoplay(); else startAutoplay(); } // Search feature function handleSearch() { const q = searchInput.value.trim().toLowerCase(); if (!q) { showActionMessage('Enter a name or number'); return; } let idx = -1; if (/^\d+$/.test(q)) { const num = parseInt(q, 10); idx = allPokemonData.findIndex(p => p.id === num); } else { idx = allPokemonData.findIndex(p => p.name.toLowerCase() === q); } if (idx >= 0) { currentPokemonIndex = idx; updatePokemonDisplay(); } else { showError('Pokemon not found'); } } function showNextPokemon(){ if(mainTransitionTimeline?.isActive()||allPokemonData.length<=1) return; currentPokemonIndex=(currentPokemonIndex+1)%allPokemonData.length; updatePokemonDisplay(); } function showPrevPokemon(){ if(mainTransitionTimeline?.isActive()||allPokemonData.length<=1) return; currentPokemonIndex=(currentPokemonIndex-1+allPokemonData.length)%allPokemonData.length; updatePokemonDisplay(); } function showRandomPokemon(){ if(mainTransitionTimeline?.isActive()||allPokemonData.length<=1) return; let idx; do{ idx=Math.floor(Math.random()*allPokemonData.length); } while(idx===currentPokemonIndex && allPokemonData.length>1); currentPokemonIndex=idx; updatePokemonDisplay(); } prevButton.addEventListener('click', showPrevPokemon); nextButton.addEventListener('click', showNextPokemon); randomButton.addEventListener('click', showRandomPokemon); toggleAutoplayButton.addEventListener('click', toggleAutoplay); searchButton.addEventListener('click', handleSearch); searchInput.addEventListener('keydown', e=>{ if(e.key==='Enter') handleSearch(); }); spriteSelector.addEventListener('change', e=>{ const key=e.target.value; const p=allPokemonData[currentPokemonIndex]; const sel=getAvailableSprites(p).find(s=>s.key===key); if(sel?.url){ loadSprite(sel.url,p,sel.key); showActionMessage(`Switched to ${formatSpriteKey(key)}`); } else if(key==='no_sprites'){ loadSprite(null,p,'no_sprites'); showActionMessage("No sprites available for this Pokemon."); } else { loadSprite(null,p,'error'); showError("Could not load selected sprite."); } }); document.addEventListener('keydown', e=>{ if(e.target.tagName==='INPUT'||e.target.tagName==='SELECT') return; if(e.key==='ArrowLeft') showPrevPokemon(); if(e.key==='ArrowRight') showNextPokemon(); if(e.key.toLowerCase()==='r') showRandomPokemon(); if(e.key.toLowerCase()==='p') toggleAutoplay(); }); document.addEventListener('DOMContentLoaded',()=>{ gsap.fromTo(pokemonContainer,{opacity:0,y:60},{opacity:1,y:0,duration:1.2,ease:"power3.out",delay:0.6}); fetchPokemonData(); }); })(); </script> </body> </html>