Files
pokemon/src/pages/404.astro

179 lines
5.7 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
export const prerender = false;
import Layout from '../layouts/Main.astro';
import Search from '../components/Search.astro';
import NavBar from '../components/NavBar.astro';
import Footer from '../components/Footer.astro';
import pokedexList from '../data/pokedex.json';
// Get random # (00011025)
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);
// If not found (rare), fallback
const pokemonName = pokemon?.Name || "Unknown Pokémon";
---
<Layout title="404 - Page Not Found">
<div class="container-fluid container-sm mt-4" slot="page">
<div class="row mb-4">
<div class="col-12 col-md-6">
<h1 class="mb-4">404 - Page Not Found</h1>
<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>
</div>
<div class="col-12 col-md-5 offset-md-1">
<div id="reveal-hint" class="alert alert-warning border p-2" role="alert">
<h4 class="alert-heading">Who's that Pokémon?</h4>
<p class="mb-0">Click the image to reveal.</p>
</div>
<div class="p-0 ratio ratio-1x1 position-relative overflow-hidden d-flex justify-items-center">
<img class="whos-that-pokemon position-absolute h-100" src="/404/lines.gif" alt="" />
<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="" />
<img
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"
src={pokedexImage}
alt=""
data-name={pokemonName}
role="button"
tabindex="0"
draggable="false"
aria-label="Reveal the Pokémon"
/>
</div>
</div>
</div>
<!-- Pokémon name reveal -->
<div class="col-12 text-center mt-3">
<h3
id="pokemon-name"
class="opacity-0 pokemon-transition"
aria-live="polite"
aria-atomic="true"
>???</h3>
<button
id="play-again"
class="btn btn-purple mt-3 opacity-0 pokemon-transition"
style="pointer-events: none;"
aria-hidden="true"
>
Guess another Pokémon
</button>
</div>
</div>
</div>
</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>
<script>
const img = document.querySelector('.masked-image') as HTMLImageElement | null;
const nameEl = document.querySelector('#pokemon-name');
const playAgainBtn = document.querySelector('#play-again') as HTMLButtonElement | null;
const hintEl = document.querySelector('#reveal-hint');
function revealPokemon() {
if (!img || !nameEl) return;
const doReveal = () => {
// 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';
nameEl.classList.remove('opacity-0');
// 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
}
};
if (!document.startViewTransition) {
doReveal();
return;
}
img.style.viewTransitionName = 'pokemon-reveal';
document.startViewTransition(() => {
doReveal();
}).finished.then(() => {
img.style.viewTransitionName = '';
});
}
img?.addEventListener('click', revealPokemon);
img?.addEventListener('keydown', (e: KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
revealPokemon();
}
});
playAgainBtn?.addEventListener('click', () => {
window.location.reload();
});
</script>