Files
oscars-pool/public/picks.html
Mathieu Moran 4308d6babf Implement en/fr localization across admin and user UIs
Introduce client-side localization helper and en/fr locale bundles.

Route admin control, picks, movies, stats, winners and settings UIs through i18n keys instead of hardcoded text.

Remove French text from the English UI and standardize shared messages (saving, network errors, confirmations).

Update .gitignore to ignore editor metadata and localization helper artifacts.
2026-02-01 20:20:02 -05:00

295 lines
12 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title data-i18n="picks.title">Oscar Pool - My picks</title>
<script src="./js/app_theme.js"></script>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="./css/app.css" rel="stylesheet">
<link rel="preload" as="image" href="img/logo_w.png">
<style>
/* Hide translatable content until translations are ready - prevents flash during page navigation */
body:not(.i18n-ready) [data-i18n] {
opacity: 0;
transition: opacity 0.1s ease-in;
}
body.i18n-ready [data-i18n] {
opacity: 1;
}
/* Fallback: show content after 2 seconds even if JS fails */
body:not(.i18n-ready) [data-i18n] {
animation: show-after-delay 0.1s 2s forwards;
}
@keyframes show-after-delay {
to { opacity: 1; }
}
</style>
<script src="./js/localization.js" defer></script>
<script src="./js/app_header_boot.js" defer></script>
<style>
.navbar-custom {
background-color: black;
color: white;
}
.navbar-custom .navbar-brand,
.navbar-custom .nav-link {
color: white;
}
.navbar-custom .nav-link:hover {
color: #ddd;
}
.navbar-toggler {
border-color: white;
}
.navbar-toggler-icon {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Cpath stroke='white' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");
}
.nominee-option {
cursor: pointer;
transition: all 0.2s;
position: relative;
padding: 0.5rem 0.75rem;
border-radius: 0.375rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.nominee-option:hover {
transform: translateY(-2px);
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.nominee-option.selected {
font-weight: 600;
box-shadow: 0 2px 6px rgba(13, 110, 253, 0.3);
}
.nominee-checkmark {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.3);
font-size: 0.75rem;
font-weight: bold;
}
.nominee-option.selected .nominee-checkmark {
background-color: rgba(255, 255, 255, 0.9);
color: #0d6efd;
}
.picks-table {
border-collapse: separate;
border-spacing: 0;
}
.picks-table tbody tr {
transition: background-color 0.2s;
}
.picks-table tbody tr:hover {
background-color: rgba(13, 110, 253, 0.05);
}
.picks-table tbody tr.has-selection {
background-color: rgba(13, 110, 253, 0.08);
border-left: 3px solid #0d6efd;
}
.selected-choice-badge {
font-weight: 600;
padding: 0.5rem 0.75rem;
border-radius: 0.375rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.table-sm td {
padding: 0.5rem;
vertical-align: middle;
}
.table-sm th {
padding: 0.5rem;
font-size: 0.875rem;
}
.user-picks-card {
margin-bottom: 1.5rem;
}
.pick-item {
padding: 0.5rem;
border-bottom: 1px solid #dee2e6;
}
.pick-item:last-child {
border-bottom: none;
}
.correct-pick {
background-color: #d1e7dd;
}
.incorrect-pick {
background-color: #f8d7da;
}
</style>
</head>
<body class="bg-light page-loading">
<!-- Preloaded loader overlay to avoid initial "clear" flash -->
<div id="page-loader-overlay" class="page-loader-overlay" data-preloaded="true" role="status" aria-live="polite" aria-busy="true">
<div class="page-loader-card page-loader-card--baronly">
<span class="visually-hidden" data-i18n="common.loading">Loading…</span>
<div class="page-loader-progress" aria-hidden="true">
<div class="page-loader-bar" id="page-loader-bar" style="width: 8%;"></div>
</div>
</div>
</div>
<nav class="navbar navbar-expand-lg navbar-custom">
<div class="container">
<a class="navbar-brand" href="movies.html">
<img src="img/logo_w.png" width="220" height="70" loading="eager" fetchpriority="high" decoding="async" style="max-width: 220px; height: 70px; object-fit: contain;">
</a>
<span class="navbar-text ms-auto me-2 d-lg-none navbar-user-name-mobile" data-user-name></span>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<div class="navbar-nav">
<a href="movies.html" class="nav-link btn btn-outline-light me-2" data-i18n="nav.films">Films</a>
<a href="checklist.html" class="nav-link btn btn-outline-light me-2" data-i18n="nav.checklist">Checklist</a>
<a href="user-stat.html" class="nav-link btn btn-outline-light me-2" data-i18n="nav.stats">Stats</a>
<a href="picks.html" class="nav-link btn btn-outline-light me-2" id="nav-link-picks" style="display: none;" data-i18n="nav.myPicks">My picks</a>
<a id="admin-control-link" href="control.html" class="nav-link btn btn-warning me-2 d-none" data-i18n="nav.admin">Admin</a>
</div>
<div class="d-flex ms-auto mt-2 mt-lg-0 align-items-center gap-2 navbar-user">
<span id="user-name" class="navbar-text" data-user-name></span>
<button class="theme-toggle" type="button" data-theme-toggle role="switch" aria-checked="false" data-i18n-aria-label="common.toggleDarkMode" aria-label="Toggle dark mode">
<span class="theme-toggle-icon theme-toggle-icon-sun" aria-hidden="true">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="4"></circle>
<path d="M12 2v2"></path>
<path d="M12 20v2"></path>
<path d="M4.93 4.93l1.41 1.41"></path>
<path d="M17.66 17.66l1.41 1.41"></path>
<path d="M2 12h2"></path>
<path d="M20 12h2"></path>
<path d="M4.93 19.07l1.41-1.41"></path>
<path d="M17.66 6.34l1.41-1.41"></path>
</svg>
</span>
<span class="theme-toggle-thumb" aria-hidden="true"></span>
<span class="theme-toggle-icon theme-toggle-icon-moon" aria-hidden="true">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"></path>
</svg>
</span>
</button>
<button id="log-off" class="btn btn-danger" data-i18n="nav.logout">Log out</button>
</div>
</div>
</div>
</nav>
<div class="container mt-4 pb-5">
<div id="alert-container"></div>
<!-- Tabs -->
<ul class="nav nav-tabs" id="picksTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="tab-my-picks" data-bs-toggle="tab" data-bs-target="#pane-my-picks" type="button" role="tab" aria-controls="pane-my-picks" aria-selected="true" data-i18n="picks.myPicks">
My picks
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="tab-scores" data-bs-toggle="tab" data-bs-target="#pane-scores" type="button" role="tab" aria-controls="pane-scores" aria-selected="false" data-i18n="picks.scores">
Scores
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link d-none" id="tab-winners" data-bs-toggle="tab" data-bs-target="#pane-winners" type="button" role="tab" aria-controls="pane-winners" aria-selected="false">
<span data-i18n="picks.winners">Winners</span>
<span class="badge bg-warning text-dark ms-1" data-i18n="picks.adminBadge">Admin</span>
</button>
</li>
</ul>
<div class="tab-content pt-3" id="picksTabsContent">
<!-- My Picks Tab -->
<div class="tab-pane fade show active" id="pane-my-picks" role="tabpanel" aria-labelledby="tab-my-picks" tabindex="0">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
<div>
<span class="fw-semibold"><span data-i18n="picks.year">Year:</span> <span id="current-year">—</span></span>
<span class="text-muted small ms-2"><span data-i18n="picks.progress">Progress:</span> <span id="picks-progress">0 / 0</span></span>
</div>
<div class="d-flex gap-2">
<button type="button" id="load-my-picks" class="btn btn-sm btn-outline-secondary" data-i18n="picks.reload">Reload</button>
</div>
</div>
<div id="categories-container">
<div class="text-center py-3 text-muted small" data-i18n="picks.loadingCategories">Loading categories…</div>
</div>
</div>
<!-- Scores Tab -->
<div class="tab-pane fade" id="pane-scores" role="tabpanel" aria-labelledby="tab-scores" tabindex="0">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
<div>
<span class="fw-semibold"><span data-i18n="picks.year">Year:</span> <span id="scores-year">—</span></span>
</div>
<div class="d-flex gap-2">
<button type="button" id="refresh-scores" class="btn btn-sm btn-outline-secondary" data-i18n="common.refresh">Refresh</button>
</div>
</div>
<div id="scores-container">
<div class="text-center py-3 text-muted small" data-i18n="picks.scoresCalculatedAfterWinners">Scores will be calculated after marking winners.</div>
</div>
</div>
<!-- Gagnants Tab (Admin only) -->
<div class="tab-pane fade" id="pane-winners" role="tabpanel" aria-labelledby="tab-winners" tabindex="0">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
<div>
<span class="fw-semibold"><span data-i18n="picks.year">Year:</span> <span id="winners-year">—</span></span>
<span class="text-muted small ms-2"><span id="winners-progress">0 / 0</span> <span data-i18n="picks.winnersMarked">winners marked</span></span>
</div>
<div class="d-flex gap-2">
<button type="button" id="refresh-winners" class="btn btn-sm btn-outline-secondary" data-i18n="common.refresh">Refresh</button>
</div>
</div>
<div id="winners-container">
<div class="text-center py-3 text-muted small" data-i18n="picks.loadingCategories">Loading categories…</div>
</div>
</div>
</div>
</div>
<!-- Modal for user picks comparison -->
<div class="modal fade" id="userPicksModal" tabindex="-1" aria-labelledby="userPicksModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="userPicksModalLabel" data-i18n="picks.choiceDetails">Choice details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="user-picks-modal-content">
<div class="text-center py-4 text-muted" data-i18n="common.loading">Loading...</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-i18n="common.close">Close</button>
</div>
</div>
</div>
</div>
<!-- Footer Section -->
<footer>
<p id="footer-copy"><span data-i18n="footer.oscarPool">Oscar Pool</span> <span aria-hidden="true">·</span> <a id="copy" href="https://github.com/quoije/oscars-pool">quoije</a></p>
<p id="footer-text"></p>
</footer>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.js"></script>
<script src="./js/app_picks.js"></script>
<script src="./js/app_version.js"></script>
</body>
</html>