mirror of
https://github.com/acedanger/pokemon.git
synced 2025-12-05 14:40:14 -08:00
chore: update dependencies and add new features
- Added Font Awesome and Annyang for enhanced UI and voice recognition capabilities. - Updated package.json to include new dependencies: @fortawesome/fontawesome-free, annyang, and wrangler. - Modified postcss.config.js for proper syntax. - Updated style.css to include Font Awesome styles and added new styles for voice search button and footer. - Adjusted tailwind.config.js to scan all relevant files for dynamic classes. - Added VSCode settings to ignore unknown at-rules in CSS, SCSS, and LESS. - Created a Caddyfile for server configuration with basic settings.
This commit is contained in:
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"css.lint.unknownAtRules": "ignore",
|
||||
"scss.lint.unknownAtRules": "ignore",
|
||||
"less.lint.unknownAtRules": "ignore"
|
||||
}
|
||||
8
Caddyfile
Normal file
8
Caddyfile
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
auto_https off
|
||||
}
|
||||
|
||||
:80 {
|
||||
root * /srv
|
||||
file_server
|
||||
}
|
||||
21
Dockerfile
21
Dockerfile
@@ -1,11 +1,10 @@
|
||||
# ---- Build Stage ----
|
||||
FROM node:20-alpine AS build
|
||||
FROM node:23-slim AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files and install dependencies
|
||||
# Copy package.json AND package-lock.json (if available)
|
||||
COPY package*.json ./
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install
|
||||
|
||||
# Copy the rest of the application code
|
||||
@@ -15,16 +14,12 @@ COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# ---- Serve Stage ----
|
||||
FROM nginx:stable-alpine
|
||||
FROM caddy:2.10.0-alpine
|
||||
|
||||
WORKDIR /srv
|
||||
|
||||
# Copy built assets from the build stage
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY --from=build /app/dist /srv
|
||||
|
||||
# Optional: Copy a custom Nginx configuration if needed
|
||||
# COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Expose port 80
|
||||
EXPOSE 80
|
||||
|
||||
# Start Nginx in the foreground
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
# Copy Caddy configuration file
|
||||
COPY Caddyfile /etc/caddy/Caddyfile
|
||||
66
index.html
66
index.html
@@ -19,31 +19,22 @@
|
||||
<div class="flex flex-col sm:flex-row gap-4 mb-6">
|
||||
<!-- Wrap input and voice button -->
|
||||
<div class="relative flex-grow">
|
||||
<!-- Search Icon -->
|
||||
<i
|
||||
class="fas fa-search absolute inset-y-0 left-3 flex items-center text-gray-500"
|
||||
></i>
|
||||
<input
|
||||
type="text"
|
||||
id="pokemonName"
|
||||
placeholder="Enter Pokémon name or use mic"
|
||||
class="w-full px-4 py-2 pr-10 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-200"
|
||||
placeholder="Enter Pokémon name"
|
||||
class="w-full pl-10 pr-10 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-200"
|
||||
/>
|
||||
<!-- Microphone Button -->
|
||||
<!-- Microphone Icon -->
|
||||
<button
|
||||
id="voiceSearchButton"
|
||||
class="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-500 hover:text-blue-600 focus:outline-none"
|
||||
class="absolute inset-y-0 right-3 flex items-center text-gray-500 hover:text-blue-600 focus:outline-none"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="w-5 h-5"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 18.75a6 6 0 0 0 6-6v-1.5m-6 7.5a6 6 0 0 1-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 0 1-3-3V4.5a3 3 0 1 1-6 0v8.25a3 3 0 0 1-3 3Z"
|
||||
/>
|
||||
</svg>
|
||||
<i class="fas fa-microphone"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
@@ -53,21 +44,44 @@
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
<div id="pokemonInfo" class="mt-6 text-center text-gray-700">
|
||||
<!-- Pokémon info will be displayed here -->
|
||||
</div>
|
||||
<div id="pokemonInfo" class="mt-6 text-center text-gray-700"></div>
|
||||
|
||||
<!-- History Section - Removed h3 and border -->
|
||||
<!-- History Section -->
|
||||
<div id="searchHistory" class="mt-8 pt-4">
|
||||
<ul id="historyList" class="list-none space-y-4">
|
||||
<!-- History items (full cards) will be added here -->
|
||||
<li class="italic text-sm text-center text-gray-600">
|
||||
No history yet.
|
||||
</li>
|
||||
<li class="italic text-sm text-center text-gray-600"></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Voice Overlay -->
|
||||
<div
|
||||
id="voiceOverlay"
|
||||
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden"
|
||||
>
|
||||
<i class="fas fa-microphone text-white text-6xl"></i>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer
|
||||
class="mt-8 text-center text-gray-600 flex items-center justify-center gap-4"
|
||||
>
|
||||
<p>Made with ❤️ by Daddy.</p>
|
||||
<a
|
||||
href="https://github.com/acedanger/pokemon"
|
||||
target="_blank"
|
||||
class="text-blue-600 hover:text-blue-800"
|
||||
>
|
||||
<i class="fab fa-github w-6 h-6"></i>
|
||||
</a>
|
||||
<button
|
||||
id="themeToggleButton"
|
||||
class="flex items-center justify-center text-gray-600 hover:text-gray-800 focus:outline-none"
|
||||
>
|
||||
<i id="themeIcon" class="fas fa-moon w-6 h-6"></i>
|
||||
</button>
|
||||
</footer>
|
||||
|
||||
<script type="module" src="/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
87
index.js
87
index.js
@@ -1,5 +1,6 @@
|
||||
import "/style.css"; // Ensure CSS is imported
|
||||
import "/style.css";
|
||||
import Pokedex from "pokedex-promise-v2";
|
||||
import annyang from "annyang";
|
||||
|
||||
const options = {
|
||||
protocol: "https",
|
||||
@@ -16,6 +17,8 @@ const pokemonNameInput = document.getElementById("pokemonName");
|
||||
const pokemonInfoDiv = document.getElementById("pokemonInfo");
|
||||
const voiceSearchButton = document.getElementById("voiceSearchButton");
|
||||
const historyList = document.getElementById("historyList");
|
||||
const themeToggleButton = document.getElementById("themeToggleButton");
|
||||
const themeIcon = document.getElementById("themeIcon");
|
||||
|
||||
// --- State ---
|
||||
let searchHistory = []; // Now stores full data objects
|
||||
@@ -36,8 +39,8 @@ const updateHistoryDisplay = (justAddedName = null) => {
|
||||
|
||||
// Determine scale based on index
|
||||
let scale = 1.0; // Default scale for the newest item
|
||||
if (index === 1) scale = 0.8;
|
||||
else if (index === 2) scale = 0.6;
|
||||
if (index === 1) scale = 0.9;
|
||||
else if (index === 2) scale = 0.8;
|
||||
|
||||
// Apply scale via inline style
|
||||
li.style.transform = `scale(${scale})`;
|
||||
@@ -137,6 +140,34 @@ const searchPokemon = () => {
|
||||
pokemon.getPokemonByName(name).then(displayPokemonInfo).catch(displayError);
|
||||
};
|
||||
|
||||
// Theme toggle logic
|
||||
// Removed references to themeText as the text is no longer part of the DOM
|
||||
const setTheme = (theme) => {
|
||||
if (theme === "dark") {
|
||||
document.documentElement.classList.add("dark");
|
||||
themeIcon.classList.remove("fa-moon");
|
||||
themeIcon.classList.add("fa-sun");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
themeIcon.classList.remove("fa-sun");
|
||||
themeIcon.classList.add("fa-moon");
|
||||
}
|
||||
localStorage.setItem("theme", theme);
|
||||
};
|
||||
|
||||
// Initialize theme based on device or saved preference
|
||||
const savedTheme = localStorage.getItem("theme");
|
||||
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
setTheme(savedTheme || (prefersDark ? "dark" : "light"));
|
||||
|
||||
// Toggle theme on button click
|
||||
themeToggleButton.addEventListener("click", () => {
|
||||
const currentTheme = document.documentElement.classList.contains("dark")
|
||||
? "dark"
|
||||
: "light";
|
||||
setTheme(currentTheme === "dark" ? "light" : "dark");
|
||||
});
|
||||
|
||||
// --- Event Listeners & Voice Search ---
|
||||
searchButton.addEventListener("click", searchPokemon);
|
||||
|
||||
@@ -158,6 +189,14 @@ if (SpeechRecognition) {
|
||||
recognition.maxAlternatives = 1;
|
||||
|
||||
voiceSearchButton.addEventListener("click", () => {
|
||||
const voiceOverlay = document.getElementById("voiceOverlay");
|
||||
voiceOverlay.classList.remove("hidden");
|
||||
|
||||
// Simulate listening for 3 seconds, then hide the overlay
|
||||
setTimeout(() => {
|
||||
voiceOverlay.classList.add("hidden");
|
||||
}, 3000);
|
||||
|
||||
try {
|
||||
pokemonNameInput.placeholder = "Listening...";
|
||||
recognition.start();
|
||||
@@ -192,8 +231,48 @@ if (SpeechRecognition) {
|
||||
pokemonNameInput.placeholder = "Enter Pokémon name or use mic";
|
||||
voiceSearchButton.classList.remove("text-red-500");
|
||||
};
|
||||
} else if (annyang) {
|
||||
console.warn("Using annyang as a fallback for voice recognition.");
|
||||
|
||||
annyang.addCommands({
|
||||
"*pokemonName": (pokemonName) => {
|
||||
pokemonNameInput.value = pokemonName.toLowerCase();
|
||||
searchPokemon();
|
||||
},
|
||||
});
|
||||
|
||||
voiceSearchButton.addEventListener("click", () => {
|
||||
const voiceOverlay = document.getElementById("voiceOverlay");
|
||||
voiceOverlay.classList.remove("hidden");
|
||||
|
||||
// Simulate listening for 3 seconds, then hide the overlay
|
||||
setTimeout(() => {
|
||||
voiceOverlay.classList.add("hidden");
|
||||
}, 3000);
|
||||
|
||||
try {
|
||||
pokemonNameInput.placeholder = "Listening...";
|
||||
annyang.start();
|
||||
voiceSearchButton.classList.add("text-red-500");
|
||||
} catch (e) {
|
||||
console.error("Annyang already started.", e);
|
||||
pokemonNameInput.placeholder = "Enter Pokémon name or use mic";
|
||||
}
|
||||
});
|
||||
|
||||
annyang.addCallback("end", () => {
|
||||
pokemonNameInput.placeholder = "Enter Pokémon name or use mic";
|
||||
voiceSearchButton.classList.remove("text-red-500");
|
||||
});
|
||||
|
||||
annyang.addCallback("error", (error) => {
|
||||
pokemonInfoDiv.innerHTML = `<p class="text-red-500">Voice recognition error: ${error}</p>`;
|
||||
console.error("Annyang error:", error);
|
||||
pokemonNameInput.placeholder = "Enter Pokémon name or use mic";
|
||||
voiceSearchButton.classList.remove("text-red-500");
|
||||
});
|
||||
} else {
|
||||
console.warn("Web Speech API not supported in this browser.");
|
||||
console.warn("Web Speech API and annyang not supported in this browser.");
|
||||
voiceSearchButton.style.display = "none";
|
||||
}
|
||||
|
||||
|
||||
991
package-lock.json
generated
991
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,10 @@
|
||||
"author": "Peter Wood <peter@peterwood.dev>",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"pokedex-promise-v2": "^4.2.1"
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"annyang": "^2.6.1",
|
||||
"pokedex-promise-v2": "^4.2.1",
|
||||
"wrangler": "^4.12.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.52.0",
|
||||
@@ -28,4 +31,4 @@
|
||||
"vite": "^6.3.2",
|
||||
"vite-plugin-node-polyfills": "^0.23.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,4 @@ export default {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
38
style.css
38
style.css
@@ -1,3 +1,4 @@
|
||||
@import "/node_modules/@fortawesome/fontawesome-free/css/all.min.css";
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -39,3 +40,40 @@
|
||||
body {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.relative.flex-grow {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#voiceSearchButton {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0.75rem; /* Matches pr-3 */
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center; /* Ensure proper centering */
|
||||
height: 100%; /* Match the input field height */
|
||||
color: #6b7280; /* Matches text-gray-500 */
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
#voiceSearchButton:hover {
|
||||
color: #2563eb; /* Matches hover:text-blue-600 */
|
||||
}
|
||||
|
||||
footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html", // Scan index.html
|
||||
"./index.js", // Scan index.js for potential dynamic classes (optional but good practice)
|
||||
],
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user