Add HLL Vietnam release countdown
This commit is contained in:
73
ai/tasks/done/TASK-233-add-hll-vietnam-release-countdown.md
Normal file
73
ai/tasks/done/TASK-233-add-hll-vietnam-release-countdown.md
Normal file
@@ -0,0 +1,73 @@
|
||||
---
|
||||
id: TASK-233
|
||||
title: Add HLL Vietnam release countdown
|
||||
status: done
|
||||
type: frontend
|
||||
team: Frontend Senior
|
||||
supporting_teams: []
|
||||
roadmap_item: foundation
|
||||
priority: medium
|
||||
---
|
||||
|
||||
# TASK-233 - Add HLL Vietnam release countdown
|
||||
|
||||
## Goal
|
||||
|
||||
Add a visible frontend-only countdown to the public home page for the Hell Let Loose Vietnam release date.
|
||||
|
||||
## Context
|
||||
|
||||
The home page is `frontend/index.html` and it loads `frontend/assets/js/main.js`, not `frontend/assets/js/index.js`. The countdown should work without backend support and should not affect other pages if the markup is absent.
|
||||
|
||||
## Files Read First
|
||||
|
||||
- `ai/architecture-index.md`
|
||||
- `ai/repo-context.md`
|
||||
- `ai/orchestrator/frontend-senior.md`
|
||||
- `frontend/index.html`
|
||||
- `frontend/assets/js/main.js`
|
||||
- `frontend/assets/css/styles.css`
|
||||
|
||||
## Expected Files Modified
|
||||
|
||||
- `frontend/index.html`
|
||||
- `frontend/assets/js/main.js`
|
||||
- `frontend/assets/css/styles.css`
|
||||
- this task file
|
||||
|
||||
## Changes
|
||||
|
||||
1. Added a semantic countdown block below the main trailer video.
|
||||
2. Added `data-hll-vietnam-countdown` and `data-countdown-target`.
|
||||
3. Used target date `2026-08-13T00:00:00+02:00`.
|
||||
4. Added frontend-only countdown logic in `main.js`.
|
||||
5. Rendered days, hours, minutes and seconds every second.
|
||||
6. Stopped the interval when the target date is reached.
|
||||
7. Prevented negative values by clamping remaining time to zero.
|
||||
8. Added a final available state message: `Hell Let Loose Vietnam ya esta disponible.`
|
||||
9. Added responsive CSS integrated with the existing tactical panel style.
|
||||
|
||||
## Design Decision
|
||||
|
||||
The countdown is placed below the video. This keeps it visually tied to the HLL Vietnam trailer while avoiding changes to the public hero or server sections.
|
||||
|
||||
## Validation
|
||||
|
||||
Passed:
|
||||
|
||||
```powershell
|
||||
node --check frontend/assets/js/main.js
|
||||
```
|
||||
|
||||
The implementation is guarded by `if (!root) return;`, so pages without the countdown block do not throw errors if they load the same JS.
|
||||
|
||||
Manual logic review:
|
||||
|
||||
- Target date parsed from `data-countdown-target`.
|
||||
- Days, hours, minutes and seconds are calculated from remaining seconds.
|
||||
- Values are clamped with `Math.max(0, ...)`.
|
||||
- After the target date, the UI shows the available message and the interval is cleared.
|
||||
|
||||
## Outcome
|
||||
|
||||
The public home now shows a responsive countdown below the main video for `2026-08-13T00:00:00+02:00`. No backend file or backend configuration was changed.
|
||||
@@ -615,6 +615,76 @@ h2 {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.release-countdown {
|
||||
margin-top: 18px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 0.85fr) minmax(360px, 1.15fr);
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
padding: 18px;
|
||||
border: 1px solid rgba(210, 182, 118, 0.24);
|
||||
border-radius: 18px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(31, 35, 24, 0.92), rgba(12, 15, 11, 0.98)),
|
||||
repeating-linear-gradient(
|
||||
-32deg,
|
||||
rgba(183, 201, 125, 0.035) 0 1px,
|
||||
transparent 1px 18px
|
||||
);
|
||||
}
|
||||
|
||||
.release-countdown__copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.release-countdown__copy h3 {
|
||||
margin: 0;
|
||||
font-size: clamp(1.15rem, 1.8vw, 1.55rem);
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.release-countdown__units {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.release-countdown__units article {
|
||||
min-width: 0;
|
||||
padding: 12px 10px;
|
||||
border: 1px solid rgba(159, 168, 141, 0.18);
|
||||
border-radius: 12px;
|
||||
background: rgba(8, 10, 7, 0.58);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.release-countdown__units strong,
|
||||
.release-countdown__units span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.release-countdown__units strong {
|
||||
color: var(--accent-warm);
|
||||
font-size: clamp(1.55rem, 3.1vw, 2.4rem);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.release-countdown__units span {
|
||||
margin-top: 6px;
|
||||
color: var(--muted);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.release-countdown__status {
|
||||
grid-column: 1 / -1;
|
||||
margin: -4px 0 0;
|
||||
color: var(--text-soft);
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.servers-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 360px), 1fr));
|
||||
@@ -1645,6 +1715,14 @@ h2 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.release-countdown {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.release-countdown__units {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.stats-comparison-card__grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -98,11 +98,13 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const serversList = document.getElementById("servers-list");
|
||||
const serversBadge = document.getElementById("servers-badge");
|
||||
const communityClansList = document.getElementById("community-clans-list");
|
||||
const releaseCountdown = document.querySelector("[data-hll-vietnam-countdown]");
|
||||
|
||||
updateBackendStatus(statusNode, "Backend comprobando", "status-chip--idle");
|
||||
setServersDataState(serversBadge, { timestampLabel: "" });
|
||||
renderServersLoadingState(serversList);
|
||||
hydrateCommunityClans(communityClansList);
|
||||
initializeReleaseCountdown(releaseCountdown);
|
||||
|
||||
let serverRefreshInFlight = false;
|
||||
const refreshServers = async () => {
|
||||
@@ -365,6 +367,72 @@ function hydrateCommunityClans(listNode) {
|
||||
.join("");
|
||||
}
|
||||
|
||||
function initializeReleaseCountdown(root) {
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
const target = new Date(root.dataset.countdownTarget || "");
|
||||
if (Number.isNaN(target.getTime())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodes = {
|
||||
days: root.querySelector("[data-countdown-days]"),
|
||||
hours: root.querySelector("[data-countdown-hours]"),
|
||||
minutes: root.querySelector("[data-countdown-minutes]"),
|
||||
seconds: root.querySelector("[data-countdown-seconds]"),
|
||||
status: root.querySelector("[data-countdown-status]"),
|
||||
};
|
||||
|
||||
const render = () => {
|
||||
const remainingMs = Math.max(0, target.getTime() - Date.now());
|
||||
const remainingSeconds = Math.floor(remainingMs / 1000);
|
||||
const seconds = remainingSeconds % 60;
|
||||
const totalMinutes = Math.floor(remainingSeconds / 60);
|
||||
const minutes = totalMinutes % 60;
|
||||
const totalHours = Math.floor(totalMinutes / 60);
|
||||
const hours = totalHours % 24;
|
||||
const days = Math.floor(totalHours / 24);
|
||||
|
||||
setCountdownUnit(nodes.days, days);
|
||||
setCountdownUnit(nodes.hours, hours);
|
||||
setCountdownUnit(nodes.minutes, minutes);
|
||||
setCountdownUnit(nodes.seconds, seconds);
|
||||
|
||||
if (remainingMs === 0) {
|
||||
root.classList.add("is-available");
|
||||
if (nodes.status) {
|
||||
nodes.status.textContent = "Hell Let Loose Vietnam ya esta disponible.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
root.classList.remove("is-available");
|
||||
if (nodes.status) {
|
||||
nodes.status.textContent = "Objetivo: 13 de agosto de 2026.";
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!render()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const countdownTimer = window.setInterval(() => {
|
||||
if (!render()) {
|
||||
window.clearInterval(countdownTimer);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function setCountdownUnit(node, value) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
node.textContent = String(Math.max(0, value)).padStart(2, "0");
|
||||
}
|
||||
|
||||
function renderCommunityClanCard(clan) {
|
||||
const logoMarkup = renderClanLogo(clan);
|
||||
const discordMarkup = renderClanDiscordLink(clan);
|
||||
|
||||
@@ -86,6 +86,38 @@
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
</div>
|
||||
<section
|
||||
class="release-countdown"
|
||||
data-hll-vietnam-countdown
|
||||
data-countdown-target="2026-08-13T00:00:00+02:00"
|
||||
aria-labelledby="release-countdown-title"
|
||||
>
|
||||
<div class="release-countdown__copy">
|
||||
<p class="eyebrow eyebrow--section">Hell Let Loose Vietnam</p>
|
||||
<h3 id="release-countdown-title">Cuenta atras para el lanzamiento</h3>
|
||||
</div>
|
||||
<div class="release-countdown__units" aria-live="off">
|
||||
<article>
|
||||
<strong data-countdown-days>0</strong>
|
||||
<span>Dias</span>
|
||||
</article>
|
||||
<article>
|
||||
<strong data-countdown-hours>0</strong>
|
||||
<span>Horas</span>
|
||||
</article>
|
||||
<article>
|
||||
<strong data-countdown-minutes>0</strong>
|
||||
<span>Minutos</span>
|
||||
</article>
|
||||
<article>
|
||||
<strong data-countdown-seconds>0</strong>
|
||||
<span>Segundos</span>
|
||||
</article>
|
||||
</div>
|
||||
<p class="release-countdown__status" data-countdown-status>
|
||||
Objetivo: 13 de agosto de 2026.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user