diff --git a/joke-generator/README.md b/joke-generator/README.md index c105083..29259f4 100644 --- a/joke-generator/README.md +++ b/joke-generator/README.md @@ -1,8 +1,29 @@ -# š Random Joke Generator +# š Enhanced Random Joke Generator -A complete, production-ready joke generator that fetches random jokes from an external API (JokeAPI). Includes implementations in Python and JavaScript with a beautiful web UI. +A complete, production-ready joke generator with **Favorites**, **Dark Mode**, and **Copy to Clipboard** features! Includes implementations in Python and JavaScript with a beautiful responsive web UI. -## š Features +## ⨠New Features Added + +### š Dark Mode +- Toggle between light and dark themes +- Preference saved to browser (localStorage) +- Beautiful color transitions +- Fully responsive design + +### ā Favorites System +- Save jokes you love +- View all favorite jokes in a modal +- Manage favorites (copy, delete) +- Favorites persist in local storage (web) or JSON file (Python) +- Visual indicator for favorited jokes + +### š Copy to Clipboard +- One-click copy jokes to clipboard +- Smooth visual feedback +- Works with full two-part jokes (setup + punchline) +- Toast notification on successful copy + +## š Features Overview ⨠**Multiple Implementations** - Python CLI with interactive menu @@ -21,10 +42,16 @@ A complete, production-ready joke generator that fetches random jokes from an ex - Graceful failure messages šØ **Beautiful UI** -- Gradient design -- Smooth animations +- Gradient design with smooth transitions +- Dark mode support - Mobile responsive - Punchline reveal functionality +- Action buttons for favorites and copy + +š± **Data Persistence** +- Browser localStorage for web UI +- JSON file storage for Python CLI +- Automatic preference saving š” **External API Integration** - Uses [JokeAPI](https://jokeapi.dev/) @@ -47,9 +74,11 @@ python joke_generator.py **Features:** - Interactive menu system +- Save favorite jokes to file +- View all favorites +- Clear favorites - Press Enter to reveal punchlines - Safe mode enabled by default -- Session management with connection pooling ### Web UI @@ -57,21 +86,25 @@ python joke_generator.py Simply open `joke_generator.html` in any modern web browser! **Features:** -- Click "Get a Joke" to fetch a random joke -- Select joke type from dropdown -- Press Enter key as shortcut -- Automatic punchline reveal button for two-part jokes +- Click "Get a Joke" to fetch +- Toggle dark mode (š) +- View favorites (ā) +- Copy jokes (š) +- Add to favorites (š¤) +- All preferences saved automatically ### JavaScript Module **Usage:** ```javascript const generator = new JokeGeneratorUI(); -generator.init(); // Initialize event listeners +generator.init(); // Initialize with all features -// Or fetch directly -const joke = await generator.fetchJoke("programming", true); -generator.displayJoke(joke); +// All features available through the UI: +// - Dark mode toggle +// - Favorites management +// - Copy to clipboard +// - Punchline reveal ``` ## š API Reference @@ -109,47 +142,66 @@ GET https://v2.jokeapi.dev/joke/programming?safe-mode=true ## š» Code Examples -### Python - Simple Usage +### Python - Using Favorites ```python -from joke_generator import JokeGenerator +from joke_generator import EnhancedJokeGenerator -# Create instance -gen = JokeGenerator() +gen = EnhancedJokeGenerator() -# Fetch and display a joke +# Fetch a joke joke = gen.get_joke("programming", safe_mode=True) -if joke: - gen.display_joke(joke) +gen.display_joke(joke) + +# Save to favorites +gen.save_favorite(joke) + +# View all favorites +gen.view_favorites() + +# Remove a favorite +gen.remove_favorite(0) ``` -### Python - Custom Implementation +### JavaScript - Access Favorites -```python -from joke_generator import JokeGenerator +```javascript +const generator = new JokeGeneratorUI(); +generator.init(); + +// Add current joke to favorites +generator.toggleFavorite(); -gen = JokeGenerator(timeout=15) -joke_data = gen.get_joke("knock-knock") +// Open favorites modal +generator.openFavoritesModal(); -if joke_data["type"] == "twopart": - print(joke_data["setup"]) - input("Press Enter for the punchline...") - print(joke_data["delivery"]) +// Copy joke to clipboard +await generator.copyJokeToClipboard(); ``` -### JavaScript - Fetch Joke +### JavaScript - Dark Mode ```javascript -const ui = new JokeGeneratorUI(); +// Toggle dark mode +generator.toggleDarkMode(); -// Fetch a programming joke -const joke = await ui.fetchJoke("programming", true); - -// Display it -ui.displayJoke(joke); +// Check current mode +console.log(generator.darkMode); ``` -## š§ Customization +## šØ Customization + +### Change Colors + +**CSS Variables in HTML:** +```css +:root { + --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + --bg-primary: #ffffff; + --text-primary: #333333; + --accent-color: #667eea; +} +``` ### Change API Endpoint @@ -175,44 +227,48 @@ joke = generator.get_joke("any", safe_mode=False) const joke = await generator.fetchJoke("any", false); ``` -### Adjust Timeout +## š File Structure -**Python:** -```python -generator = JokeGenerator(timeout=20) ``` - -**JavaScript:** -```javascript -generator.timeout = 20000; // milliseconds +joke-generator/ +āāā joke_generator.py # Python CLI with favorites +āāā joke_generator.js # JavaScript module (enhanced) +āāā joke_generator.html # Web UI with dark mode +āāā README.md # Documentation ``` ## š Future Enhancements -- [ ] Save favorite jokes to local storage +- [ ] Export favorites as CSV/JSON - [ ] Share jokes on social media -- [ ] Rate jokes (like/dislike) -- [ ] Search by keyword +- [ ] Joke rating system (1-5 stars) +- [ ] Search favorites by keyword - [ ] Multiple language support -- [ ] Daily joke email notifications -- [ ] Dark mode toggle -- [ ] Accessibility improvements (WCAG 2.1) - -## š File Structure - -``` -joke-generator/ -āāā joke_generator.py # Python CLI implementation -āāā joke_generator.js # JavaScript module -āāā joke_generator.html # Web UI -āāā README.md # Documentation -``` +- [ ] Voice/Text-to-Speech +- [ ] Joke categories in modal +- [ ] Shuffle favorites mode +- [ ] Service Worker for offline support +- [ ] Unit tests and coverage ## š Resources - [JokeAPI Documentation](https://jokeapi.dev/) - [Python Requests Library](https://requests.readthedocs.io/) - [MDN Web Docs - Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) +- [MDN Web Docs - localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) + +## š¾ Data Storage + +### Web UI (Browser) +- Favorites stored in `localStorage` (browser-specific, persists across sessions) +- Dark mode preference also in `localStorage` +- No server connection required +- Private to your browser + +### Python CLI +- Favorites stored in `joke_favorites.json` in the application directory +- Human-readable JSON format +- Can be shared or backed up easily ## š License @@ -225,3 +281,6 @@ Feel free to fork, modify, and use this project as you wish. Suggestions for imp --- **Made with ā¤ļø for the developer community** + +*Last Updated: 2026-05-14* +*Version: 2.0 (Enhanced)* diff --git a/joke-generator/joke_generator.html b/joke-generator/joke_generator.html index ba5b296..fc714d7 100644 --- a/joke-generator/joke_generator.html +++ b/joke-generator/joke_generator.html @@ -11,24 +11,51 @@ box-sizing: border-box; } + :root { + --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + --bg-primary: #ffffff; + --bg-secondary: #f8f9fa; + --text-primary: #333333; + --text-secondary: #666666; + --border-color: #e0e0e0; + --accent-color: #667eea; + --shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + } + + body.dark-mode { + --bg-primary: #1a1a2e; + --bg-secondary: #16213e; + --text-primary: #eaeaea; + --text-secondary: #b0b0b0; + --border-color: #404060; + --shadow: 0 20px 60px rgba(0, 0, 0, 0.7); + } + body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: var(--primary-gradient); min-height: 100vh; display: flex; justify-content: center; align-items: center; padding: 20px; + color: var(--text-primary); + transition: background 0.3s ease; + } + + body.dark-mode { + background: linear-gradient(135deg, #0f3460 0%, #16213e 100%); } .container { - background: white; + background: var(--bg-primary); border-radius: 20px; - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + box-shadow: var(--shadow); padding: 40px; - max-width: 600px; + max-width: 700px; width: 100%; animation: slideIn 0.5s ease-out; + transition: background 0.3s ease, color 0.3s ease; } @keyframes slideIn { @@ -43,25 +70,62 @@ } .header { - text-align: center; + display: flex; + justify-content: space-between; + align-items: center; margin-bottom: 30px; } + .header-content { + flex: 1; + } + .header h1 { font-size: 2.5em; - color: #333; margin-bottom: 10px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: var(--primary-gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .header p { - color: #666; + color: var(--text-secondary); font-size: 1.1em; } + .header-controls { + display: flex; + gap: 10px; + align-items: center; + } + + .icon-btn { + width: 45px; + height: 45px; + border: 2px solid var(--border-color); + background: var(--bg-primary); + border-radius: 50%; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5em; + transition: all 0.3s; + color: var(--text-primary); + } + + .icon-btn:hover { + border-color: var(--accent-color); + transform: scale(1.05); + } + + .icon-btn.active { + background: var(--accent-color); + border-color: var(--accent-color); + color: white; + } + .controls { display: flex; gap: 10px; @@ -73,21 +137,22 @@ flex: 1; min-width: 150px; padding: 12px 15px; - border: 2px solid #e0e0e0; + border: 2px solid var(--border-color); border-radius: 10px; font-size: 1em; cursor: pointer; transition: border-color 0.3s; - background-color: white; + background-color: var(--bg-primary); + color: var(--text-primary); } select:hover { - border-color: #667eea; + border-color: var(--accent-color); } select:focus { outline: none; - border-color: #667eea; + border-color: var(--accent-color); box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } @@ -95,7 +160,7 @@ flex: 1; min-width: 150px; padding: 12px 30px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: var(--primary-gradient); color: white; border: none; border-radius: 10px; @@ -121,12 +186,13 @@ .joke-container { display: none; - background: #f8f9fa; + background: var(--bg-secondary); border-radius: 15px; padding: 25px; margin-bottom: 20px; animation: fadeIn 0.3s ease-in; - border-left: 5px solid #667eea; + border-left: 5px solid var(--accent-color); + transition: background 0.3s ease; } @keyframes fadeIn { @@ -140,26 +206,176 @@ #joke-text { font-size: 1.3em; - color: #333; + color: var(--text-primary); line-height: 1.6; white-space: pre-wrap; word-wrap: break-word; + margin-bottom: 15px; + } + + .joke-actions { + display: flex; + gap: 10px; + flex-wrap: wrap; + } + + .action-btn { + flex: 1; + min-width: 100px; + padding: 10px 15px; + background: var(--accent-color); + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-weight: 600; + font-size: 0.9em; + transition: all 0.2s; + display: flex; + align-items: center; + justify-content: center; + gap: 5px; + } + + .action-btn:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3); + } + + .action-btn.favorited { + background: #ff6b6b; + } + + .copy-feedback { + position: fixed; + top: 20px; + right: 20px; + background: #51cf66; + color: white; + padding: 12px 20px; + border-radius: 8px; + animation: slideInRight 0.3s ease; + z-index: 1000; + } + + @keyframes slideInRight { + from { + opacity: 0; + transform: translateX(100px); + } + to { + opacity: 1; + transform: translateX(0); + } } #punchline-btn { - margin-top: 15px; width: 100%; } + .favorites-modal { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.7); + z-index: 999; + justify-content: center; + align-items: center; + padding: 20px; + } + + .favorites-modal.active { + display: flex; + animation: fadeIn 0.3s ease-in; + } + + .favorites-content { + background: var(--bg-primary); + border-radius: 20px; + padding: 30px; + max-width: 500px; + width: 100%; + max-height: 80vh; + overflow-y: auto; + transition: background 0.3s ease; + } + + .favorites-content h2 { + margin-bottom: 20px; + color: var(--text-primary); + } + + .favorite-item { + background: var(--bg-secondary); + padding: 15px; + border-radius: 10px; + margin-bottom: 10px; + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 10px; + transition: background 0.3s ease; + } + + .favorite-item-text { + flex: 1; + color: var(--text-primary); + line-height: 1.4; + } + + .favorite-item-actions { + display: flex; + gap: 5px; + } + + .favorite-item-actions button { + padding: 5px 10px; + font-size: 0.8em; + min-width: auto; + flex: none; + } + + .empty-favorites { + text-align: center; + color: var(--text-secondary); + padding: 40px 20px; + } + + .modal-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + } + + .close-btn { + background: none; + border: none; + font-size: 1.5em; + cursor: pointer; + color: var(--text-primary); + width: auto; + padding: 0; + min-width: auto; + } + + .close-btn:hover { + color: var(--accent-color); + } + .footer { text-align: center; margin-top: 20px; - color: #999; + color: var(--text-secondary); font-size: 0.9em; + transition: color 0.3s ease; } .footer a { - color: #667eea; + color: var(--accent-color); text-decoration: none; transition: color 0.2s; } @@ -173,6 +389,16 @@ padding: 25px; } + .header { + flex-direction: column; + align-items: flex-start; + } + + .header-controls { + align-self: flex-end; + margin-top: 10px; + } + .header h1 { font-size: 2em; } @@ -185,14 +411,24 @@ min-width: unset; width: 100%; } + + .favorites-content { + max-height: 70vh; + } }
Get random jokes instantly!
+Get random jokes instantly!
+