diff --git a/ai/tasks/done/TASK-233-add-hll-vietnam-release-countdown.md b/ai/tasks/done/TASK-233-add-hll-vietnam-release-countdown.md new file mode 100644 index 0000000..12e1831 --- /dev/null +++ b/ai/tasks/done/TASK-233-add-hll-vietnam-release-countdown.md @@ -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. diff --git a/frontend/assets/css/styles.css b/frontend/assets/css/styles.css index 637e083..0d98622 100644 --- a/frontend/assets/css/styles.css +++ b/frontend/assets/css/styles.css @@ -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; } diff --git a/frontend/assets/js/main.js b/frontend/assets/js/main.js index 745c13d..eb56f0c 100644 --- a/frontend/assets/js/main.js +++ b/frontend/assets/js/main.js @@ -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); diff --git a/frontend/index.html b/frontend/index.html index eac0736..a8d5bd4 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -86,6 +86,38 @@ allowfullscreen > +
+
+

Hell Let Loose Vietnam

+

Cuenta atras para el lanzamiento

+
+
+
+ 0 + Dias +
+
+ 0 + Horas +
+
+ 0 + Minutos +
+
+ 0 + Segundos +
+
+

+ Objetivo: 13 de agosto de 2026. +

+