├── README.md ├── index.html ├── script.js └── style.css /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Viewing Vortex 4 | 5 | Viewing Vortex is a web application that allows users to discover random movies, TV shows, and books. The app fetches data from [TMDB](https://www.themoviedb.org/documentation/api) for movies and TV shows and [Open Library](https://openlibrary.org/developers/api) for books. Users can save their favorites and toggle between light and dark themes. 6 | 7 | ## Features 8 | 9 | - **Random Discovery:** Get random movies, TV shows, or books with a single click. 10 | - **Detailed Information:** View detailed information such as runtime, ratings, and descriptions. 11 | - **Trailer Playback:** Watch trailers for movies and TV shows on YouTube. 12 | - **Favorites:** Save your favorite media items to local storage. 13 | - **Theme Toggle:** Switch between light and dark themes. 14 | 15 | 16 | ## Installation 17 | 18 | 1. **Clone the repository:** 19 | 20 | ```bash 21 | git clone https://github.com/yourusername/random-media-picker.git 22 | cd random-media-picker 23 | ``` 24 | 25 | 2. **Open the project:** 26 | 27 | Simply open the `index.html` file in your favorite browser. 28 | 29 | ```bash 30 | open index.html 31 | ``` 32 | ``` 33 | 34 | ## Usage 35 | 36 | - Click on the navigation buttons to switch between Movies, TV Shows, and Books. 37 | - Click the **Pick Another** button to load a new random item. 38 | - Use the **Save to Favorites** button to add an item to your favorites list. 39 | - Toggle the theme using the theme button in the header. 40 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Curated Media Explorer 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 43 |
44 |
45 | 46 |
47 |
48 |
49 |
50 | 51 | 60 |
61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | const TMDB_API_KEY = 'e1397be1c562b21fb75ae207765852dc'; 2 | 3 | class MediaPicker { 4 | constructor(type, updateCallback) { 5 | this.type = type; 6 | this.currentItem = null; 7 | this.filters = { 8 | genre: null, 9 | year: null, 10 | language: 'en-US' 11 | }; 12 | this.updateCallback = updateCallback; 13 | } 14 | 15 | async fetchRandom() { 16 | try { 17 | let data; 18 | switch (this.type) { 19 | case 'movie': 20 | data = await this.fetchFromTMDB('movie'); 21 | break; 22 | case 'tv': 23 | data = await this.fetchFromTMDB('tv'); 24 | break; 25 | case 'book': 26 | data = await this.fetchRandomBook(); 27 | break; 28 | } 29 | this.currentItem = data; 30 | await this.displayMedia(data); 31 | } catch (error) { 32 | this.showError(error); 33 | } 34 | } 35 | 36 | async fetchFromTMDB(contentType) { 37 | try { 38 | const discoverUrl = new URL(`https://api.themoviedb.org/3/discover/${contentType}`); 39 | const params = { 40 | api_key: TMDB_API_KEY, 41 | language: this.filters.language, 42 | sort_by: 'popularity.desc', 43 | page: Math.floor(Math.random() * 100) + 1, 44 | include_adult: false 45 | }; 46 | 47 | if (this.filters.genre) params.with_genres = this.filters.genre; 48 | if (this.filters.year) params.primary_release_year = this.filters.year; 49 | 50 | discoverUrl.search = new URLSearchParams(params).toString(); 51 | const discoverResponse = await fetch(discoverUrl); 52 | 53 | if (!discoverResponse.ok) throw new Error(`HTTP error! status: ${discoverResponse.status}`); 54 | 55 | const discoverData = await discoverResponse.json(); 56 | 57 | if (!discoverData.results?.length) { 58 | throw new Error('No results found'); 59 | } 60 | 61 | const randomItem = discoverData.results[Math.floor(Math.random() * discoverData.results.length)]; 62 | 63 | const detailsUrl = `https://api.themoviedb.org/3/${contentType}/${randomItem.id}?api_key=${TMDB_API_KEY}`; 64 | const detailsResponse = await fetch(detailsUrl); 65 | 66 | if (!detailsResponse.ok) throw new Error(`HTTP error! status: ${detailsResponse.status}`); 67 | 68 | return await detailsResponse.json(); 69 | 70 | } catch (error) { 71 | this.showError(error); 72 | throw error; 73 | } 74 | } 75 | 76 | async fetchRandomBook() { 77 | try { 78 | const response = await fetch( 79 | `https://openlibrary.org/search.json?q=${Math.random().toString(36).substring(7)}` 80 | ); 81 | 82 | if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); 83 | 84 | const data = await response.json(); 85 | 86 | if (!data.docs?.length) { 87 | throw new Error('No books found'); 88 | } 89 | 90 | const book = data.docs[Math.floor(Math.random() * data.docs.length)]; 91 | 92 | return { 93 | id: book.key, 94 | title: book.title, 95 | author: book.author_name?.[0] || 'Unknown', 96 | year: book.first_publish_year || 'Unknown', 97 | description: book.first_sentence?.[0] || 'No description available', 98 | cover: book.cover_i ? `https://covers.openlibrary.org/b/id/${book.cover_i}-M.jpg` : null 99 | }; 100 | 101 | } catch (error) { 102 | this.showError(error); 103 | throw error; 104 | } 105 | } 106 | 107 | async displayMedia(data) { 108 | const section = document.getElementById(this.type); 109 | const trailerButton = await this.getTrailerButton(data); 110 | 111 | section.innerHTML = ` 112 |
113 |
114 | ${this.getImageHTML(data)} 115 |
116 |
117 |

${data.title || data.name || 'Untitled'}

118 |
119 | ${this.getMetaData(data)} 120 |
121 |

${this.getDescription(data)}

122 |
123 | 124 | 125 | ${trailerButton} 126 |
127 |
128 |
129 | `; 130 | 131 | section.querySelector('.pick-another').addEventListener('click', () => this.fetchRandom()); 132 | section.querySelector('.save-favorite').addEventListener('click', () => this.saveToFavorites()); 133 | } 134 | 135 | getImageHTML(data) { 136 | if (this.type === 'book') { 137 | return data.cover ? 138 | `${data.title}` : 139 | '
No cover available
'; 140 | } 141 | return data.poster_path ? 142 | `${data.title || data.name}` : 143 | '
No image available
'; 144 | } 145 | 146 | getMetaData(data) { 147 | if (this.type === 'movie' || this.type === 'tv') { 148 | return ` 149 |
150 | 151 | ${(data.vote_average?.toFixed(1) || 'N/A')} 152 |
153 |
154 | 155 | ${(this.type === 'movie' 156 | ? data.release_date?.substring(0,4) 157 | : data.first_air_date?.substring(0,4)) || 'N/A'} 158 |
159 |
160 | 161 | ${this.type === 'movie' 162 | ? this.formatRuntime(data.runtime) 163 | : this.formatSeasons(data.number_of_seasons)} 164 |
165 | `; 166 | } 167 | return ` 168 |
169 | 170 | ${data.author || 'Unknown'} 171 |
172 |
173 | 174 | ${data.year || 'Unknown'} 175 |
176 | `; 177 | } 178 | 179 | formatRuntime(minutes) { 180 | if (typeof minutes !== 'number' || minutes < 1) return 'N/A mins'; 181 | const hours = Math.floor(minutes / 60); 182 | const mins = minutes % 60; 183 | return hours > 0 ? 184 | `${hours}h ${mins.toString().padStart(2, '0')}m` : 185 | `${minutes}m`; 186 | } 187 | 188 | 189 | formatSeasons(seasons) { 190 | if (!seasons || seasons < 1) return 'N/A seasons'; 191 | return `${seasons} ${seasons===1 ? 'season' : 'seasons' }`; 192 | } 193 | getDescription(data) { 194 | if (this.type === 'book') return data.description; 195 | return data.overview || 196 | 'No description available'; 197 | } 198 | async getTrailerButton(data) { 199 | if (this.type === 'book') return ''; 200 | try { 201 | const 202 | response = await fetch(`https://api.themoviedb.org/3/${this.type}/${data.id}/videos?api_key=${TMDB_API_KEY}`); 203 | const 204 | videos = await response.json(); 205 | const trailer = videos.results?.find(v => v.site === 'YouTube' && v.type === 'Trailer'); 206 | return trailer ? 207 | `` : 210 | ''; 211 | } catch (error) { 212 | return ''; 213 | } 214 | } 215 | 216 | saveToFavorites() { 217 | if (!this.currentItem) return; 218 | 219 | const favorites = JSON.parse(localStorage.getItem('favorites') || '[]'); 220 | const favoriteItem = { 221 | type: this.type, 222 | data: this.currentItem, 223 | dateAdded: new Date().toISOString() 224 | }; 225 | 226 | const exists = favorites.some(fav => 227 | (fav.data.id && fav.data.id === favoriteItem.data.id) || 228 | (!fav.data.id && fav.data.title === favoriteItem.data.title && fav.type === favoriteItem.type) 229 | ); 230 | 231 | if (!exists) { 232 | favorites.push(favoriteItem); 233 | localStorage.setItem('favorites', JSON.stringify(favorites)); 234 | if (this.updateCallback) this.updateCallback(); 235 | } 236 | } 237 | 238 | showError(error) { 239 | const section = document.getElementById(this.type); 240 | section.innerHTML = ` 241 |
242 |

Error loading content

243 |

${error.message}

244 | 245 |
246 | `; 247 | section.querySelector('.retry-button').addEventListener('click', () => this.fetchRandom()); 248 | } 249 | } 250 | 251 | class AppController { 252 | constructor() { 253 | this.moviePicker = new MediaPicker('movie', () => this.updateFavoritesList()); 254 | this.tvPicker = new MediaPicker('tv', () => this.updateFavoritesList()); 255 | this.bookPicker = new MediaPicker('book', () => this.updateFavoritesList()); 256 | 257 | this.initializeEventListeners(); 258 | this.initializeTheme(); 259 | this.updateFavoritesList(); 260 | } 261 | 262 | initializeEventListeners() { 263 | document.querySelectorAll('.nav-btn').forEach(btn => { 264 | btn.addEventListener('click', () => this.switchSection(btn.dataset.section)); 265 | }); 266 | 267 | document.getElementById('theme-toggle').addEventListener('click', () => this.toggleTheme()); 268 | 269 | document.getElementById('favorites-toggle').addEventListener('click', () => 270 | document.getElementById('favorites-sidebar').classList.add('active')); 271 | 272 | document.getElementById('close-favorites').addEventListener('click', () => 273 | document.getElementById('favorites-sidebar').classList.remove('active')); 274 | 275 | this.moviePicker.fetchRandom(); 276 | } 277 | 278 | switchSection(sectionId) { 279 | document.querySelectorAll('.nav-btn').forEach(btn => btn.classList.remove('active')); 280 | document.querySelectorAll('.picker-section').forEach(section => section.classList.remove('active')); 281 | 282 | document.querySelector(`[data-section="${sectionId}"]`).classList.add('active'); 283 | document.getElementById(sectionId).classList.add('active'); 284 | 285 | if (!document.getElementById(sectionId).innerHTML) { 286 | this[`${sectionId}Picker`].fetchRandom(); 287 | } 288 | } 289 | 290 | toggleTheme() { 291 | const currentTheme = document.body.getAttribute('data-theme') || 'light'; 292 | const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; 293 | 294 | document.body.setAttribute('data-theme', newTheme); 295 | 296 | localStorage.setItem('theme', newTheme); 297 | 298 | document.getElementById('theme-toggle').innerHTML = 299 | newTheme === 'dark' ? 300 | '' : 301 | ''; 302 | } 303 | 304 | initializeTheme() { 305 | const savedTheme = localStorage.getItem('theme') || 'light'; 306 | document.body.setAttribute('data-theme', savedTheme); 307 | document.getElementById('theme-toggle').innerHTML = 308 | savedTheme === 'dark' ? 309 | '' : 310 | ''; 311 | } 312 | 313 | 314 | updateFavoritesList() { 315 | const favorites = JSON.parse(localStorage.getItem('favorites') || '[]'); 316 | const list = document.getElementById('favorites-list'); 317 | list.innerHTML = favorites.map(fav => ` 318 |
319 | ${this.getFavoriteImage(fav)} 320 |
321 |

${fav.data.title || fav.data.name || 'Untitled'}

322 | ${fav.type.toUpperCase()} · ${new Date(fav.dateAdded).toLocaleDateString()} 323 | 324 |
325 |
326 | `).join(''); 327 | 328 | list.querySelectorAll('.remove-favorite').forEach(btn => { 329 | btn.addEventListener('click', () => { 330 | const newFavorites = favorites.filter(f => f.dateAdded !== btn.dataset.id); 331 | localStorage.setItem('favorites', JSON.stringify(newFavorites)); 332 | this.updateFavoritesList(); 333 | }); 334 | }); 335 | } 336 | 337 | getFavoriteImage(fav) { 338 | if (fav.data.poster_path) { 339 | return `${fav.data.title}`; 340 | } 341 | if (fav.data.cover) { 342 | return `${fav.data.title}`; 343 | } 344 | return '
No Image
'; 345 | } 346 | } 347 | 348 | document.addEventListener('DOMContentLoaded', () => new AppController()); 349 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color: #1e3a8a; 3 | --secondary-color: #3b82f6; 4 | --accent-color: #f97316; 5 | --background-color: #f9fafb; 6 | --card-bg: #ffffff; 7 | 8 | --text-primary: #1f2937; 9 | --text-secondary: #4b5563; 10 | --text-light: #f9fafb; 11 | 12 | --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1); 13 | --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06); 14 | --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); 15 | --shadow-inset: inset 0 2px 4px rgba(0, 0, 0, 0.06); 16 | 17 | --radius-sm: 4px; 18 | --radius-md: 8px; 19 | --radius-lg: 16px; 20 | --radius-full: 9999px; 21 | 22 | --transition-fast: 0.2s ease; 23 | --transition-normal: 0.3s ease; 24 | --transition-slow: 0.5s ease; 25 | } 26 | 27 | [data-theme="dark"] { 28 | --primary-color: #1e40af; 29 | --secondary-color: #60a5fa; 30 | --accent-color: #fb923c; 31 | --background-color: #111827; 32 | --card-bg: #1f2937; 33 | 34 | --text-primary: #f3f4f6; 35 | --text-secondary: #e5e7eb; 36 | --text-light: #f9fafb; 37 | 38 | --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3); 39 | --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.2); 40 | --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2); 41 | --shadow-inset: inset 0 2px 4px rgba(0, 0, 0, 0.2); 42 | } 43 | 44 | * { 45 | box-sizing: border-box; 46 | margin: 0; 47 | padding: 0; 48 | } 49 | 50 | body { 51 | font-family: 'Poppins', sans-serif; 52 | background-color: var(--background-color); 53 | color: var(--text-primary); 54 | transition: all var(--transition-normal); 55 | line-height: 1.6; 56 | } 57 | 58 | header { 59 | position: sticky; 60 | top: 0; 61 | z-index: 100; 62 | width: 100%; 63 | backdrop-filter: blur(10px); 64 | } 65 | 66 | nav { 67 | display: flex; 68 | justify-content: space-between; 69 | align-items: center; 70 | padding: 1rem 1.5rem; 71 | background-color: var(--primary-color); 72 | color: var(--text-light); 73 | box-shadow: var(--shadow-md); 74 | } 75 | 76 | .logo-container { 77 | display: flex; 78 | align-items: center; 79 | gap: 0.75rem; 80 | } 81 | 82 | .logo-icon { 83 | font-size: 1.5rem; 84 | color: var(--accent-color); 85 | } 86 | 87 | h1 { 88 | font-size: 1.5rem; 89 | font-weight: 600; 90 | letter-spacing: -0.5px; 91 | } 92 | 93 | h1 span { 94 | font-weight: 300; 95 | opacity: 0.9; 96 | } 97 | 98 | .nav-buttons { 99 | display: flex; 100 | gap: 0.75rem; 101 | } 102 | 103 | .nav-btn { 104 | display: flex; 105 | align-items: center; 106 | gap: 0.5rem; 107 | padding: 0.6rem 1.2rem; 108 | border: none; 109 | border-radius: var(--radius-full); 110 | background: rgba(255, 255, 255, 0.1); 111 | color: var(--text-light); 112 | font-weight: 500; 113 | cursor: pointer; 114 | transition: all var(--transition-fast); 115 | font-size: 0.875rem; 116 | } 117 | 118 | .nav-btn:hover { 119 | background: rgba(255, 255, 255, 0.2); 120 | transform: translateY(-1px); 121 | } 122 | 123 | .nav-btn.active { 124 | background: var(--secondary-color); 125 | box-shadow: var(--shadow-sm); 126 | } 127 | 128 | .header-controls { 129 | display: flex; 130 | gap: 0.75rem; 131 | } 132 | 133 | .icon-btn { 134 | display: flex; 135 | align-items: center; 136 | justify-content: center; 137 | width: 2.5rem; 138 | height: 2.5rem; 139 | border: none; 140 | border-radius: var(--radius-full); 141 | background: rgba(255, 255, 255, 0.1); 142 | color: var(--text-light); 143 | cursor: pointer; 144 | transition: all var(--transition-fast); 145 | } 146 | 147 | .icon-btn:hover { 148 | background: rgba(255, 255, 255, 0.2); 149 | transform: translateY(-1px); 150 | } 151 | 152 | button { 153 | font-family: 'Poppins', sans-serif; 154 | font-weight: 500; 155 | } 156 | 157 | main { 158 | position: relative; 159 | min-height: calc(100vh - 70px); 160 | } 161 | 162 | .picker-section { 163 | display: none; 164 | padding: 2rem 1rem; 165 | max-width: 1200px; 166 | margin: 0 auto; 167 | } 168 | 169 | .picker-section.active { 170 | display: block; 171 | animation: fadeIn var(--transition-normal); 172 | } 173 | 174 | @keyframes fadeIn { 175 | from { opacity: 0; transform: translateY(10px); } 176 | to { opacity: 1; transform: translateY(0); } 177 | } 178 | 179 | .picker-card { 180 | max-width: 1000px; 181 | margin: 0 auto; 182 | background: var(--card-bg); 183 | border-radius: var(--radius-lg); 184 | box-shadow: var(--shadow-lg); 185 | overflow: hidden; 186 | display: grid; 187 | grid-template-columns: 1fr 2fr; 188 | transition: all var(--transition-normal); 189 | } 190 | 191 | .picker-card:hover { 192 | transform: translateY(-5px); 193 | box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); 194 | } 195 | 196 | .media-poster { 197 | height: 100%; 198 | overflow: hidden; 199 | position: relative; 200 | } 201 | 202 | .media-poster::after { 203 | content: ''; 204 | position: absolute; 205 | left: 0; 206 | right: 0; 207 | bottom: 0; 208 | height: 100px; 209 | background: linear-gradient(to top, rgba(0,0,0,0.7), transparent); 210 | opacity: 0.5; 211 | } 212 | 213 | .media-poster img { 214 | width: 100%; 215 | height: 100%; 216 | object-fit: cover; 217 | transition: transform var(--transition-slow); 218 | } 219 | 220 | .picker-card:hover .media-poster img { 221 | transform: scale(1.05); 222 | } 223 | 224 | .media-details { 225 | padding: 2rem; 226 | position: relative; 227 | } 228 | 229 | .media-details h2 { 230 | font-size: 1.75rem; 231 | font-weight: 600; 232 | margin-bottom: 1rem; 233 | line-height: 1.2; 234 | color: var(--text-primary); 235 | } 236 | 237 | .meta-info { 238 | display: flex; 239 | gap: 0.75rem; 240 | margin: 1rem 0; 241 | flex-wrap: wrap; 242 | } 243 | 244 | .meta-item { 245 | display: flex; 246 | align-items: center; 247 | gap: 0.5rem; 248 | background: rgba(0, 0, 0, 0.05); 249 | padding: 0.5rem 1rem; 250 | border-radius: var(--radius-full); 251 | font-size: 0.875rem; 252 | font-weight: 500; 253 | color: var(--text-secondary); 254 | transition: all var(--transition-fast); 255 | } 256 | 257 | .meta-item:hover { 258 | background: rgba(0, 0, 0, 0.1); 259 | } 260 | 261 | .description { 262 | margin: 1.5rem 0; 263 | font-size: 0.95rem; 264 | line-height: 1.7; 265 | color: var(--text-secondary); 266 | display: -webkit-box; 267 | -webkit-line-clamp: 5; 268 | -webkit-box-orient: vertical; 269 | overflow: hidden; 270 | } 271 | 272 | .action-buttons { 273 | display: flex; 274 | gap: 1rem; 275 | margin-top: 1.5rem; 276 | flex-wrap: wrap; 277 | } 278 | 279 | .action-buttons button { 280 | padding: 0.75rem 1.25rem; 281 | border: none; 282 | border-radius: var(--radius-full); 283 | font-weight: 500; 284 | cursor: pointer; 285 | transition: all var(--transition-fast); 286 | display: flex; 287 | align-items: center; 288 | gap: 0.5rem; 289 | } 290 | 291 | .pick-another { 292 | background: var(--secondary-color); 293 | color: white; 294 | } 295 | 296 | .pick-another:hover { 297 | background: var(--primary-color); 298 | transform: translateY(-2px); 299 | } 300 | 301 | .save-favorite { 302 | background: var(--accent-color); 303 | color: white; 304 | } 305 | 306 | .save-favorite:hover { 307 | background: #ea580c; 308 | transform: translateY(-2px); 309 | } 310 | 311 | .watch-trailer { 312 | background: #ef4444; 313 | color: white; 314 | } 315 | 316 | .watch-trailer:hover { 317 | background: #dc2626; 318 | transform: translateY(-2px); 319 | } 320 | 321 | #favorites-sidebar { 322 | position: fixed; 323 | right: -350px; 324 | top: 0; 325 | width: 350px; 326 | height: 100%; 327 | background: var(--card-bg); 328 | box-shadow: var(--shadow-lg); 329 | transition: right var(--transition-normal); 330 | padding: 0; 331 | z-index: 200; 332 | overflow-y: auto; 333 | } 334 | 335 | #favorites-sidebar.active { 336 | right: 0; 337 | } 338 | 339 | .favorites-header { 340 | display: flex; 341 | justify-content: space-between; 342 | align-items: center; 343 | padding: 1.5rem; 344 | background: var(--primary-color); 345 | color: var(--text-light); 346 | } 347 | 348 | .favorites-header h2 { 349 | font-size: 1.25rem; 350 | font-weight: 500; 351 | display: flex; 352 | align-items: center; 353 | gap: 0.5rem; 354 | } 355 | 356 | #favorites-list { 357 | padding: 1rem; 358 | } 359 | 360 | .favorite-item { 361 | display: flex; 362 | align-items: center; 363 | gap: 1rem; 364 | padding: 1rem; 365 | margin: 0.75rem 0; 366 | background: rgba(0, 0, 0, 0.03); 367 | border-radius: var(--radius-md); 368 | position: relative; 369 | transition: all var(--transition-fast); 370 | } 371 | 372 | .favorite-item:hover { 373 | background: rgba(0, 0, 0, 0.06); 374 | transform: translateX(-5px); 375 | } 376 | 377 | .favorite-item img { 378 | width: 60px; 379 | height: 90px; 380 | object-fit: cover; 381 | border-radius: var(--radius-sm); 382 | box-shadow: var(--shadow-sm); 383 | } 384 | 385 | .favorite-item h3 { 386 | font-size: 1rem; 387 | font-weight: 500; 388 | margin-bottom: 0.25rem; 389 | } 390 | 391 | .favorite-item small { 392 | color: var(--text-secondary); 393 | font-size: 0.75rem; 394 | } 395 | 396 | .remove-favorite { 397 | position: absolute; 398 | top: 0.5rem; 399 | right: 0.5rem; 400 | width: 1.5rem; 401 | height: 1.5rem; 402 | border-radius: var(--radius-full); 403 | background: rgba(0, 0, 0, 0.1); 404 | color: var(--text-secondary); 405 | display: flex; 406 | align-items: center; 407 | justify-content: center; 408 | font-size: 0.75rem; 409 | cursor: pointer; 410 | transition: all var(--transition-fast); 411 | padding: 0; 412 | } 413 | 414 | .remove-favorite:hover { 415 | background: #ef4444; 416 | color: white; 417 | } 418 | 419 | .no-fav-image { 420 | width: 60px; 421 | height: 90px; 422 | display: flex; 423 | align-items: center; 424 | justify-content: center; 425 | background: rgba(0, 0, 0, 0.1); 426 | border-radius: var(--radius-sm); 427 | color: var(--text-secondary); 428 | font-size: 0.75rem; 429 | text-align: center; 430 | } 431 | 432 | .error { 433 | padding: 2rem; 434 | text-align: center; 435 | background: var(--card-bg); 436 | border-radius: var(--radius-lg); 437 | box-shadow: var(--shadow-md); 438 | max-width: 600px; 439 | margin: 2rem auto; 440 | } 441 | 442 | .error h2 { 443 | color: #ef4444; 444 | margin-bottom: 1rem; 445 | } 446 | 447 | .retry-button { 448 | background: var(--secondary-color); 449 | color: white; 450 | padding: 0.75rem 1.5rem; 451 | border: none; 452 | border-radius: var(--radius-full); 453 | margin-top: 1.5rem; 454 | cursor: pointer; 455 | transition: all var(--transition-fast); 456 | } 457 | 458 | .retry-button:hover { 459 | background: var(--primary-color); 460 | transform: translateY(-2px); 461 | } 462 | 463 | .no-poster { 464 | width: 100%; 465 | height: 100%; 466 | display: flex; 467 | align-items: center; 468 | justify-content: center; 469 | background: linear-gradient(135deg, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.05) 100%); 470 | color: var(--text-secondary); 471 | font-size: 1rem; 472 | text-align: center; 473 | padding: 2rem; 474 | } 475 | 476 | .no-cover { 477 | width: 100%; 478 | height: 100%; 479 | display: flex; 480 | align-items: center; 481 | justify-content: center; 482 | background: linear-gradient(135deg, rgba(0,0,0,0.1) 0%, rgba(0,0,0,0.05) 100%); 483 | color: var(--text-secondary); 484 | font-size: 1rem; 485 | text-align: center; 486 | padding: 2rem; 487 | } 488 | 489 | @media (max-width: 992px) { 490 | .picker-card { 491 | grid-template-columns: 1fr; 492 | } 493 | 494 | .media-poster { 495 | height: 300px; 496 | } 497 | 498 | .media-details { 499 | padding: 1.5rem; 500 | } 501 | } 502 | 503 | @media (max-width: 768px) { 504 | nav { 505 | flex-direction: column; 506 | gap: 1rem; 507 | padding: 1rem; 508 | } 509 | 510 | .nav-buttons { 511 | width: 100%; 512 | justify-content: center; 513 | } 514 | 515 | .header-controls { 516 | position: absolute; 517 | top: 1rem; 518 | right: 1rem; 519 | } 520 | 521 | .picker-section { 522 | padding: 1rem 0.5rem; 523 | } 524 | 525 | .media-poster { 526 | height: 250px; 527 | } 528 | 529 | .media-details h2 { 530 | font-size: 1.5rem; 531 | } 532 | 533 | .description { 534 | -webkit-line-clamp: 3; 535 | } 536 | 537 | .action-buttons { 538 | justify-content: center; 539 | } 540 | 541 | #favorites-sidebar { 542 | width: 100%; 543 | right: -100%; 544 | } 545 | } 546 | 547 | @media (max-width: 480px) { 548 | h1 { 549 | font-size: 1.25rem; 550 | } 551 | 552 | .nav-btn { 553 | padding: 0.5rem 1rem; 554 | font-size: 0.8rem; 555 | } 556 | 557 | .icon-btn { 558 | width: 2rem; 559 | height: 2rem; 560 | } 561 | 562 | .media-poster { 563 | height: 200px; 564 | } 565 | 566 | .media-details { 567 | padding: 1rem; 568 | } 569 | 570 | .media-details h2 { 571 | font-size: 1.25rem; 572 | } 573 | 574 | .meta-info { 575 | gap: 0.5rem; 576 | } 577 | 578 | .meta-item { 579 | padding: 0.4rem 0.8rem; 580 | font-size: 0.75rem; 581 | } 582 | 583 | .action-buttons { 584 | flex-direction: column; 585 | gap: 0.75rem; 586 | } 587 | 588 | .action-buttons button { 589 | width: 100%; 590 | justify-content: center; 591 | } 592 | } 593 | 594 | .loading { 595 | display: flex; 596 | flex-direction: column; 597 | align-items: center; 598 | justify-content: center; 599 | padding: 3rem; 600 | text-align: center; 601 | } 602 | 603 | .loading-spinner { 604 | width: 50px; 605 | height: 50px; 606 | border: 4px solid rgba(0, 0, 0, 0.1); 607 | border-left-color: var(--secondary-color); 608 | border-radius: 50%; 609 | animation: spin 1s linear infinite; 610 | margin-bottom: 1rem; 611 | } 612 | 613 | @keyframes spin { 614 | to { transform: rotate(360deg); } 615 | } 616 | 617 | .no-results { 618 | text-align: center; 619 | padding: 3rem 1rem; 620 | color: var(--text-secondary); 621 | } 622 | 623 | ::-webkit-scrollbar { 624 | width: 6px; 625 | } 626 | 627 | ::-webkit-scrollbar-track { 628 | background: rgba(0, 0, 0, 0.05); 629 | } 630 | 631 | ::-webkit-scrollbar-thumb { 632 | background: var(--secondary-color); 633 | border-radius: var(--radius-full); 634 | } 635 | 636 | ::-webkit-scrollbar-thumb:hover { 637 | background: var(--primary-color); 638 | } 639 | 640 | [data-tooltip] { 641 | position: relative; 642 | } 643 | 644 | [data-tooltip]:hover::after { 645 | content: attr(data-tooltip); 646 | position: absolute; 647 | bottom: 100%; 648 | left: 50%; 649 | transform: translateX(-50%); 650 | background: rgba(0, 0, 0, 0.8); 651 | color: white; 652 | padding: 0.5rem 0.75rem; 653 | border-radius: var(--radius-sm); 654 | font-size: 0.75rem; 655 | white-space: nowrap; 656 | z-index: 10; 657 | pointer-events: none; 658 | } 659 | 660 | .meta-item i { 661 | transition: transform var(--transition-fast); 662 | } 663 | 664 | .meta-item:hover i { 665 | transform: scale(1.2); 666 | } 667 | 668 | button:focus-visible { 669 | outline: 2px solid var(--secondary-color); 670 | outline-offset: 2px; 671 | } 672 | --------------------------------------------------------------------------------