diff --git a/full_test2.mjs b/full_test2.mjs new file mode 100644 index 0000000..6d074cc --- /dev/null +++ b/full_test2.mjs @@ -0,0 +1,174 @@ +import { chromium } from 'playwright'; + +const BASE = 'http://127.0.0.1:3000'; +const opts = { waitUntil: 'domcontentloaded', timeout: 15000 }; + +(async () => { + const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] }); + + // Obtenir un token via API + const apiCtx = await browser.newContext(); + const apiPage = await apiCtx.newPage(); + await apiPage.goto(BASE + '/api/auth/login', opts); + const token = await apiPage.evaluate(async () => { + const res = await fetch('/api/auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ username: 'roman', password: 'roman123' }) + }); + const data = await res.json(); + return data.access_token || null; + }); + await apiCtx.close(); + console.log(`Token obtained: ${token ? token.substring(0, 20) + '...' : 'FAILED'}`); + + if (!token) { + console.error('Cannot get token, aborting'); + process.exit(1); + } + + // ========== NON AUTHENTIFIE ========== + console.log('\n=== NON AUTHENTIFIE ==='); + const anonCtx = await browser.newContext({ viewport: { width: 1440, height: 900 } }); + const anon = await anonCtx.newPage(); + + const snap = async (p, name, url, wait = 3000) => { + try { + await p.goto(url, opts); + await p.waitForTimeout(wait); + await p.screenshot({ path: `/tmp/screenshots/${name}.png`, fullPage: false }); + console.log(`OK: ${name}`); + } catch(e) { + console.log(`FAIL: ${name} - ${e.message}`); + } + }; + + await snap(anon, 'anon_01_home', `${BASE}/`); + await snap(anon, 'anon_02_watchlist', `${BASE}/watchlist`); + await snap(anon, 'anon_03_favorites', `${BASE}/favorites`); + await snap(anon, 'anon_04_downloads', `${BASE}/downloads`); + await snap(anon, 'anon_05_settings', `${BASE}/settings`); + await snap(anon, 'anon_06_recommendations', `${BASE}/recommendations`); + + // ========== AUTHENTIFIE (cookie + localStorage) ========== + console.log('\n=== AUTHENTIFIE ==='); + const authCtx = await browser.newContext({ + viewport: { width: 1440, height: 900 }, + }); + // Injecter le token comme cookie AVANT toute navigation + await authCtx.addCookies([{ + name: 'auth_token', + value: token, + domain: '127.0.0.1', + path: '/', + sameSite: 'Strict', + httpOnly: false, + }]); + + const auth = await authCtx.newPage(); + + // Injecter dans localStorage au premier chargement + await auth.goto(BASE + '/', opts); + await auth.evaluate((t) => { + localStorage.setItem('auth_token', t); + }, token); + await auth.waitForTimeout(3000); + await auth.screenshot({ path: '/tmp/screenshots/auth_01_home.png', fullPage: false }); + console.log('OK: auth_01_home'); + + await snap(auth, 'auth_02_watchlist', `${BASE}/watchlist`); + await snap(auth, 'auth_03_favorites', `${BASE}/favorites`); + await snap(auth, 'auth_04_downloads', `${BASE}/downloads`); + await snap(auth, 'auth_05_settings', `${BASE}/settings`); + await snap(auth, 'auth_06_recommendations', `${BASE}/recommendations`); + + // ========== TESTS FONCTIONNELS ========== + console.log('\n=== TESTS FONCTIONNELS ==='); + + // Test API: toggle favori + const favResult = await auth.evaluate(async (t) => { + try { + const res = await fetch('/api/favorites/toggle', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${t}` + }, + body: JSON.stringify({ content_type: 'anime', anime_id: 'test-screenshot-1', title: 'Test Screenshot Anime' }) + }); + const data = await res.json(); + return { status: res.status, is_favorite: data.is_favorite }; + } catch(e) { + return { error: e.message }; + } + }, token); + console.log(`Favorite toggle: ${JSON.stringify(favResult)}`); + + // Voir les favoris + await snap(auth, 'auth_07_favorites_after_add', `${BASE}/favorites`); + + // Test API: ajouter watchlist item + const wlResult = await auth.evaluate(async (t) => { + try { + const res = await fetch('/api/watchlist', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${t}` + }, + body: JSON.stringify({ + anime_title: 'Test Screenshot Anime', + anime_url: 'https://example.com/anime/1', + episode_count: 12, + current_episode: 0, + status: 'watching' + }) + }); + const data = await res.json(); + return { status: res.status, id: data.id, title: data.anime_title }; + } catch(e) { + return { error: e.message }; + } + }, token); + console.log(`Watchlist add: ${JSON.stringify(wlResult)}`); + + // Voir la watchlist + await snap(auth, 'auth_08_watchlist_with_item', `${BASE}/watchlist`); + + // Scroller sur la home + await auth.goto(`${BASE}/`, opts); + await auth.waitForTimeout(2000); + await auth.evaluate(() => window.scrollTo(0, 600)); + await auth.waitForTimeout(1000); + await auth.screenshot({ path: '/tmp/screenshots/auth_09_home_scrolled.png', fullPage: false }); + console.log('OK: auth_09_home_scrolled'); + + // ========== NETTOYAGE ========== + console.log('\n=== Nettoyage ==='); + // Retirer le favori de test + await auth.evaluate(async (t) => { + await fetch('/api/favorites/toggle', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${t}` + }, + body: JSON.stringify({ content_type: 'anime', anime_id: 'test-screenshot-1', title: 'Test Screenshot Anime' }) + }); + }); + + // Retirer le watchlist item de test + if (wlResult.id) { + await auth.evaluate(async ({t, id}) => { + await fetch(`/api/watchlist/${id}`, { + method: 'DELETE', + headers: { 'Authorization': `Bearer ${t}` } + }); + }, { t: token, id: wlResult.id }); + console.log('Test watchlist item deleted'); + } + console.log('Test favorite removed'); + + await browser.close(); + console.log('\n=== ALL DONE ==='); +})(); diff --git a/static/css/style.css b/static/css/style.css index f604c5f..1170286 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,14 +1,22 @@ /* Ohm Streaming - Flat Design Theme */ :root { - /* ========== FLAT DESIGN VARIABLES ========== */ - --primary: #f15025; - --primary-hover: #d94420; - --bg-dark: #ffffff; - --bg-card: #e6e8e6; - --text-main: #191919; - --text-dim: #ced0ce; - --secondary: #ced0ce; - --accent: #f15025; + /* ========== FLAT DESIGN VARIABLES - SUNSET GLITCH PALETTE ========== */ + --primary: #FF9F1C; + --primary-hover: #e08a15; + --bg-dark: #15171A; + --bg-card: #202327; + --bg-elevated: #2a2d32; + --text-main: #F2F2F2; + --text-dim: #8a8f98; + --text-muted: #5a5f68; + --secondary: #FF9F1C; + --border: #2a2d32; + --border-hover: #FFBF69; + --accent: #FF9F1C; + --hover: rgba(255, 191, 105, 0.15); + --lilac: #8a8f98; + --pastel-petal: #F2F2F2; + --surface-hover: rgba(255, 191, 105, 0.08); --danger: #e63946; --success: #2d936c; --warning: #f4a261; @@ -39,7 +47,7 @@ body { } ::-webkit-scrollbar-track { - background: #e6e8e6; + background: #202327; border-radius: 4px; } @@ -50,13 +58,13 @@ body { } ::-webkit-scrollbar-thumb:hover { - background: var(--primary); + background: var(--text-main); } /* Firefox scrollbar */ * { scrollbar-width: thin; - scrollbar-color: var(--text-dim) #e6e8e6; + scrollbar-color: var(--text-dim) #202327; } /* ========== CONTAINER ========== */ @@ -97,7 +105,7 @@ h1 { .section-header h2 { font-size: 1.5rem; font-weight: 700; - border-left: 4px solid var(--primary); + border-left: 4px solid #FFBF69; padding-left: 15px; color: var(--text-main); } @@ -188,8 +196,8 @@ h1 { } .btn-outlined:hover:not(:disabled) { - border-color: var(--primary); - color: var(--primary); + border-color: var(--text-dim); + color: var(--text-dim); } /* Text Button */ @@ -268,7 +276,7 @@ h1 { } .card:hover, .hc:hover, .download-item:hover { - border-color: var(--primary); + border-color: #FFBF69; } /* ========== HORIZONTAL SCROLL ROW ========== */ @@ -300,7 +308,7 @@ h1 { .streaming-row::-webkit-scrollbar-thumb:hover, .recommendations-carousel::-webkit-scrollbar-thumb:hover, .releases-carousel::-webkit-scrollbar-thumb:hover { - background: var(--primary); + background: var(--text-main); } /* ========== HOME CARD ========== */ @@ -317,7 +325,7 @@ h1 { } .hc:hover { - border-color: var(--primary); + border-color: #FFBF69; } .hc-poster { @@ -432,7 +440,7 @@ h1 { } .tab.active { - color: var(--primary); + color: #FFBF69; } .tab.active::after { @@ -442,7 +450,7 @@ h1 { left: 0; width: 100%; height: 3px; - background: var(--primary); + background: #FF9F1C; border-radius: 3px 3px 0 0; } @@ -457,7 +465,7 @@ h1 { } .input-group:focus-within { - border-color: var(--primary); + border-color: #FFBF69; } .input-group input { @@ -511,14 +519,14 @@ h1 { .form-group input:focus { outline: none; - border-bottom-color: var(--primary); + border-bottom-color: #FFBF69; } .form-group input:focus + label, .form-group input:not(:placeholder-shown) + label { transform: translateY(-24px); font-size: 0.75rem; - color: var(--primary); + color: var(--text-dim); } .form-group input::placeholder { @@ -530,7 +538,7 @@ h1 { margin-bottom: 25px; padding: 16px 20px; background: var(--bg-card); - border: 1px solid var(--primary); + border: 1px solid var(--text-dim); border-radius: var(--card-radius); display: flex; justify-content: space-between; @@ -543,13 +551,13 @@ h1 { padding: 40px; background: var(--bg-card); border-radius: 6px; - border: 1px solid var(--text-dim); + border: 1px solid rgba(255, 191, 105, 0.3); } .auth-title { text-align: center; margin-bottom: 30px; - color: var(--primary); + color: var(--text-dim); } .auth-tabs { @@ -570,7 +578,7 @@ h1 { } .auth-tab.active { - color: var(--primary); + color: #FFBF69; } .auth-tab.active::after { @@ -580,7 +588,7 @@ h1 { left: 0; width: 100%; height: 3px; - background: var(--primary); + background: #FF9F1C; } .auth-form { @@ -606,7 +614,7 @@ h1 { } .auth-success { - background: rgba(45, 147, 108, 0.1); + background: rgba(255, 191, 105, 0.1); border: 1px solid var(--success); color: var(--success); } @@ -618,7 +626,7 @@ h1 { /* ========== FLAT PROGRESS BARS ========== */ .progress-container { height: 6px; - background: var(--text-dim); + background: rgba(255, 191, 105, 0.15); border-radius: 3px; margin: 12px 0; overflow: hidden; @@ -626,7 +634,7 @@ h1 { .progress-bar { height: 100%; - background: var(--primary); + background: #FF9F1C; transition: width 0.3s ease; border-radius: 3px; } @@ -690,8 +698,8 @@ h1 { } .download-item:hover { - border-color: var(--primary); - border-left-color: var(--primary); + border-color: #FFBF69; + border-left-color: #FFBF69; } .download-info { @@ -769,13 +777,13 @@ h1 { /* Progress bar for downloading */ .download-item.status-downloading .progress-bar { - background: var(--primary); + background: var(--text-dim); } /* ========== BADGE SYSTEM ========== */ .badge-completed { color: var(--success); - background: rgba(45, 147, 108, 0.1); + background: rgba(255, 191, 105, 0.1); padding: 4px 8px; border-radius: var(--input-radius); } @@ -789,7 +797,7 @@ h1 { .badge-downloading { color: var(--primary); - background: rgba(241, 80, 37, 0.1); + background: rgba(255, 191, 105, 0.15); padding: 4px 8px; border-radius: var(--input-radius); } @@ -817,7 +825,7 @@ h1 { width: 40px; height: 40px; border: 3px solid var(--text-dim); - border-top-color: var(--primary); + border-top-color: var(--text-main); border-radius: 50%; animation: spin 1s linear infinite; } @@ -830,7 +838,7 @@ h1 { /* Skeleton Loading */ .skeleton { - background: var(--bg-card); + background: rgba(255, 191, 105, 0.1); animation: skeleton-loading 1.5s ease-in-out infinite; border-radius: var(--input-radius); } @@ -904,7 +912,7 @@ h1 { .toast { padding: 16px 24px; background: var(--bg-card); - border-left: 4px solid var(--primary); + border-left: 4px solid var(--text-dim); border-radius: var(--card-radius); border: 1px solid var(--text-dim); display: flex; @@ -929,7 +937,7 @@ h1 { } .toast.info { - border-left-color: var(--primary); + border-left-color: #FFBF69; } @keyframes slide-up { diff --git a/static/js/anime-details.js b/static/js/anime-details.js index 2cf8d7b..b530340 100644 --- a/static/js/anime-details.js +++ b/static/js/anime-details.js @@ -82,7 +82,7 @@ async function searchAnimeDetails(query, malId = null) { if (hasResults) { streamingParts.unshift( `
âčïž Aucune fiche trouvĂ©e sur MyAnimeList pour "${escapeHtml(query)}"
+Aucune fiche trouvée sur MyAnimeList pour "${escapeHtml(query)}"
Essayez le nom en anglais ou japonais (ex: "Frieren: Beyond Journey's End")
@@ -125,7 +125,7 @@ async function searchAnimeDetails(query, malId = null) { } else { resultsContainer.innerHTML = `â Aucun rĂ©sultat trouvĂ© pour "${escapeHtml(query)}"
+Aucun résultat trouvé pour "${escapeHtml(query)}"
Essayez le nom en anglais ou japonais (ex: "Frieren: Beyond Journey's End", "One Piece")
@@ -138,7 +138,7 @@ async function searchAnimeDetails(query, malId = null) { console.error('Error searching anime details:', error); resultsContainer.innerHTML = `â Erreur lors de la recherche.
+Erreur lors de la recherche.
${error.message}
${escapeHtml(synopsis)}
@@ -302,7 +302,7 @@ function renderAnimeDetails(anime) { ${seasons.length > 0 ? `