2026-02-21 16:26:34 -05:00
|
|
|
|
---
|
2026-03-11 15:21:43 -04:00
|
|
|
|
export const prerender = false;
|
2026-02-21 16:26:34 -05:00
|
|
|
|
import Layout from '../layouts/Main.astro';
|
2026-02-26 18:08:08 -05:00
|
|
|
|
import NavItems from '../components/NavItems.astro';
|
|
|
|
|
|
import NavBar from '../components/NavBar.astro';
|
2026-02-27 15:20:55 -05:00
|
|
|
|
import Footer from '../components/Footer.astro';
|
2026-03-11 15:21:43 -04:00
|
|
|
|
import pokedexList from '../data/pokedex.json';
|
2026-02-21 16:26:34 -05:00
|
|
|
|
|
2026-02-26 18:08:08 -05:00
|
|
|
|
// Get random # (0001–1025)
|
|
|
|
|
|
const randomNumber = String(Math.floor(Math.random() * 1025) + 1).padStart(4, "0");
|
|
|
|
|
|
|
|
|
|
|
|
// Image path
|
|
|
|
|
|
const pokedexImage = `/404/pokedex/${randomNumber}.png`;
|
|
|
|
|
|
|
|
|
|
|
|
// Find Pokémon from JSON
|
|
|
|
|
|
const pokemon = pokedexList.find(p => p["#"] === randomNumber);
|
2026-02-21 16:26:34 -05:00
|
|
|
|
|
2026-02-26 18:08:08 -05:00
|
|
|
|
// If not found (rare), fallback
|
|
|
|
|
|
const pokemonName = pokemon?.Name || "Unknown Pokémon";
|
|
|
|
|
|
---
|
2026-03-11 15:21:43 -04:00
|
|
|
|
<Layout title="404 - Page Not Found">
|
2026-02-26 18:08:08 -05:00
|
|
|
|
<NavBar slot="navbar">
|
|
|
|
|
|
<NavItems slot="navItems" />
|
|
|
|
|
|
</NavBar>
|
|
|
|
|
|
<div class="row mb-4" slot="page">
|
|
|
|
|
|
<div class="col-12 col-md-6">
|
2026-03-11 15:21:43 -04:00
|
|
|
|
<h1 class="mb-4">404<br/>Page Not Found</h1>
|
2026-02-26 18:08:08 -05:00
|
|
|
|
<h4>Sorry, the page you are looking for does not exist.</h4>
|
|
|
|
|
|
<p class="copy-big my-4">
|
|
|
|
|
|
Return to the <a href="/">home page</a> or search for another <a href="/pokemon">Pokémon</a>.
|
|
|
|
|
|
</p>
|
2026-02-21 16:26:34 -05:00
|
|
|
|
</div>
|
2026-03-03 13:36:11 -05:00
|
|
|
|
<div class="col-12 col-md-5 offset-md-1">
|
2026-03-12 13:40:12 -04:00
|
|
|
|
<div id="reveal-hint" class="alert alert-warning border p-2" role="alert">
|
2026-02-22 10:53:30 -05:00
|
|
|
|
<h4 class="alert-heading">Who's that Pokémon?</h4>
|
2026-02-26 18:08:08 -05:00
|
|
|
|
<p class="mb-0">Click the image to reveal.</p>
|
2026-02-23 07:53:57 -05:00
|
|
|
|
</div>
|
2026-02-26 18:08:08 -05:00
|
|
|
|
|
2026-03-02 14:09:59 -05:00
|
|
|
|
<div class="p-0 ratio ratio-1x1 position-relative overflow-hidden d-flex justify-items-center">
|
2026-03-11 15:21:43 -04:00
|
|
|
|
<img class="whos-that-pokemon position-absolute h-100" src="/404/lines.gif" alt="" />
|
2026-02-26 18:08:08 -05:00
|
|
|
|
|
2026-03-11 15:21:43 -04:00
|
|
|
|
<div class="d-flex flex-column-reverse flex-lg-row">
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<img class="w-100 starburst top-0 bottom-0 left-0 right-0" src="/404/glow.png" alt="" />
|
2026-02-26 18:08:08 -05:00
|
|
|
|
|
2026-03-11 15:21:43 -04:00
|
|
|
|
<img
|
2026-03-12 13:40:12 -04:00
|
|
|
|
class="m-auto position-absolute w-75 top-0 left-25 bottom-10 right-0 d-block img-fluid masked-image top-50 start-50 translate-middle pokemon-clickable"
|
2026-03-11 15:21:43 -04:00
|
|
|
|
src={pokedexImage}
|
2026-03-12 13:40:12 -04:00
|
|
|
|
alt=""
|
2026-03-11 15:21:43 -04:00
|
|
|
|
data-name={pokemonName}
|
|
|
|
|
|
role="button"
|
|
|
|
|
|
tabindex="0"
|
2026-03-12 13:40:12 -04:00
|
|
|
|
draggable="false"
|
|
|
|
|
|
aria-label="Reveal the Pokémon"
|
2026-03-11 15:21:43 -04:00
|
|
|
|
/>
|
2026-02-23 07:53:57 -05:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-02-26 18:08:08 -05:00
|
|
|
|
|
|
|
|
|
|
<!-- Pokémon name reveal -->
|
|
|
|
|
|
<div class="col-12 text-center mt-3">
|
2026-03-12 13:40:12 -04:00
|
|
|
|
<h3
|
|
|
|
|
|
id="pokemon-name"
|
|
|
|
|
|
class="opacity-0 pokemon-transition"
|
|
|
|
|
|
aria-live="polite"
|
|
|
|
|
|
aria-atomic="true"
|
|
|
|
|
|
>???</h3>
|
|
|
|
|
|
<button
|
|
|
|
|
|
id="play-again"
|
|
|
|
|
|
class="btn btn-primary mt-3 opacity-0 pokemon-transition"
|
|
|
|
|
|
style="pointer-events: none;"
|
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
|
>
|
|
|
|
|
|
Guess another Pokémon
|
|
|
|
|
|
</button>
|
2026-02-22 10:53:30 -05:00
|
|
|
|
</div>
|
2026-02-21 16:26:34 -05:00
|
|
|
|
</div>
|
2026-03-12 13:40:12 -04:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<Footer slot="footer" />
|
|
|
|
|
|
</Layout>
|
|
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
|
.pokemon-transition {
|
|
|
|
|
|
transition: opacity 0.4s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pokemon-clickable {
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pokemon-clickable:focus-visible {
|
|
|
|
|
|
outline: 3px solid #ffc107;
|
|
|
|
|
|
outline-offset: 4px;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes pokemon-pulse {
|
|
|
|
|
|
0%, 100% { filter: brightness(0) drop-shadow(0 0 6px var(--bs-info-border-subtle)); }
|
|
|
|
|
|
50% { filter: brightness(0) drop-shadow(0 0 18px var(--bs-info)); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.masked-image {
|
|
|
|
|
|
filter: brightness(0);
|
|
|
|
|
|
animation: pokemon-pulse 2s ease-in-out infinite;
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
|
2026-03-11 15:21:43 -04:00
|
|
|
|
<script>
|
|
|
|
|
|
const img = document.querySelector('.masked-image') as HTMLImageElement | null;
|
|
|
|
|
|
const nameEl = document.querySelector('#pokemon-name');
|
2026-03-12 13:40:12 -04:00
|
|
|
|
const playAgainBtn = document.querySelector('#play-again') as HTMLButtonElement | null;
|
|
|
|
|
|
const hintEl = document.querySelector('#reveal-hint');
|
2026-03-11 15:21:43 -04:00
|
|
|
|
|
|
|
|
|
|
function revealPokemon() {
|
|
|
|
|
|
if (!img || !nameEl) return;
|
|
|
|
|
|
|
|
|
|
|
|
const doReveal = () => {
|
2026-03-12 13:40:12 -04:00
|
|
|
|
// Remove masked styles and interactivity from image
|
|
|
|
|
|
img.classList.remove('masked-image', 'pokemon-clickable');
|
|
|
|
|
|
img.removeAttribute('role');
|
|
|
|
|
|
img.removeAttribute('tabindex');
|
|
|
|
|
|
img.removeAttribute('aria-label');
|
|
|
|
|
|
img.style.animation = '';
|
|
|
|
|
|
|
|
|
|
|
|
// Update alt text now that it's revealed
|
|
|
|
|
|
img.alt = img.dataset.name || 'Unknown Pokémon';
|
|
|
|
|
|
|
|
|
|
|
|
// Reveal name
|
|
|
|
|
|
nameEl.textContent = img.dataset.name || 'Unknown Pokémon';
|
2026-03-11 15:21:43 -04:00
|
|
|
|
nameEl.classList.remove('opacity-0');
|
2026-03-12 13:40:12 -04:00
|
|
|
|
|
|
|
|
|
|
// Update hint text
|
|
|
|
|
|
if (hintEl) {
|
|
|
|
|
|
hintEl.querySelector('p')!.textContent = "It's " + (img.dataset.name || 'Unknown Pokémon') + "!";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Show play again button
|
|
|
|
|
|
if (playAgainBtn) {
|
|
|
|
|
|
playAgainBtn.classList.remove('opacity-0');
|
|
|
|
|
|
playAgainBtn.style.pointerEvents = '';
|
|
|
|
|
|
playAgainBtn.removeAttribute('aria-hidden');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Fire analytics safely
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (typeof dataLayer !== 'undefined') {
|
|
|
|
|
|
dataLayer.push({ event: '404reveal', pokemonName: img.dataset.name });
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
// Analytics unavailable, continue silently
|
|
|
|
|
|
}
|
2026-03-11 15:21:43 -04:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (!document.startViewTransition) {
|
|
|
|
|
|
doReveal();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
img.style.viewTransitionName = 'pokemon-reveal';
|
|
|
|
|
|
|
|
|
|
|
|
document.startViewTransition(() => {
|
|
|
|
|
|
doReveal();
|
|
|
|
|
|
}).finished.then(() => {
|
|
|
|
|
|
img.style.viewTransitionName = '';
|
2026-02-22 10:53:30 -05:00
|
|
|
|
});
|
2026-03-11 15:21:43 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
img?.addEventListener('click', revealPokemon);
|
|
|
|
|
|
img?.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
|
|
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
revealPokemon();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-03-12 13:40:12 -04:00
|
|
|
|
playAgainBtn?.addEventListener('click', () => {
|
|
|
|
|
|
window.location.reload();
|
|
|
|
|
|
});
|
|
|
|
|
|
</script>
|