├── 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 |
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 |
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 | ` ` :
139 | 'No cover available
';
140 | }
141 | return data.poster_path ?
142 | ` ` :
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 | `
208 | Watch Trailer
209 | ` :
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 |
Try Again
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 ` `;
340 | }
341 | if (fav.data.cover) {
342 | return ` `;
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 |
--------------------------------------------------------------------------------