Theme rework: polish dark mode and previews
Refine mobile nav layout, improve dark theme UI polish, and update README previews with light/dark variants.
This commit is contained in:
@@ -41,9 +41,17 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const nameEl = document.getElementById('user-name');
|
||||
if (nameEl && !nameEl.textContent) {
|
||||
nameEl.textContent = decoded.name || '';
|
||||
const name = decoded.name || '';
|
||||
const nameTargets = document.querySelectorAll('[data-user-name]');
|
||||
if (nameTargets.length) {
|
||||
nameTargets.forEach((el) => {
|
||||
if (el && !el.textContent) el.textContent = name;
|
||||
});
|
||||
} else {
|
||||
const nameEl = document.getElementById('user-name');
|
||||
if (nameEl && !nameEl.textContent) {
|
||||
nameEl.textContent = name;
|
||||
}
|
||||
}
|
||||
|
||||
const adminLink = document.getElementById('admin-control-link');
|
||||
|
||||
@@ -579,7 +579,7 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
<div class="movie-imdb-block">
|
||||
${movie.rating ? `<span>⭐ ${movie.rating}</span>` : ''}
|
||||
<a href="https://www.imdb.com/title/${movie.imdb_id}/" target="_blank" rel="noopener noreferrer" aria-label="Voir sur IMDb">
|
||||
<img src="/img/imdb.png" class="imdb-icon" alt="IMDb">
|
||||
<span class="imdb-icon" aria-hidden="true">IMDb</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="movie-rating-stars" role="group" aria-label="Noter ce film" data-movie-id="${movieId}" data-current-rating="${Number.isFinite(userRating) ? userRating : ''}">
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
// Theme toggle with system preference fallback.
|
||||
(function () {
|
||||
const STORAGE_KEY = 'theme_preference';
|
||||
const THEME_ATTR = 'data-theme';
|
||||
const SOURCE_ATTR = 'data-theme-source';
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
const API_ENDPOINT = '/api/users/theme';
|
||||
|
||||
function normalizeTheme(value) {
|
||||
return value === 'light' || value === 'dark' ? value : null;
|
||||
}
|
||||
|
||||
function getSystemTheme() {
|
||||
return mediaQuery.matches ? 'dark' : 'light';
|
||||
}
|
||||
|
||||
function setTheme(theme, source) {
|
||||
document.documentElement.setAttribute(THEME_ATTR, theme);
|
||||
document.documentElement.setAttribute(SOURCE_ATTR, source);
|
||||
updateToggleUi(theme, source);
|
||||
}
|
||||
|
||||
function getAuthToken() {
|
||||
try {
|
||||
return localStorage.getItem('auth_token');
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function savePreferenceToServer(themePreference) {
|
||||
const token = getAuthToken();
|
||||
if (!token) return;
|
||||
try {
|
||||
await fetch(API_ENDPOINT, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({ themePreference: themePreference ?? null })
|
||||
});
|
||||
} catch (_) {
|
||||
// Ignore network errors to avoid blocking UI.
|
||||
}
|
||||
}
|
||||
|
||||
async function syncPreferenceFromServer() {
|
||||
const token = getAuthToken();
|
||||
if (!token) return;
|
||||
try {
|
||||
const res = await fetch(API_ENDPOINT, {
|
||||
headers: { 'Authorization': `Bearer ${token}` },
|
||||
cache: 'no-store'
|
||||
});
|
||||
if (!res.ok) return;
|
||||
const data = await res.json();
|
||||
const pref = normalizeTheme(data?.themePreference);
|
||||
if (pref) {
|
||||
localStorage.setItem(STORAGE_KEY, pref);
|
||||
} else {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
applyStoredOrSystem();
|
||||
} catch (_) {
|
||||
// Ignore fetch errors.
|
||||
}
|
||||
}
|
||||
|
||||
function applyStoredOrSystem() {
|
||||
const stored = normalizeTheme(localStorage.getItem(STORAGE_KEY));
|
||||
const theme = stored || getSystemTheme();
|
||||
const source = stored ? 'user' : 'system';
|
||||
setTheme(theme, source);
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
const stored = normalizeTheme(localStorage.getItem(STORAGE_KEY));
|
||||
const current = stored || getSystemTheme();
|
||||
const next = current === 'dark' ? 'light' : 'dark';
|
||||
localStorage.setItem(STORAGE_KEY, next);
|
||||
setTheme(next, 'user');
|
||||
savePreferenceToServer(next);
|
||||
}
|
||||
|
||||
function resetToSystem() {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
applyStoredOrSystem();
|
||||
savePreferenceToServer(null);
|
||||
}
|
||||
|
||||
function updateToggleUi(theme, source) {
|
||||
const buttons = document.querySelectorAll('[data-theme-toggle]');
|
||||
const isDark = theme === 'dark';
|
||||
buttons.forEach((button) => {
|
||||
button.setAttribute('aria-checked', String(isDark));
|
||||
button.dataset.theme = theme;
|
||||
const label = isDark ? 'Mode sombre' : 'Mode clair';
|
||||
const sourceLabel = source === 'system' ? ' (système)' : '';
|
||||
button.setAttribute('title', `${label}${sourceLabel} — Shift+clic pour système`);
|
||||
});
|
||||
}
|
||||
|
||||
applyStoredOrSystem();
|
||||
syncPreferenceFromServer();
|
||||
|
||||
mediaQuery.addEventListener('change', () => {
|
||||
const stored = normalizeTheme(localStorage.getItem(STORAGE_KEY));
|
||||
if (!stored) {
|
||||
applyStoredOrSystem();
|
||||
}
|
||||
});
|
||||
|
||||
function bindToggleButtons() {
|
||||
document.querySelectorAll('[data-theme-toggle]').forEach((button) => {
|
||||
if (button.dataset.themeBound === 'true') return;
|
||||
button.dataset.themeBound = 'true';
|
||||
button.addEventListener('click', (event) => {
|
||||
if (event.shiftKey) {
|
||||
resetToSystem();
|
||||
return;
|
||||
}
|
||||
toggleTheme();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', bindToggleButtons);
|
||||
} else {
|
||||
bindToggleButtons();
|
||||
}
|
||||
})();
|
||||
@@ -515,16 +515,11 @@ window.addEventListener('DOMContentLoaded', async function () {
|
||||
imdbLink.rel = 'noopener noreferrer';
|
||||
imdbLink.setAttribute('aria-label', 'Voir sur IMDb');
|
||||
|
||||
const imdbText = document.createElement('span');
|
||||
imdbText.className = 'small';
|
||||
imdbText.textContent = 'IMDb';
|
||||
|
||||
const imdbIcon = document.createElement('img');
|
||||
imdbIcon.src = '/img/imdb.png';
|
||||
const imdbIcon = document.createElement('span');
|
||||
imdbIcon.className = 'imdb-icon';
|
||||
imdbIcon.alt = 'IMDb';
|
||||
imdbIcon.setAttribute('aria-hidden', 'true');
|
||||
imdbIcon.textContent = 'IMDb';
|
||||
|
||||
imdbLink.appendChild(imdbText);
|
||||
imdbLink.appendChild(imdbIcon);
|
||||
right.appendChild(imdbLink);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user