Preview: add UI V2 mode with grid, filters, sort, and toasts
This commit is contained in:
249
app.js
249
app.js
@@ -61,6 +61,16 @@ const googleRestoreBtn = document.getElementById("googleRestoreBtn");
|
|||||||
const quickSearchInput = document.getElementById("quickSearchInput");
|
const quickSearchInput = document.getElementById("quickSearchInput");
|
||||||
const quickSearchResults = document.getElementById("quickSearchResults");
|
const quickSearchResults = document.getElementById("quickSearchResults");
|
||||||
const loanedFilterBtn = document.getElementById("loanedFilterBtn");
|
const loanedFilterBtn = document.getElementById("loanedFilterBtn");
|
||||||
|
const v2Toolbar = document.getElementById("v2Toolbar");
|
||||||
|
const v2SearchInput = document.getElementById("v2SearchInput");
|
||||||
|
const v2ConsoleFilter = document.getElementById("v2ConsoleFilter");
|
||||||
|
const v2GenreFilter = document.getElementById("v2GenreFilter");
|
||||||
|
const v2SortSelect = document.getElementById("v2SortSelect");
|
||||||
|
const v2StickyBar = document.getElementById("v2StickyBar");
|
||||||
|
const v2StickyCount = document.getElementById("v2StickyCount");
|
||||||
|
const v2ToggleFormBtn = document.getElementById("v2ToggleFormBtn");
|
||||||
|
const v2QuickBackupBtn = document.getElementById("v2QuickBackupBtn");
|
||||||
|
const toastContainer = document.getElementById("toastContainer");
|
||||||
const scannerStatus = document.getElementById("scannerStatus");
|
const scannerStatus = document.getElementById("scannerStatus");
|
||||||
const scannerStartBtn = document.getElementById("scannerStartBtn");
|
const scannerStartBtn = document.getElementById("scannerStartBtn");
|
||||||
const scannerStopBtn = document.getElementById("scannerStopBtn");
|
const scannerStopBtn = document.getElementById("scannerStopBtn");
|
||||||
@@ -80,6 +90,12 @@ let scannerRunning = false;
|
|||||||
let scannerLoopId = null;
|
let scannerLoopId = null;
|
||||||
let scannerLastCodeValue = "";
|
let scannerLastCodeValue = "";
|
||||||
let scannerLastCodeAt = 0;
|
let scannerLastCodeAt = 0;
|
||||||
|
const uiV2Enabled = new URLSearchParams(window.location.search).get("ui") === "v2";
|
||||||
|
let v2SearchTerm = "";
|
||||||
|
let v2ConsoleValue = "";
|
||||||
|
let v2GenreValue = "";
|
||||||
|
let v2SortValue = "title_asc";
|
||||||
|
let v2FormCollapsed = false;
|
||||||
|
|
||||||
coverFileInput.addEventListener("change", async (event) => {
|
coverFileInput.addEventListener("change", async (event) => {
|
||||||
const input = event.target;
|
const input = event.target;
|
||||||
@@ -203,6 +219,48 @@ loanedFilterBtn.addEventListener("click", () => {
|
|||||||
renderGames();
|
renderGames();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (v2SearchInput) {
|
||||||
|
v2SearchInput.addEventListener("input", (event) => {
|
||||||
|
v2SearchTerm = event.target.value.trim();
|
||||||
|
renderGames();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v2ConsoleFilter) {
|
||||||
|
v2ConsoleFilter.addEventListener("change", (event) => {
|
||||||
|
v2ConsoleValue = event.target.value;
|
||||||
|
renderGames();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v2GenreFilter) {
|
||||||
|
v2GenreFilter.addEventListener("change", (event) => {
|
||||||
|
v2GenreValue = event.target.value;
|
||||||
|
renderGames();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v2SortSelect) {
|
||||||
|
v2SortSelect.addEventListener("change", (event) => {
|
||||||
|
v2SortValue = event.target.value || "title_asc";
|
||||||
|
renderGames();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v2ToggleFormBtn) {
|
||||||
|
v2ToggleFormBtn.addEventListener("click", () => {
|
||||||
|
v2FormCollapsed = !v2FormCollapsed;
|
||||||
|
gameForm.classList.toggle("hidden", v2FormCollapsed);
|
||||||
|
v2ToggleFormBtn.textContent = v2FormCollapsed ? "Afficher formulaire" : "Ajouter";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v2QuickBackupBtn) {
|
||||||
|
v2QuickBackupBtn.addEventListener("click", () => {
|
||||||
|
backupBtn.click();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (scannerStartBtn) {
|
if (scannerStartBtn) {
|
||||||
scannerStartBtn.addEventListener("click", async () => {
|
scannerStartBtn.addEventListener("click", async () => {
|
||||||
await startScanner();
|
await startScanner();
|
||||||
@@ -258,6 +316,7 @@ platformForm.addEventListener("submit", async (event) => {
|
|||||||
|
|
||||||
gameForm.addEventListener("submit", async (event) => {
|
gameForm.addEventListener("submit", async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
const wasEditing = Boolean(editingGameId);
|
||||||
|
|
||||||
const title = titleInput.value.trim();
|
const title = titleInput.value.trim();
|
||||||
if (!title || !state.selectedConsole) {
|
if (!title || !state.selectedConsole) {
|
||||||
@@ -297,6 +356,7 @@ gameForm.addEventListener("submit", async (event) => {
|
|||||||
|
|
||||||
resetEditMode();
|
resetEditMode();
|
||||||
await refreshFromApi(state.selectedBrand, state.selectedConsole);
|
await refreshFromApi(state.selectedBrand, state.selectedConsole);
|
||||||
|
showToast(wasEditing ? "Jeu mis a jour." : "Jeu ajoute.", "success");
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -350,6 +410,7 @@ gameForm.addEventListener("submit", async (event) => {
|
|||||||
persist();
|
persist();
|
||||||
markLocalDataForImport();
|
markLocalDataForImport();
|
||||||
render();
|
render();
|
||||||
|
showToast(wasEditing ? "Jeu mis a jour." : "Jeu ajoute.", "success");
|
||||||
});
|
});
|
||||||
|
|
||||||
brandTabs.addEventListener("click", (event) => {
|
brandTabs.addEventListener("click", (event) => {
|
||||||
@@ -496,6 +557,7 @@ gamesList.addEventListener("click", async (event) => {
|
|||||||
persist();
|
persist();
|
||||||
markLocalDataForImport();
|
markLocalDataForImport();
|
||||||
render();
|
render();
|
||||||
|
showToast("Jeu mis a jour.", "success");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,6 +592,7 @@ gamesList.addEventListener("click", async (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await refreshFromApi(state.selectedBrand, state.selectedConsole);
|
await refreshFromApi(state.selectedBrand, state.selectedConsole);
|
||||||
|
showToast("Action enregistree.", "success");
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -564,6 +627,7 @@ gamesList.addEventListener("click", async (event) => {
|
|||||||
persist();
|
persist();
|
||||||
markLocalDataForImport();
|
markLocalDataForImport();
|
||||||
render();
|
render();
|
||||||
|
showToast("Action enregistree.", "success");
|
||||||
});
|
});
|
||||||
|
|
||||||
cancelEditBtn.addEventListener("click", () => {
|
cancelEditBtn.addEventListener("click", () => {
|
||||||
@@ -678,10 +742,12 @@ restoreFileInput.addEventListener("change", async (event) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
|
renderV2Chrome();
|
||||||
renderDataMode();
|
renderDataMode();
|
||||||
renderGoogleStatus();
|
renderGoogleStatus();
|
||||||
renderBrandTabs();
|
renderBrandTabs();
|
||||||
renderConsoleTabs();
|
renderConsoleTabs();
|
||||||
|
updateV2FilterOptions();
|
||||||
renderGames();
|
renderGames();
|
||||||
renderSearchResults();
|
renderSearchResults();
|
||||||
renderCollectionStats();
|
renderCollectionStats();
|
||||||
@@ -837,6 +903,78 @@ function renderCollectionStats() {
|
|||||||
totalGamesValue.textContent = `${totalValue.toFixed(2)} EUR`;
|
totalGamesValue.textContent = `${totalValue.toFixed(2)} EUR`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showToast(message, type = "info", timeoutMs = 2600) {
|
||||||
|
if (!toastContainer || !message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const toast = document.createElement("div");
|
||||||
|
toast.className = `toast ${type}`.trim();
|
||||||
|
toast.textContent = message;
|
||||||
|
toastContainer.append(toast);
|
||||||
|
window.setTimeout(() => {
|
||||||
|
toast.remove();
|
||||||
|
}, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderV2Chrome() {
|
||||||
|
if (!uiV2Enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.classList.add("ui-v2");
|
||||||
|
if (v2Toolbar) {
|
||||||
|
v2Toolbar.classList.remove("hidden");
|
||||||
|
}
|
||||||
|
if (v2StickyBar) {
|
||||||
|
v2StickyBar.classList.remove("hidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateV2FilterOptions() {
|
||||||
|
if (!uiV2Enabled || !v2ConsoleFilter || !v2GenreFilter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allGames = collectAllGames();
|
||||||
|
const consoles = Array.from(new Set(allGames.map((game) => normalizeText(game.consoleName)).filter(Boolean))).sort();
|
||||||
|
const genres = Array.from(new Set(allGames.flatMap((game) => splitGenres(game.genre)))).sort();
|
||||||
|
|
||||||
|
v2ConsoleFilter.innerHTML = `<option value="">Toutes</option>${consoles
|
||||||
|
.map((consoleName) => `<option value="${escapeHtml(consoleName)}">${escapeHtml(consoleName)}</option>`)
|
||||||
|
.join("")}`;
|
||||||
|
v2GenreFilter.innerHTML = `<option value="">Tous</option>${genres
|
||||||
|
.map((genre) => `<option value="${escapeHtml(genre)}">${escapeHtml(genre)}</option>`)
|
||||||
|
.join("")}`;
|
||||||
|
|
||||||
|
v2ConsoleFilter.value = v2ConsoleValue;
|
||||||
|
v2GenreFilter.value = v2GenreValue;
|
||||||
|
v2SortSelect.value = v2SortValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitGenres(genreRaw) {
|
||||||
|
return normalizeText(genreRaw)
|
||||||
|
.split(/[\/,|]/)
|
||||||
|
.map((item) => normalizeText(item))
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
function badgeClassForGenre(genreValue) {
|
||||||
|
const normalized = normalizeText(genreValue).toLowerCase();
|
||||||
|
if (normalized.includes("rpg")) {
|
||||||
|
return "rpg";
|
||||||
|
}
|
||||||
|
if (normalized.includes("action") || normalized.includes("aventure") || normalized.includes("adventure")) {
|
||||||
|
return "action";
|
||||||
|
}
|
||||||
|
if (normalized.includes("sport") || normalized.includes("football") || normalized.includes("course")) {
|
||||||
|
return "sport";
|
||||||
|
}
|
||||||
|
if (normalized.includes("racing") || normalized.includes("kart")) {
|
||||||
|
return "racing";
|
||||||
|
}
|
||||||
|
return "default";
|
||||||
|
}
|
||||||
|
|
||||||
function renderSearchResults() {
|
function renderSearchResults() {
|
||||||
if (!quickSearchResults) {
|
if (!quickSearchResults) {
|
||||||
return;
|
return;
|
||||||
@@ -941,27 +1079,76 @@ function renderConsoleTabs() {
|
|||||||
|
|
||||||
function renderGames() {
|
function renderGames() {
|
||||||
const selectedConsole = state.selectedConsole;
|
const selectedConsole = state.selectedConsole;
|
||||||
gameSectionTitle.textContent = showLoanedOnly
|
const inV2 = uiV2Enabled;
|
||||||
? "Jeux pretes - Toutes consoles"
|
gameSectionTitle.textContent = inV2
|
||||||
: selectedConsole
|
? "Catalogue jeux"
|
||||||
? `Jeux - ${selectedConsole}`
|
: showLoanedOnly
|
||||||
: "Jeux";
|
? "Jeux pretes - Toutes consoles"
|
||||||
|
: selectedConsole
|
||||||
|
? `Jeux - ${selectedConsole}`
|
||||||
|
: "Jeux";
|
||||||
gamesList.innerHTML = "";
|
gamesList.innerHTML = "";
|
||||||
loanedFilterBtn.textContent = showLoanedOnly ? "Voir tous les jeux" : "Voir jeux pretes";
|
loanedFilterBtn.textContent = showLoanedOnly ? "Voir tous les jeux" : "Voir jeux pretes";
|
||||||
|
|
||||||
if (!showLoanedOnly && !selectedConsole) {
|
if (!inV2 && !showLoanedOnly && !selectedConsole) {
|
||||||
gamesList.innerHTML = '<p class="empty">Ajoute une section pour commencer.</p>';
|
gamesList.innerHTML = '<p class="empty">Ajoute une section pour commencer.</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const games = showLoanedOnly
|
let games = inV2
|
||||||
? collectAllGames().filter((game) => normalizeText(game.loanedTo))
|
? collectAllGames()
|
||||||
: state.gamesByConsole[selectedConsole] || [];
|
: showLoanedOnly
|
||||||
|
? collectAllGames().filter((game) => normalizeText(game.loanedTo))
|
||||||
|
: state.gamesByConsole[selectedConsole] || [];
|
||||||
|
|
||||||
|
if (showLoanedOnly) {
|
||||||
|
games = games.filter((game) => normalizeText(game.loanedTo));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inV2) {
|
||||||
|
const searchTerm = v2SearchTerm.toLowerCase();
|
||||||
|
if (searchTerm) {
|
||||||
|
games = games.filter((game) => {
|
||||||
|
const fields = [
|
||||||
|
game.title,
|
||||||
|
game.publisher,
|
||||||
|
game.barcode,
|
||||||
|
game.consoleName,
|
||||||
|
game.genre,
|
||||||
|
]
|
||||||
|
.map((value) => normalizeText(value).toLowerCase())
|
||||||
|
.join(" ");
|
||||||
|
return fields.includes(searchTerm);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (v2ConsoleValue) {
|
||||||
|
games = games.filter((game) => normalizeText(game.consoleName) === v2ConsoleValue);
|
||||||
|
}
|
||||||
|
if (v2GenreValue) {
|
||||||
|
games = games.filter((game) =>
|
||||||
|
splitGenres(game.genre).some((genre) => genre.toLowerCase() === v2GenreValue.toLowerCase()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
games.sort((a, b) => {
|
||||||
|
if (v2SortValue === "year_desc") {
|
||||||
|
return Number(b.year || 0) - Number(a.year || 0);
|
||||||
|
}
|
||||||
|
if (v2SortValue === "value_desc") {
|
||||||
|
return Number(b.value || 0) - Number(a.value || 0);
|
||||||
|
}
|
||||||
|
return normalizeText(a.title).localeCompare(normalizeText(b.title), "fr", { sensitivity: "base" });
|
||||||
|
});
|
||||||
|
|
||||||
|
if (v2StickyCount) {
|
||||||
|
v2StickyCount.textContent = `${games.length} jeu${games.length > 1 ? "x" : ""}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!games.length) {
|
if (!games.length) {
|
||||||
gamesList.innerHTML = showLoanedOnly
|
gamesList.innerHTML = showLoanedOnly
|
||||||
? '<p class="empty">Aucun jeu prete actuellement.</p>'
|
? '<p class="empty">Aucun jeu prete actuellement.</p>'
|
||||||
: '<p class="empty">Aucun jeu sur cette console pour le moment.</p>';
|
: '<p class="empty">Aucun jeu pour ces filtres.</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -974,20 +1161,38 @@ function renderGames() {
|
|||||||
|
|
||||||
card.querySelector(".game-title").textContent = game.title;
|
card.querySelector(".game-title").textContent = game.title;
|
||||||
|
|
||||||
const metaParts = [
|
const metaParts = inV2
|
||||||
showLoanedOnly ? `${game.brand} / ${game.consoleName}` : null,
|
? [
|
||||||
game.barcode ? `Code: ${game.barcode}` : null,
|
game.publisher ? `Editeur: ${game.publisher}` : null,
|
||||||
game.version ? `Version: ${game.version}` : null,
|
game.year ? `Annee: ${game.year}` : null,
|
||||||
game.genre ? `Genre: ${game.genre}` : null,
|
game.version ? `Version: ${game.version}` : null,
|
||||||
game.publisher ? `Editeur: ${game.publisher}` : null,
|
game.value != null ? `Cote: ${Number(game.value).toFixed(2)} EUR` : null,
|
||||||
game.isDuplicate ? "Double: OUI" : null,
|
game.consoleName ? `Console: ${game.consoleName}` : null,
|
||||||
game.year ? `Annee: ${game.year}` : null,
|
].filter(Boolean)
|
||||||
game.purchasePrice != null ? `Prix achat: ${game.purchasePrice.toFixed(2)} EUR` : null,
|
: [
|
||||||
game.value != null ? `Cote: ${game.value.toFixed(2)} EUR` : null,
|
showLoanedOnly ? `${game.brand} / ${game.consoleName}` : null,
|
||||||
game.condition != null ? `Etat: ${game.condition}` : null,
|
game.barcode ? `Code: ${game.barcode}` : null,
|
||||||
].filter(Boolean);
|
game.version ? `Version: ${game.version}` : null,
|
||||||
|
game.genre ? `Genre: ${game.genre}` : null,
|
||||||
|
game.publisher ? `Editeur: ${game.publisher}` : null,
|
||||||
|
game.isDuplicate ? "Double: OUI" : null,
|
||||||
|
game.year ? `Annee: ${game.year}` : null,
|
||||||
|
game.purchasePrice != null ? `Prix achat: ${game.purchasePrice.toFixed(2)} EUR` : null,
|
||||||
|
game.value != null ? `Cote: ${game.value.toFixed(2)} EUR` : null,
|
||||||
|
game.condition != null ? `Etat: ${game.condition}` : null,
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
card.querySelector(".game-meta").textContent = metaParts.join(" | ") || "Aucune information complementaire";
|
card.querySelector(".game-meta").textContent = metaParts.join(" | ") || "Aucune information complementaire";
|
||||||
|
const badgesContainer = card.querySelector(".game-badges");
|
||||||
|
const genres = splitGenres(game.genre);
|
||||||
|
if (genres.length) {
|
||||||
|
badgesContainer.innerHTML = genres
|
||||||
|
.slice(0, 4)
|
||||||
|
.map((genre) => `<span class="genre-badge ${badgeClassForGenre(genre)}">${escapeHtml(genre)}</span>`)
|
||||||
|
.join("");
|
||||||
|
} else {
|
||||||
|
badgesContainer.innerHTML = "";
|
||||||
|
}
|
||||||
const coverEl = card.querySelector(".game-cover");
|
const coverEl = card.querySelector(".game-cover");
|
||||||
const coverUrl = normalizeText(game.coverUrl);
|
const coverUrl = normalizeText(game.coverUrl);
|
||||||
if (coverUrl) {
|
if (coverUrl) {
|
||||||
|
|||||||
37
index.html
37
index.html
@@ -61,6 +61,15 @@
|
|||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="app-shell">
|
<main class="app-shell">
|
||||||
|
<div id="v2StickyBar" class="v2-sticky hidden">
|
||||||
|
<div class="v2-sticky-title">UI V2 Preview</div>
|
||||||
|
<div id="v2StickyCount" class="v2-sticky-count">0 jeu</div>
|
||||||
|
<div class="v2-sticky-actions">
|
||||||
|
<button id="v2ToggleFormBtn" type="button" class="btn-secondary">Ajouter</button>
|
||||||
|
<button id="v2QuickBackupBtn" type="button" class="btn-secondary">Sauvegarder</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<header class="hero">
|
<header class="hero">
|
||||||
<div class="hero-copy">
|
<div class="hero-copy">
|
||||||
<p class="eyebrow">Catalogue perso</p>
|
<p class="eyebrow">Catalogue perso</p>
|
||||||
@@ -94,6 +103,32 @@
|
|||||||
<h2 id="gameSectionTitle">Jeux</h2>
|
<h2 id="gameSectionTitle">Jeux</h2>
|
||||||
<p id="dataModeInfo" class="data-mode"></p>
|
<p id="dataModeInfo" class="data-mode"></p>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="v2Toolbar" class="v2-toolbar hidden">
|
||||||
|
<label>
|
||||||
|
Recherche
|
||||||
|
<input id="v2SearchInput" placeholder="Titre, editeur, code-barres..." />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Console
|
||||||
|
<select id="v2ConsoleFilter">
|
||||||
|
<option value="">Toutes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Genre
|
||||||
|
<select id="v2GenreFilter">
|
||||||
|
<option value="">Tous</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Tri
|
||||||
|
<select id="v2SortSelect">
|
||||||
|
<option value="title_asc">Titre (A-Z)</option>
|
||||||
|
<option value="year_desc">Annee (recent d'abord)</option>
|
||||||
|
<option value="value_desc">Cote (elevee d'abord)</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<div class="games-actions-bar">
|
<div class="games-actions-bar">
|
||||||
<button id="loanedFilterBtn" type="button" class="btn-secondary">Voir jeux pretes</button>
|
<button id="loanedFilterBtn" type="button" class="btn-secondary">Voir jeux pretes</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -177,12 +212,14 @@
|
|||||||
<div id="gamesList" class="games-list"></div>
|
<div id="gamesList" class="games-list"></div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
<div id="toastContainer" class="toast-container" aria-live="polite" aria-atomic="true"></div>
|
||||||
|
|
||||||
<template id="gameCardTemplate">
|
<template id="gameCardTemplate">
|
||||||
<article class="game-card">
|
<article class="game-card">
|
||||||
<img class="game-cover hidden" alt="Pochette du jeu" loading="lazy" />
|
<img class="game-cover hidden" alt="Pochette du jeu" loading="lazy" />
|
||||||
<div class="game-main">
|
<div class="game-main">
|
||||||
<h3 class="game-title"></h3>
|
<h3 class="game-title"></h3>
|
||||||
|
<div class="game-badges"></div>
|
||||||
<p class="game-meta"></p>
|
<p class="game-meta"></p>
|
||||||
<p class="game-loan"></p>
|
<p class="game-loan"></p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
201
styles.css
201
styles.css
@@ -8,6 +8,9 @@
|
|||||||
--danger: #bf2f47;
|
--danger: #bf2f47;
|
||||||
--border: #d6dde6;
|
--border: #d6dde6;
|
||||||
--shadow: 0 10px 24px rgba(17, 36, 57, 0.08);
|
--shadow: 0 10px 24px rgba(17, 36, 57, 0.08);
|
||||||
|
--v2-primary: #6c5ce7;
|
||||||
|
--v2-success: #00b894;
|
||||||
|
--v2-bg: #f8f9fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@@ -519,7 +522,201 @@ button {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-sticky {
|
||||||
|
position: sticky;
|
||||||
|
top: 0.55rem;
|
||||||
|
z-index: 20;
|
||||||
|
background: linear-gradient(130deg, #f2eeff, #edf7f4);
|
||||||
|
border: 1px solid #d8d8ea;
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 0.55rem 0.7rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-sticky-title {
|
||||||
|
font-weight: 700;
|
||||||
|
color: #2f2a66;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-sticky-count {
|
||||||
|
font-size: 0.88rem;
|
||||||
|
color: #3c4e61;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-sticky-actions {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-toolbar {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #f8fbff;
|
||||||
|
padding: 0.7rem;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
display: grid;
|
||||||
|
gap: 0.55rem;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.v2-toolbar select {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0.62rem 0.65rem;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game-badges {
|
||||||
|
margin: 0.4rem 0 0;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.33rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.genre-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.2rem 0.5rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.73rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.genre-badge.default {
|
||||||
|
background: #e8eef8;
|
||||||
|
color: #234361;
|
||||||
|
border-color: #d4dfed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.genre-badge.rpg {
|
||||||
|
background: #efe7ff;
|
||||||
|
color: #4f2f9a;
|
||||||
|
border-color: #ddccff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.genre-badge.action {
|
||||||
|
background: #ffe9ec;
|
||||||
|
color: #a23649;
|
||||||
|
border-color: #ffd2d9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.genre-badge.sport {
|
||||||
|
background: #e6fbf4;
|
||||||
|
color: #146c57;
|
||||||
|
border-color: #c7f2e4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.genre-badge.racing {
|
||||||
|
background: #fff4df;
|
||||||
|
color: #9c6515;
|
||||||
|
border-color: #ffe6b9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-container {
|
||||||
|
position: fixed;
|
||||||
|
right: 1rem;
|
||||||
|
bottom: 1rem;
|
||||||
|
z-index: 80;
|
||||||
|
display: grid;
|
||||||
|
gap: 0.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
min-width: 220px;
|
||||||
|
max-width: 360px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #d7e0ec;
|
||||||
|
background: #ffffff;
|
||||||
|
color: #1d2f44;
|
||||||
|
padding: 0.58rem 0.7rem;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.success {
|
||||||
|
border-color: #bdecd8;
|
||||||
|
background: #eefbf5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.error {
|
||||||
|
border-color: #ffd1da;
|
||||||
|
background: #fff1f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.ui-v2 {
|
||||||
|
background: radial-gradient(circle at top right, #efeaff 0, transparent 34%), var(--v2-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.ui-v2 .games-list {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
body.ui-v2 .game-card {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.55rem;
|
||||||
|
transition: transform 180ms ease, box-shadow 180ms ease, border-color 180ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.ui-v2 .game-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
border-color: #d3cef9;
|
||||||
|
box-shadow: 0 12px 24px rgba(50, 50, 93, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.ui-v2 .game-cover {
|
||||||
|
width: 72px;
|
||||||
|
height: 98px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.ui-v2 .game-actions {
|
||||||
|
width: 100%;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
body.ui-v2 {
|
||||||
|
background: #10141a;
|
||||||
|
color: #e8edf6;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.ui-v2 .panel,
|
||||||
|
body.ui-v2 .hero,
|
||||||
|
body.ui-v2 .v2-toolbar,
|
||||||
|
body.ui-v2 .v2-sticky,
|
||||||
|
body.ui-v2 .game-card,
|
||||||
|
body.ui-v2 .search-zone,
|
||||||
|
body.ui-v2 .scanner-zone {
|
||||||
|
background: #161d26;
|
||||||
|
border-color: #2b3747;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.ui-v2 .btn-secondary,
|
||||||
|
body.ui-v2 .btn-inline {
|
||||||
|
background: #2a3647;
|
||||||
|
color: #dbe5f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.ui-v2 input,
|
||||||
|
body.ui-v2 select {
|
||||||
|
background: #10161f;
|
||||||
|
border-color: #314157;
|
||||||
|
color: #e7eef8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
|
.v2-toolbar {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.grid-form,
|
.grid-form,
|
||||||
.game-form {
|
.game-form {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
@@ -532,6 +729,10 @@ button {
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.v2-toolbar {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.tools-toggle-btn {
|
.tools-toggle-btn {
|
||||||
top: auto;
|
top: auto;
|
||||||
bottom: 1rem;
|
bottom: 1rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user