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 ----
|
# ---- Build Stage ----
|
||||||
FROM node:20-alpine AS build
|
FROM node:23-slim AS build
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy package files and install dependencies
|
# Copy package files and install dependencies
|
||||||
# Copy package.json AND package-lock.json (if available)
|
COPY package.json package-lock.json ./
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
# Copy the rest of the application code
|
# Copy the rest of the application code
|
||||||
@@ -15,16 +14,12 @@ COPY . .
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# ---- Serve Stage ----
|
# ---- Serve Stage ----
|
||||||
FROM nginx:stable-alpine
|
FROM caddy:2.10.0-alpine
|
||||||
|
|
||||||
|
WORKDIR /srv
|
||||||
|
|
||||||
# Copy built assets from the build stage
|
# 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 Caddy configuration file
|
||||||
# COPY nginx.conf /etc/nginx/conf.d/default.conf
|
COPY Caddyfile /etc/caddy/Caddyfile
|
||||||
|
|
||||||
# Expose port 80
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
# Start Nginx in the foreground
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
|
||||||
66
index.html
66
index.html
@@ -19,31 +19,22 @@
|
|||||||
<div class="flex flex-col sm:flex-row gap-4 mb-6">
|
<div class="flex flex-col sm:flex-row gap-4 mb-6">
|
||||||
<!-- Wrap input and voice button -->
|
<!-- Wrap input and voice button -->
|
||||||
<div class="relative flex-grow">
|
<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
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="pokemonName"
|
id="pokemonName"
|
||||||
placeholder="Enter Pokémon name or use mic"
|
placeholder="Enter Pokémon name"
|
||||||
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"
|
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
|
<button
|
||||||
id="voiceSearchButton"
|
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
|
<i class="fas fa-microphone"></i>
|
||||||
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>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -53,21 +44,44 @@
|
|||||||
Search
|
Search
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="pokemonInfo" class="mt-6 text-center text-gray-700">
|
<div id="pokemonInfo" class="mt-6 text-center text-gray-700"></div>
|
||||||
<!-- Pokémon info will be displayed here -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- History Section - Removed h3 and border -->
|
<!-- History Section -->
|
||||||
<div id="searchHistory" class="mt-8 pt-4">
|
<div id="searchHistory" class="mt-8 pt-4">
|
||||||
<ul id="historyList" class="list-none space-y-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"></li>
|
||||||
<li class="italic text-sm text-center text-gray-600">
|
|
||||||
No history yet.
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<script type="module" src="/index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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 Pokedex from "pokedex-promise-v2";
|
||||||
|
import annyang from "annyang";
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
protocol: "https",
|
protocol: "https",
|
||||||
@@ -16,6 +17,8 @@ const pokemonNameInput = document.getElementById("pokemonName");
|
|||||||
const pokemonInfoDiv = document.getElementById("pokemonInfo");
|
const pokemonInfoDiv = document.getElementById("pokemonInfo");
|
||||||
const voiceSearchButton = document.getElementById("voiceSearchButton");
|
const voiceSearchButton = document.getElementById("voiceSearchButton");
|
||||||
const historyList = document.getElementById("historyList");
|
const historyList = document.getElementById("historyList");
|
||||||
|
const themeToggleButton = document.getElementById("themeToggleButton");
|
||||||
|
const themeIcon = document.getElementById("themeIcon");
|
||||||
|
|
||||||
// --- State ---
|
// --- State ---
|
||||||
let searchHistory = []; // Now stores full data objects
|
let searchHistory = []; // Now stores full data objects
|
||||||
@@ -36,8 +39,8 @@ const updateHistoryDisplay = (justAddedName = null) => {
|
|||||||
|
|
||||||
// Determine scale based on index
|
// Determine scale based on index
|
||||||
let scale = 1.0; // Default scale for the newest item
|
let scale = 1.0; // Default scale for the newest item
|
||||||
if (index === 1) scale = 0.8;
|
if (index === 1) scale = 0.9;
|
||||||
else if (index === 2) scale = 0.6;
|
else if (index === 2) scale = 0.8;
|
||||||
|
|
||||||
// Apply scale via inline style
|
// Apply scale via inline style
|
||||||
li.style.transform = `scale(${scale})`;
|
li.style.transform = `scale(${scale})`;
|
||||||
@@ -137,6 +140,34 @@ const searchPokemon = () => {
|
|||||||
pokemon.getPokemonByName(name).then(displayPokemonInfo).catch(displayError);
|
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 ---
|
// --- Event Listeners & Voice Search ---
|
||||||
searchButton.addEventListener("click", searchPokemon);
|
searchButton.addEventListener("click", searchPokemon);
|
||||||
|
|
||||||
@@ -158,6 +189,14 @@ if (SpeechRecognition) {
|
|||||||
recognition.maxAlternatives = 1;
|
recognition.maxAlternatives = 1;
|
||||||
|
|
||||||
voiceSearchButton.addEventListener("click", () => {
|
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 {
|
try {
|
||||||
pokemonNameInput.placeholder = "Listening...";
|
pokemonNameInput.placeholder = "Listening...";
|
||||||
recognition.start();
|
recognition.start();
|
||||||
@@ -192,8 +231,48 @@ if (SpeechRecognition) {
|
|||||||
pokemonNameInput.placeholder = "Enter Pokémon name or use mic";
|
pokemonNameInput.placeholder = "Enter Pokémon name or use mic";
|
||||||
voiceSearchButton.classList.remove("text-red-500");
|
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 {
|
} 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";
|
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>",
|
"author": "Peter Wood <peter@peterwood.dev>",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"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": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.52.0",
|
"@playwright/test": "^1.52.0",
|
||||||
@@ -28,4 +31,4 @@
|
|||||||
"vite": "^6.3.2",
|
"vite": "^6.3.2",
|
||||||
"vite-plugin-node-polyfills": "^0.23.0"
|
"vite-plugin-node-polyfills": "^0.23.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,4 +3,4 @@ export default {
|
|||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
38
style.css
38
style.css
@@ -1,3 +1,4 @@
|
|||||||
|
@import "/node_modules/@fortawesome/fontawesome-free/css/all.min.css";
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
@@ -39,3 +40,40 @@
|
|||||||
body {
|
body {
|
||||||
min-height: 100vh;
|
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} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
content: [
|
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||||
"./index.html", // Scan index.html
|
|
||||||
"./index.js", // Scan index.js for potential dynamic classes (optional but good practice)
|
|
||||||
],
|
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user