Game — HUD Overlay (health · ammo · minimap)
A full in-game HUD overlay rendered over a CSS-drawn neon battlefield: segmented health and shield bars with low-HP pulse, ability icons with radial cooldown sweeps, a bottom-center ammo counter with reload bar and weapon name, a rotating radar minimap with hostile and ally blips, a crosshair with fire kick, an objective banner with live score, and an animated kill feed. A simulate-combat loop drives damage, burst fire, ability cooldowns, and kill-feed entries — all in vanilla HTML, CSS, and JS.
MCP
Code
:root {
--bg: #0a0b10;
--bg-2: #12131c;
--panel: #171926;
--panel-2: #1f2233;
--text: #e7e9f3;
--muted: #9aa0bf;
--line: rgba(231, 233, 243, 0.10);
--line-2: rgba(231, 233, 243, 0.18);
--accent: #00e5ff;
--accent-2: #7c4dff;
--accent-3: #ff3d71;
--success: #36e27a;
--warn: #ffc857;
--danger: #ff4d4d;
--glow: 0 0 18px rgba(0, 229, 255, 0.45);
--r-sm: 6px;
--r-md: 10px;
--r-lg: 16px;
}
* { box-sizing: border-box; }
html, body {
margin: 0;
height: 100%;
}
body {
background: var(--bg);
color: var(--text);
font-family: "Inter", system-ui, -apple-system, sans-serif;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: grid;
place-items: center;
min-height: 100vh;
padding: 16px;
}
button { font-family: inherit; cursor: pointer; }
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: var(--r-sm);
}
/* ============ STAGE + SCENE ============ */
.stage {
position: relative;
width: min(1100px, 100%);
aspect-ratio: 16 / 9;
min-height: 460px;
border-radius: var(--r-lg);
overflow: hidden;
border: 1px solid var(--line-2);
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.6), inset 0 0 0 1px rgba(255, 255, 255, 0.02);
user-select: none;
}
.scene { position: absolute; inset: 0; }
.scene-sky {
position: absolute; inset: 0;
background:
radial-gradient(120% 80% at 70% 0%, #2a1640 0%, transparent 55%),
linear-gradient(180deg, #1a0f2e 0%, #0c0a18 48%, #08070f 100%);
}
.scene-sun {
position: absolute; top: 14%; right: 24%;
width: 160px; height: 160px; border-radius: 50%;
background: radial-gradient(circle, #ff7a59 0%, #ff3d71 45%, rgba(255, 61, 113, 0) 72%);
filter: blur(2px);
opacity: 0.85;
}
.scene-ridge {
position: absolute; left: -5%; right: -5%; bottom: 38%;
height: 26%;
clip-path: polygon(0 100%, 8% 60%, 18% 80%, 30% 35%, 42% 70%, 55% 28%, 68% 66%, 80% 40%, 92% 72%, 100% 50%, 100% 100%);
}
.ridge-far { background: #1d1733; bottom: 40%; opacity: 0.7; height: 24%; }
.ridge-mid { background: #140f24; }
.scene-floor {
position: absolute; left: 0; right: 0; bottom: 0; height: 42%;
background: linear-gradient(180deg, #0d1224 0%, #07090f 100%);
}
.scene-grid {
position: absolute; left: -20%; right: -20%; bottom: 0; height: 42%;
background-image:
linear-gradient(rgba(0, 229, 255, 0.18) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 229, 255, 0.10) 1px, transparent 1px);
background-size: 100% 34px, 56px 100%;
transform: perspective(420px) rotateX(64deg);
transform-origin: bottom;
mask-image: linear-gradient(180deg, transparent, #000 35%);
animation: gridFlow 6s linear infinite;
}
@keyframes gridFlow { to { background-position: 0 34px, 0 0; } }
.scene-fog {
position: absolute; left: 0; right: 0; bottom: 28%; height: 22%;
background: radial-gradient(60% 100% at 50% 100%, rgba(124, 77, 255, 0.22), transparent 70%);
filter: blur(6px);
}
.scene-tower {
position: absolute; bottom: 36%;
background: linear-gradient(180deg, #221a3c, #0d0a1a);
border-top: 2px solid rgba(0, 229, 255, 0.4);
box-shadow: 0 0 18px rgba(0, 229, 255, 0.12);
}
.tower-a { left: 12%; width: 46px; height: 130px; }
.tower-b { left: 22%; width: 30px; height: 90px; bottom: 36%; }
.tower-c { right: 16%; width: 54px; height: 160px; }
.scene-tower::after {
content: ""; position: absolute; top: 8px; left: 50%; transform: translateX(-50%);
width: 6px; height: 6px; border-radius: 50%;
background: var(--accent-3); box-shadow: 0 0 10px var(--accent-3);
animation: blink 1.6s ease-in-out infinite;
}
@keyframes blink { 50% { opacity: 0.25; } }
/* ============ HUD ============ */
.hud {
position: absolute; inset: 0;
font-size: 13px;
pointer-events: none;
}
.hud section, .hud button { pointer-events: auto; }
.hud-vitals {
position: absolute; top: 18px; left: 18px;
width: 248px;
display: grid; gap: 10px;
}
.op-card {
display: flex; align-items: center; gap: 10px;
padding: 8px 12px 8px 8px;
background: linear-gradient(135deg, var(--panel-2), var(--panel));
border: 1px solid var(--line-2);
clip-path: polygon(0 0, 100% 0, 100% 70%, 92% 100%, 0 100%);
box-shadow: inset 0 0 14px rgba(0, 229, 255, 0.08);
}
.op-avatar {
width: 40px; height: 40px; display: grid; place-items: center;
font-family: "Orbitron", sans-serif; font-weight: 900; font-size: 14px;
color: #06121a;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
clip-path: polygon(0 0, 100% 0, 100% 78%, 78% 100%, 0 100%);
box-shadow: var(--glow);
}
.op-meta { display: grid; line-height: 1.2; }
.op-name { font-family: "Orbitron", sans-serif; font-weight: 700; letter-spacing: 0.06em; font-size: 13px; }
.op-class { color: var(--muted); font-size: 10.5px; font-weight: 500; }
.bar-block { display: grid; gap: 4px; }
.bar-head { display: flex; justify-content: space-between; align-items: baseline; }
.bar-label {
font-family: "Orbitron", sans-serif; font-weight: 700; font-size: 9.5px;
letter-spacing: 0.16em; color: var(--accent-3);
}
.bar-label.shield { color: var(--accent); }
.bar-val { font-family: "Orbitron", sans-serif; font-weight: 700; font-size: 12px; }
.bar-val i { color: var(--muted); font-style: normal; font-size: 9.5px; font-weight: 500; }
.seg-bar {
--fill: 100%;
--c1: var(--accent-3); --c2: #ff7a59;
height: 12px; position: relative;
background:
repeating-linear-gradient(90deg, transparent 0 calc(10% - 2px), rgba(0, 0, 0, 0.55) calc(10% - 2px) 10%);
border: 1px solid var(--line-2);
clip-path: polygon(0 0, 100% 0, 100% 100%, 6px 100%, 0 60%);
background-color: rgba(0, 0, 0, 0.4);
overflow: hidden;
}
.seg-bar::before {
content: ""; position: absolute; inset: 0; right: auto;
width: var(--fill);
background: linear-gradient(90deg, var(--c1), var(--c2));
box-shadow: 0 0 14px var(--c1);
transition: width 0.4s cubic-bezier(.2, .8, .2, 1);
}
.seg-bar::after {
content: ""; position: absolute; inset: 0;
background: repeating-linear-gradient(90deg, transparent 0 calc(10% - 2px), rgba(0, 0, 0, 0.7) calc(10% - 2px) 10%);
pointer-events: none;
}
.seg-bar.shield { --c1: var(--accent); --c2: #66f0ff; }
.seg-bar.low::before { animation: barPulse 0.7s ease-in-out infinite; }
@keyframes barPulse { 50% { filter: brightness(1.6); } }
/* objective */
.hud-objective {
position: absolute; top: 18px; left: 50%; transform: translateX(-50%);
display: grid; gap: 7px; justify-items: center;
}
.obj-banner {
display: flex; align-items: center; gap: 10px;
padding: 7px 16px;
background: linear-gradient(180deg, var(--panel-2), var(--panel));
border: 1px solid var(--line-2);
border-top: 2px solid var(--accent);
clip-path: polygon(10px 0, calc(100% - 10px) 0, 100% 100%, 0 100%);
box-shadow: 0 6px 22px rgba(0, 0, 0, 0.4), inset 0 0 16px rgba(0, 229, 255, 0.06);
}
.obj-tag {
font-family: "Orbitron", sans-serif; font-weight: 900; font-size: 9px;
letter-spacing: 0.2em; color: var(--accent);
}
.obj-text { font-size: 12px; color: var(--text); font-weight: 500; }
.obj-text strong { color: var(--warn); font-family: "Orbitron", sans-serif; }
.score-strip {
display: inline-flex; align-items: baseline; gap: 8px;
padding: 2px 12px; border: 1px solid var(--line);
border-radius: 999px; background: rgba(0, 0, 0, 0.35);
}
.score-k { font-size: 9px; letter-spacing: 0.18em; color: var(--muted); font-weight: 700; }
.score-v { font-family: "Orbitron", sans-serif; font-weight: 900; font-size: 14px; color: var(--accent); }
/* minimap */
.hud-minimap { position: absolute; top: 18px; right: 18px; }
.map-frame {
padding: 8px; display: grid; gap: 6px; justify-items: center;
background: linear-gradient(180deg, var(--panel-2), var(--panel));
border: 1px solid var(--line-2);
clip-path: polygon(0 0, 100% 0, 100% 100%, 18px 100%, 0 calc(100% - 18px));
box-shadow: inset 0 0 16px rgba(0, 229, 255, 0.08);
}
.radar {
position: relative; width: 124px; height: 124px; border-radius: 50%;
background: radial-gradient(circle, rgba(0, 229, 255, 0.10), rgba(8, 9, 16, 0.9) 70%);
border: 1px solid rgba(0, 229, 255, 0.35);
overflow: hidden;
}
.radar-ring {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
border: 1px solid rgba(0, 229, 255, 0.20); border-radius: 50%;
}
.radar-ring.r1 { width: 80px; height: 80px; }
.radar-ring.r2 { width: 40px; height: 40px; }
.radar-cross { position: absolute; background: rgba(0, 229, 255, 0.16); }
.radar-cross.h { left: 0; right: 0; top: 50%; height: 1px; }
.radar-cross.v { top: 0; bottom: 0; left: 50%; width: 1px; }
.radar-sweep {
position: absolute; top: 0; left: 50%; width: 50%; height: 50%;
transform-origin: left bottom;
background: conic-gradient(from 0deg at left bottom, rgba(0, 229, 255, 0.45), transparent 40%);
animation: sweep 3.4s linear infinite;
}
@keyframes sweep { to { transform: rotate(360deg); } }
.radar-self {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
width: 8px; height: 8px; border-radius: 50%;
background: var(--success); box-shadow: 0 0 8px var(--success);
}
.blip {
position: absolute; width: 7px; height: 7px; border-radius: 50%;
background: var(--accent-3); box-shadow: 0 0 8px var(--accent-3);
transform: translate(-50%, -50%);
transition: top 0.6s linear, left 0.6s linear;
}
.blip.ally { background: var(--accent); box-shadow: 0 0 8px var(--accent); }
.map-label {
display: inline-flex; align-items: center; gap: 6px;
font-family: "Orbitron", sans-serif; font-size: 8.5px; letter-spacing: 0.12em;
color: var(--muted);
}
.map-label .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--success); box-shadow: 0 0 6px var(--success); }
/* kill feed */
.kill-feed {
position: absolute; top: 92px; right: 18px;
list-style: none; margin: 0; padding: 0;
display: grid; gap: 5px; justify-items: end; width: 280px;
}
.kf-item {
display: inline-flex; align-items: center; gap: 8px;
padding: 4px 10px; font-size: 11px;
background: rgba(10, 11, 16, 0.78);
border: 1px solid var(--line); border-left: 2px solid var(--accent-3);
clip-path: polygon(8px 0, 100% 0, 100% 100%, 0 100%);
animation: kfIn 0.3s ease both;
}
@keyframes kfIn { from { opacity: 0; transform: translateX(16px); } }
.kf-item.fade { opacity: 0; transition: opacity 0.5s; }
.kf-killer { font-weight: 700; color: var(--accent); }
.kf-victim { color: var(--muted); }
.kf-wpn { color: var(--text); opacity: 0.8; }
/* crosshair */
.crosshair {
position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
width: 30px; height: 30px;
}
.crosshair.fire { animation: chKick 0.12s ease; }
@keyframes chKick { 50% { transform: translate(-50%, -50%) scale(1.4); } }
.ch { position: absolute; background: var(--accent); box-shadow: 0 0 6px var(--accent); }
.ch.t, .ch.b { left: 50%; width: 2px; height: 9px; transform: translateX(-50%); }
.ch.l, .ch.r { top: 50%; height: 2px; width: 9px; transform: translateY(-50%); }
.ch.t { top: 0; } .ch.b { bottom: 0; } .ch.l { left: 0; } .ch.r { right: 0; }
.ch.dot { top: 50%; left: 50%; width: 2px; height: 2px; border-radius: 50%; transform: translate(-50%, -50%); }
/* abilities */
.hud-abilities {
position: absolute; bottom: 18px; left: 18px;
display: flex; gap: 10px;
}
.ability {
position: relative; width: 60px; height: 60px;
display: grid; place-items: center; gap: 0;
background: linear-gradient(180deg, var(--panel-2), var(--panel));
border: 1px solid var(--line-2);
clip-path: polygon(0 12px, 12px 0, 100% 0, 100% calc(100% - 12px), calc(100% - 12px) 100%, 0 100%);
color: var(--text);
transition: transform 0.12s, box-shadow 0.2s, border-color 0.2s;
overflow: hidden;
}
.ability:hover { transform: translateY(-2px); border-color: var(--accent); box-shadow: var(--glow); }
.ability:active { transform: translateY(0) scale(0.96); }
.ab-glyph { font-size: 22px; line-height: 1; color: var(--accent); text-shadow: 0 0 10px rgba(0, 229, 255, 0.5); }
.ab-name { font-family: "Orbitron", sans-serif; font-size: 7.5px; letter-spacing: 0.12em; color: var(--muted); margin-top: 3px; }
.ab-key {
position: absolute; top: 3px; right: 4px;
font-family: "Orbitron", sans-serif; font-size: 8px; font-weight: 700;
color: var(--muted); border: 1px solid var(--line); border-radius: 3px; padding: 0 3px;
}
.ability.ult .ab-glyph { color: var(--accent-2); text-shadow: 0 0 12px rgba(124, 77, 255, 0.6); }
.ability.ult { border-color: rgba(124, 77, 255, 0.4); }
.ability.ready { animation: abReady 1.8s ease-in-out infinite; }
@keyframes abReady { 50% { box-shadow: 0 0 16px rgba(0, 229, 255, 0.4); } }
.ability.ult.ready { animation: abReadyU 1.8s ease-in-out infinite; }
@keyframes abReadyU { 50% { box-shadow: 0 0 18px rgba(124, 77, 255, 0.55); } }
.ab-cd {
position: absolute; inset: 0;
background: conic-gradient(rgba(8, 9, 16, 0.85) var(--cd, 0deg), transparent 0deg);
pointer-events: none;
opacity: 0; transition: opacity 0.2s;
}
.ability.cooling .ab-cd { opacity: 1; }
.ability.cooling { filter: grayscale(0.5); }
/* ammo */
.hud-ammo {
position: absolute; bottom: 18px; left: 50%; transform: translateX(-50%);
display: grid; justify-items: center; gap: 2px;
padding: 8px 22px 6px;
background: linear-gradient(180deg, rgba(23, 25, 38, 0.85), rgba(10, 11, 16, 0.7));
border: 1px solid var(--line-2);
clip-path: polygon(14px 0, calc(100% - 14px) 0, 100% 100%, 0 100%);
min-width: 190px;
}
.hud-ammo.reloading { animation: reloadShake 0.4s; border-color: var(--warn); }
@keyframes reloadShake { 25% { transform: translateX(-51%); } 75% { transform: translateX(-49%); } }
.weapon-name { display: flex; flex-direction: column; align-items: center; line-height: 1.1; }
.wpn { font-family: "Orbitron", sans-serif; font-weight: 700; font-size: 11px; letter-spacing: 0.1em; color: var(--accent); }
.wpn-sub { font-size: 9px; color: var(--muted); }
.ammo-counter { display: flex; align-items: baseline; gap: 4px; }
.ammo-cur { font-family: "Orbitron", sans-serif; font-weight: 900; font-size: 34px; line-height: 1; color: var(--text); text-shadow: 0 0 12px rgba(0, 229, 255, 0.3); }
.ammo-cur.empty { color: var(--danger); text-shadow: 0 0 12px rgba(255, 77, 77, 0.5); }
.ammo-sep { color: var(--muted); font-size: 18px; }
.ammo-res { font-family: "Orbitron", sans-serif; font-weight: 700; font-size: 18px; color: var(--muted); }
.reload-bar { width: 140px; height: 3px; background: rgba(0, 0, 0, 0.5); border-radius: 2px; overflow: hidden; opacity: 0; }
.reload-bar.on { opacity: 1; }
.reload-bar span { display: block; height: 100%; width: 0; background: var(--warn); box-shadow: 0 0 8px var(--warn); }
.ammo-hint {
font-family: "Orbitron", sans-serif; font-size: 8px; letter-spacing: 0.18em;
color: var(--success);
}
.ammo-hint.reload { color: var(--warn); animation: hintPulse 0.6s ease-in-out infinite; }
.ammo-hint.dry { color: var(--danger); }
@keyframes hintPulse { 50% { opacity: 0.4; } }
/* controls */
.hud-controls {
position: absolute; bottom: 18px; right: 18px;
display: grid; gap: 8px; width: 196px;
}
.ctl {
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
padding: 9px 12px;
font-family: "Orbitron", sans-serif; font-weight: 700; font-size: 11px; letter-spacing: 0.06em;
color: #06121a;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
border: none;
clip-path: polygon(8px 0, 100% 0, calc(100% - 8px) 100%, 0 100%);
transition: transform 0.12s, filter 0.2s, box-shadow 0.2s;
box-shadow: var(--glow);
}
.ctl:hover { transform: translateY(-1px); filter: brightness(1.1); }
.ctl:active { transform: translateY(0) scale(0.97); }
.ctl span { font-size: 9px; }
.ctl-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.ctl.sm {
font-size: 10px; padding: 8px 6px; color: var(--text);
background: linear-gradient(180deg, var(--panel-2), var(--panel));
border: 1px solid var(--line-2);
box-shadow: none;
}
.ctl.sm:hover { border-color: var(--accent); box-shadow: 0 0 10px rgba(0, 229, 255, 0.25); }
.ctl.sm.danger:hover { border-color: var(--danger); box-shadow: 0 0 10px rgba(255, 77, 77, 0.3); }
.ctl.sm.success:hover { border-color: var(--success); box-shadow: 0 0 10px rgba(54, 226, 122, 0.3); }
.ctl.sm.accent:hover { border-color: var(--accent-2); box-shadow: 0 0 10px rgba(124, 77, 255, 0.35); }
.ctl#btnSim.live { background: linear-gradient(135deg, var(--accent-3), var(--accent-2)); }
/* damage flash */
.stage.hit::after {
content: ""; position: absolute; inset: 0; pointer-events: none;
box-shadow: inset 0 0 120px rgba(255, 77, 77, 0.55);
animation: dmgFlash 0.45s ease;
}
@keyframes dmgFlash { from { opacity: 1; } to { opacity: 0; } }
/* toasts */
.toast-wrap {
position: fixed; bottom: 16px; left: 50%; transform: translateX(-50%);
display: grid; gap: 8px; z-index: 50; pointer-events: none;
}
.toast {
padding: 9px 16px; font-family: "Orbitron", sans-serif; font-size: 11px; letter-spacing: 0.06em;
color: var(--text);
background: linear-gradient(180deg, var(--panel-2), var(--panel));
border: 1px solid var(--line-2); border-left: 3px solid var(--accent);
border-radius: var(--r-sm);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
animation: toastIn 0.25s ease;
}
.toast.out { animation: toastOut 0.3s ease forwards; }
@keyframes toastIn { from { opacity: 0; transform: translateY(10px); } }
@keyframes toastOut { to { opacity: 0; transform: translateY(10px); } }
/* ============ RESPONSIVE ============ */
@media (max-width: 760px) {
.hud { font-size: 12px; }
.hud-vitals { width: 200px; }
.radar { width: 96px; height: 96px; }
.kill-feed { width: 200px; top: 80px; }
}
@media (max-width: 520px) {
body { padding: 8px; }
.stage { aspect-ratio: auto; height: 560px; min-height: 0; }
.hud-vitals { top: 10px; left: 10px; width: 150px; }
.op-class { display: none; }
.hud-objective { top: auto; bottom: 200px; }
.obj-text { font-size: 11px; }
.hud-minimap { top: 10px; right: 10px; }
.map-frame { padding: 5px; }
.radar { width: 78px; height: 78px; }
.map-label { display: none; }
.kill-feed { display: none; }
.hud-abilities { bottom: 132px; left: 10px; gap: 7px; }
.ability { width: 48px; height: 48px; }
.ab-glyph { font-size: 17px; }
.hud-ammo { bottom: 132px; left: 50%; min-width: 150px; padding: 6px 14px; }
.ammo-cur { font-size: 26px; }
.hud-controls { bottom: 10px; left: 10px; right: 10px; width: auto; }
}
@media (prefers-reduced-motion: reduce) {
* { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; }
.scene-grid { animation: none; }
}(function () {
"use strict";
// ---------- state ----------
var state = {
hp: 100,
hpMax: 100,
shield: 75,
shieldMax: 100,
ammo: 30,
mag: 30,
reserve: 120,
score: 0,
objective: 2,
objectiveMax: 3,
reloading: false,
};
var ABILITIES = {
dash: { cd: 4500, name: "Phase Dash" },
pulse: { cd: 7000, name: "Ion Pulse" },
ult: { cd: 16000, name: "Ashfall" },
};
var ENEMIES = ["Hollow-9", "Cinder Wraith", "Nullforge Drone", "Rift Stalker", "Ash Reaver", "Vector-X"];
var ALLIES = ["KESTREL", "VYSE", "NOMAD", "ECHO-4"];
var WEAPONS = ["Hollow Reign", "Neon Drift", "Ashen Spike", "Voidline", "Pulse Rifle"];
// ---------- elements ----------
var $ = function (id) { return document.getElementById(id); };
var stage = $("stage");
var hpBar = $("hpBar"), shBar = $("shBar");
var hpVal = $("hpVal"), shVal = $("shVal");
var ammoCur = $("ammoCur"), ammoRes = $("ammoRes");
var ammoBlock = $("ammoBlock"), ammoHint = $("ammoHint");
var reloadBar = $("reloadBar");
var scoreVal = $("scoreVal"), objCount = $("objCount");
var radar = $("radar"), killFeed = $("killFeed");
var crosshair = $("crosshair");
var toastWrap = $("toastWrap");
var abilityBtns = Array.prototype.slice.call(document.querySelectorAll(".ability"));
// ---------- toast ----------
function toast(msg, accent) {
var t = document.createElement("div");
t.className = "toast";
if (accent) t.style.borderLeftColor = accent;
t.textContent = msg;
toastWrap.appendChild(t);
setTimeout(function () {
t.classList.add("out");
setTimeout(function () { t.remove(); }, 320);
}, 2200);
}
// ---------- vitals render ----------
function renderVitals() {
var hpPct = Math.max(0, state.hp) / state.hpMax * 100;
var shPct = Math.max(0, state.shield) / state.shieldMax * 100;
hpBar.style.setProperty("--fill", hpPct + "%");
shBar.style.setProperty("--fill", shPct + "%");
hpVal.textContent = Math.max(0, Math.round(state.hp));
shVal.textContent = Math.max(0, Math.round(state.shield));
hpBar.setAttribute("aria-valuenow", Math.max(0, Math.round(state.hp)));
shBar.setAttribute("aria-valuenow", Math.max(0, Math.round(state.shield)));
hpBar.classList.toggle("low", state.hp <= 30 && state.hp > 0);
}
// ---------- damage / heal ----------
function damage(amount) {
var dmg = amount || (8 + Math.floor(Math.random() * 18));
if (state.shield > 0) {
var absorbed = Math.min(state.shield, dmg);
state.shield -= absorbed;
dmg -= absorbed;
}
if (dmg > 0) state.hp = Math.max(0, state.hp - dmg);
stage.classList.remove("hit");
void stage.offsetWidth;
stage.classList.add("hit");
renderVitals();
if (state.hp <= 0) {
toast("OPERATIVE DOWN — respawning", "#ff4d4d");
setTimeout(respawn, 900);
} else if (state.hp <= 30) {
toast("CRITICAL HEALTH", "#ff4d4d");
}
}
function heal(amount) {
var h = amount || 26;
state.hp = Math.min(state.hpMax, state.hp + h);
if (state.shield < state.shieldMax) {
state.shield = Math.min(state.shieldMax, state.shield + 14);
}
renderVitals();
toast("REGEN +" + h + " HP", "#36e27a");
}
function respawn() {
state.hp = state.hpMax;
state.shield = state.shieldMax;
state.ammo = state.mag;
state.reserve = 120;
renderVitals();
renderAmmo();
toast("RESPAWNED · SECTOR ND-12", "#00e5ff");
}
// ---------- ammo ----------
function renderAmmo() {
ammoCur.textContent = state.ammo;
ammoRes.textContent = state.reserve;
ammoCur.classList.toggle("empty", state.ammo === 0);
if (state.reloading) {
ammoHint.textContent = "RELOADING";
ammoHint.className = "ammo-hint reload";
} else if (state.ammo === 0 && state.reserve === 0) {
ammoHint.textContent = "NO AMMO";
ammoHint.className = "ammo-hint dry";
} else if (state.ammo === 0) {
ammoHint.textContent = "PRESS RELOAD";
ammoHint.className = "ammo-hint reload";
} else {
ammoHint.textContent = "FIRE-READY";
ammoHint.className = "ammo-hint";
}
}
function fire() {
if (state.reloading) return;
if (state.ammo <= 0) {
reload();
return;
}
state.ammo--;
renderAmmo();
crosshair.classList.remove("fire");
void crosshair.offsetWidth;
crosshair.classList.add("fire");
state.score += 5;
scoreVal.textContent = state.score;
if (state.ammo === 0) reload();
}
function reload() {
if (state.reloading || state.reserve <= 0) {
if (state.reserve <= 0 && state.ammo <= 0) toast("OUT OF AMMO — find resupply", "#ff4d4d");
return;
}
if (state.ammo >= state.mag) return;
state.reloading = true;
ammoBlock.classList.add("reloading");
reloadBar.classList.add("on");
var fill = reloadBar.firstElementChild;
fill.style.transition = "none";
fill.style.width = "0%";
renderAmmo();
void fill.offsetWidth;
fill.style.transition = "width 1s linear";
fill.style.width = "100%";
toast("RELOAD · " + WEAPONS[0], "#ffc857");
setTimeout(function () {
var need = state.mag - state.ammo;
var take = Math.min(need, state.reserve);
state.ammo += take;
state.reserve -= take;
state.reloading = false;
ammoBlock.classList.remove("reloading");
reloadBar.classList.remove("on");
renderAmmo();
}, 1000);
}
// ---------- abilities ----------
var cooldowns = {};
function useAbility(key) {
var btn = abilityBtns.filter(function (b) { return b.dataset.ability === key; })[0];
if (!btn || cooldowns[key]) return;
var spec = ABILITIES[key];
cooldowns[key] = true;
btn.classList.remove("ready");
btn.classList.add("cooling");
toast(spec.name.toUpperCase() + " ACTIVATED", key === "ult" ? "#7c4dff" : "#00e5ff");
if (key === "ult") {
state.score += 60;
scoreVal.textContent = state.score;
killFeedPush("VANGUARD-07", ENEMIES[Math.floor(Math.random() * ENEMIES.length)], "ASHFALL");
} else if (key === "pulse") {
state.shield = Math.min(state.shieldMax, state.shield + 30);
renderVitals();
}
var cdEl = btn.querySelector(".ab-cd");
var start = Date.now();
var iv = setInterval(function () {
var t = (Date.now() - start) / spec.cd;
if (t >= 1) {
clearInterval(iv);
cdEl.style.setProperty("--cd", "0deg");
btn.classList.remove("cooling");
btn.classList.add("ready");
cooldowns[key] = false;
toast(spec.name.toUpperCase() + " READY", "#36e27a");
return;
}
cdEl.style.setProperty("--cd", (360 * (1 - t)) + "deg");
}, 60);
}
abilityBtns.forEach(function (btn) {
btn.classList.add("ready");
btn.addEventListener("click", function () { useAbility(btn.dataset.ability); });
});
// ---------- kill feed ----------
function killFeedPush(killer, victim, wpn) {
var li = document.createElement("li");
li.className = "kf-item";
li.innerHTML =
'<span class="kf-killer"></span>' +
'<span class="kf-wpn"></span>' +
'<span class="kf-victim"></span>';
li.children[0].textContent = killer;
li.children[1].textContent = "⟪ " + wpn + " ⟫";
li.children[2].textContent = victim;
killFeed.appendChild(li);
while (killFeed.children.length > 4) killFeed.removeChild(killFeed.firstChild);
setTimeout(function () {
li.classList.add("fade");
setTimeout(function () { if (li.parentNode) li.remove(); }, 500);
}, 5000);
}
// ---------- minimap blips ----------
var blips = [];
function spawnBlips() {
for (var i = 0; i < 5; i++) {
var b = document.createElement("span");
b.className = "blip" + (i < 2 ? " ally" : "");
radar.appendChild(b);
blips.push({ el: b, a: Math.random() * Math.PI * 2, r: 14 + Math.random() * 44, spd: (Math.random() - 0.5) * 0.05 });
}
moveBlips();
}
function moveBlips() {
blips.forEach(function (b) {
b.a += b.spd;
var cx = 50 + Math.cos(b.a) * (b.r / 62 * 50);
var cy = 50 + Math.sin(b.a) * (b.r / 62 * 50);
b.el.style.left = cx + "%";
b.el.style.top = cy + "%";
});
}
setInterval(moveBlips, 600);
// ---------- simulate combat loop ----------
var simLive = false;
var simTimer = null;
function tick() {
var roll = Math.random();
if (roll < 0.35) {
damage(6 + Math.floor(Math.random() * 14));
} else if (roll < 0.55 && state.hp < 60) {
heal(12);
}
// burst fire
var shots = 2 + Math.floor(Math.random() * 4);
var fireIv = setInterval(function () {
fire();
if (--shots <= 0) clearInterval(fireIv);
}, 110);
// random ability
if (Math.random() < 0.4) {
var keys = Object.keys(ABILITIES).filter(function (k) { return !cooldowns[k]; });
if (keys.length) useAbility(keys[Math.floor(Math.random() * keys.length)]);
}
// kill feed + score
if (Math.random() < 0.7) {
var killer = Math.random() < 0.6 ? "VANGUARD-07" : ALLIES[Math.floor(Math.random() * ALLIES.length)];
killFeedPush(
killer,
ENEMIES[Math.floor(Math.random() * ENEMIES.length)],
WEAPONS[Math.floor(Math.random() * WEAPONS.length)]
);
if (killer === "VANGUARD-07") {
state.score += 35;
scoreVal.textContent = state.score;
}
}
// objective progress
if (Math.random() < 0.12 && state.objective < state.objectiveMax) {
state.objective++;
objCount.textContent = state.objective;
toast("RELAY NODE SECURED " + state.objective + "/" + state.objectiveMax, "#00e5ff");
if (state.objective >= state.objectiveMax) {
toast("OBJECTIVE COMPLETE — VICTORY", "#36e27a");
}
}
}
function toggleSim() {
var btn = $("btnSim");
simLive = !simLive;
btn.classList.toggle("live", simLive);
if (simLive) {
btn.innerHTML = "<span>■</span> Stop Simulation";
toast("COMBAT SIMULATION ENGAGED", "#ff3d71");
tick();
simTimer = setInterval(tick, 1800);
} else {
btn.innerHTML = "<span>▶</span> Simulate Combat";
clearInterval(simTimer);
toast("SIMULATION PAUSED", "#9aa0bf");
}
}
// ---------- buttons ----------
$("btnSim").addEventListener("click", toggleSim);
$("btnDamage").addEventListener("click", function () { damage(); });
$("btnHeal").addEventListener("click", function () { heal(); });
$("btnFire").addEventListener("click", fire);
$("btnAbility").addEventListener("click", function () {
var keys = Object.keys(ABILITIES).filter(function (k) { return !cooldowns[k]; });
useAbility(keys.length ? keys[0] : "dash");
});
// keyboard shortcuts
document.addEventListener("keydown", function (e) {
if (e.repeat) return;
var k = e.key.toLowerCase();
if (k === "q") useAbility("dash");
else if (k === "e") useAbility("pulse");
else if (k === "x") useAbility("ult");
else if (k === "r") reload();
else if (k === " ") { e.preventDefault(); fire(); }
});
// ---------- init ----------
renderVitals();
renderAmmo();
spawnBlips();
killFeedPush("KESTREL", "Hollow-9", "Neon Drift");
killFeedPush("VANGUARD-07", "Cinder Wraith", "Hollow Reign");
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Ashen Vanguard — HUD Overlay</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@500;700;900&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="stage" id="stage">
<!-- CSS-drawn game scene backdrop -->
<div class="scene" aria-hidden="true">
<div class="scene-sky"></div>
<div class="scene-sun"></div>
<div class="scene-ridge ridge-far"></div>
<div class="scene-ridge ridge-mid"></div>
<div class="scene-grid"></div>
<div class="scene-floor"></div>
<div class="scene-fog"></div>
<div class="scene-tower tower-a"></div>
<div class="scene-tower tower-b"></div>
<div class="scene-tower tower-c"></div>
</div>
<!-- HUD OVERLAY -->
<div class="hud" role="region" aria-label="In-game heads up display">
<!-- Top-left: vitals -->
<section class="hud-vitals" aria-label="Player vitals">
<div class="op-card">
<div class="op-avatar" aria-hidden="true">AV</div>
<div class="op-meta">
<span class="op-name">VANGUARD-07</span>
<span class="op-class">Ashen Operative · LVL 24</span>
</div>
</div>
<div class="bar-block">
<div class="bar-head">
<span class="bar-label">HEALTH</span>
<span class="bar-val"><span id="hpVal">100</span><i>/100</i></span>
</div>
<div class="seg-bar" id="hpBar" role="progressbar" aria-label="Health" aria-valuemin="0" aria-valuemax="100" aria-valuenow="100"></div>
</div>
<div class="bar-block">
<div class="bar-head">
<span class="bar-label shield">SHIELD</span>
<span class="bar-val"><span id="shVal">75</span><i>/100</i></span>
</div>
<div class="seg-bar shield" id="shBar" role="progressbar" aria-label="Shield" aria-valuemin="0" aria-valuemax="100" aria-valuenow="75"></div>
</div>
</section>
<!-- Top-center: objective banner -->
<section class="hud-objective" aria-label="Objective">
<div class="obj-banner">
<span class="obj-tag">OBJECTIVE</span>
<span class="obj-text" id="objText">Secure the Nullforge Relay — <strong id="objCount">2</strong>/3</span>
</div>
<div class="score-strip">
<span class="score-k">SCORE</span>
<span class="score-v" id="scoreVal">0</span>
</div>
</section>
<!-- Top-right: minimap / radar -->
<section class="hud-minimap" aria-label="Radar minimap">
<div class="map-frame">
<div class="radar" id="radar">
<span class="radar-ring r1"></span>
<span class="radar-ring r2"></span>
<span class="radar-cross h"></span>
<span class="radar-cross v"></span>
<span class="radar-sweep"></span>
<span class="radar-self"></span>
</div>
<div class="map-label"><span class="dot"></span> SECTOR · ND-12</div>
</div>
</section>
<!-- Kill feed -->
<ul class="kill-feed" id="killFeed" aria-label="Kill feed" aria-live="polite"></ul>
<!-- Center crosshair -->
<div class="crosshair" id="crosshair" aria-hidden="true">
<span class="ch t"></span><span class="ch r"></span><span class="ch b"></span><span class="ch l"></span>
<span class="ch dot"></span>
</div>
<!-- Bottom-left: abilities -->
<section class="hud-abilities" aria-label="Abilities">
<button class="ability" data-ability="dash" type="button" aria-label="Phase Dash ability">
<span class="ab-glyph">⟁</span>
<span class="ab-name">DASH</span>
<span class="ab-cd" aria-hidden="true"></span>
<span class="ab-key">Q</span>
</button>
<button class="ability" data-ability="pulse" type="button" aria-label="Ion Pulse ability">
<span class="ab-glyph">◎</span>
<span class="ab-name">PULSE</span>
<span class="ab-cd" aria-hidden="true"></span>
<span class="ab-key">E</span>
</button>
<button class="ability ult" data-ability="ult" type="button" aria-label="Ashfall ultimate ability">
<span class="ab-glyph">✦</span>
<span class="ab-name">ASHFALL</span>
<span class="ab-cd" aria-hidden="true"></span>
<span class="ab-key">X</span>
</button>
</section>
<!-- Bottom-center: ammo -->
<section class="hud-ammo" id="ammoBlock" aria-label="Weapon and ammo">
<div class="weapon-name">
<span class="wpn">HOLLOW REIGN</span>
<span class="wpn-sub">MK-IV · Pulse Rifle</span>
</div>
<div class="ammo-counter">
<span class="ammo-cur" id="ammoCur">30</span>
<span class="ammo-sep">/</span>
<span class="ammo-res" id="ammoRes">120</span>
</div>
<div class="reload-bar" id="reloadBar" aria-hidden="true"><span></span></div>
<div class="ammo-hint" id="ammoHint">FIRE-READY</div>
</section>
<!-- Bottom-right: controls -->
<section class="hud-controls" aria-label="Simulation controls">
<button class="ctl" id="btnSim" type="button"><span>▶</span> Simulate Combat</button>
<div class="ctl-row">
<button class="ctl sm danger" id="btnDamage" type="button">Take Damage</button>
<button class="ctl sm success" id="btnHeal" type="button">Heal</button>
</div>
<div class="ctl-row">
<button class="ctl sm" id="btnFire" type="button">Fire</button>
<button class="ctl sm accent" id="btnAbility" type="button">Use Ability</button>
</div>
</section>
</div>
</div>
<div class="toast-wrap" id="toastWrap" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>HUD Overlay (health · ammo · minimap)
A complete sci-fi shooter HUD laid over a pure-CSS game scene — perspective grid floor, glowing ridge lines, towers with blinking beacons, and a synthwave sun. The overlay covers every classic HUD region: top-left operative card with segmented health and shield bars (the health bar pulses when critical), a top-center objective banner with live score, a top-right radar minimap with a rotating sweep and drifting hostile/ally blips, a center crosshair, and a right-side kill feed that animates entries in and fades them out.
Along the bottom sit three ability buttons (Q / E / X) with conic-gradient radial cooldown sweeps and ready-state glow pulses, plus a bottom-center ammo block showing the magazine count, reserve, weapon name, and an animated reload bar with status hints (FIRE-READY, RELOADING, NO AMMO).
Everything is driven by a vanilla JS sim console: take damage (shield absorbs first, the stage flashes red), heal, fire (crosshair kick, auto-reload on empty), and use abilities — or hit “Simulate Combat” to run a full loop that deals damage, burst-fires, triggers cooldowns, pushes kill-feed entries, and advances the objective. Keyboard shortcuts (Q/E/X/R/Space) mirror real game bindings, and toasts announce events.
Illustrative UI only — fictional games, studios, characters, and data. Not engine integrations.