luneraria-control-center/public/assets/js/map.js
Martin Stickelbroeck 0df07eefd1
Some checks are pending
CI/CD Cloud Deployment / test-and-build (push) Waiting to run
feat: Champions of Luneraria - Portal & LiveOps Schaltzentrale
- Triple-A Werbeseite mit Gameplay-Videos, 3-Lane Radar und Einheiten-Showcase
- LiveOps-Schaltzentrale mit CCU-Tracking, Download-Statistiken und Match-Balance
- Reale Engine-Benchmarks (C# vs Godot, U2-U5.5 FPS, GPU-Skinning Entlastung)
- Zero-Dependency Node.js Backend mit REST-APIs und Server-Sent Events (SSE)
- Cloud-Ready mit Dockerfile, docker-compose.yml und GitHub Actions CI/CD
2026-10-11 20:07:51 +02:00

234 lines
11 KiB
JavaScript

/**
* CHAMPIONS OF LUNERARIA - Tactical Radar 3-Lane Visualizer (Studio Grade)
* Holographic military MOBA radar with dynamic SVG pulse waves,
* contour elevation guides, and live tactical landmark inspector.
*/
const TACTICAL_REGIONS = {
mid: {
id: 'mid',
title: 'Mid-Lane (Zentralplattform)',
type: 'Direkte Offensive',
length: '61,16 Meter',
elevation: 'Plateau (Höhe 0 m)',
threat: 'Höchste Konfrontationsdichte',
preview: 'assets/img/hero-overview.png',
desc: 'Die kürzeste und blutigste Route. Führt geradlinig über die uralte Runensteinplattform direkt über die Symmetriekluft. Der Verlust der beiden Mid-Türme legt sofort die Flanken zur eigenen Basis frei.'
},
top: {
id: 'top',
title: 'Top-Lane (Hochlandkamm)',
type: 'Flankenkorridor',
length: '96,82 Meter',
elevation: 'Hochland (+8 m bis +14 m)',
threat: 'Flankenüberfälle & Grotte',
preview: 'assets/img/tuerme.png',
desc: 'Zieht im Nordwesten durch zerklüftetes Felsmassiv. Wegen des Höhenprofils und der direkten Anbindung an den Zugang zur Bossgrotte Mondschlund der ideale Pfad für Assassinen wie Sylvara.'
},
bot: {
id: 'bot',
title: 'Bottom-Lane (Deltabrücke)',
type: 'Weitfeld-Belagerung',
length: '96,82 Meter',
elevation: 'Tiefland / Flussdelta (-4 m)',
threat: 'Belagerungs- & Fernkampfzone',
preview: 'assets/img/aufmarsch.jpg',
desc: 'Führt durch die weite Flussniederung und über die massive Deltabrücke. Weitläufige Sichtachsen begünstigen Belagerungsmaschinen wie die Glasballiste und dichte Schützenformationen.'
},
grotto: {
id: 'grotto',
title: 'Bossgrotte „Mondschlund“',
type: 'Neutrales Primärziel',
length: 'Zentrales Terrassengebiet',
elevation: 'Grottensenke (+4 m)',
threat: 'Superboss Schlundkrone (6.800 HP)',
preview: 'assets/img/bosskampf.jpg',
desc: 'Auf der zentralen Symmetrieachse gelegen. Die Bezwingung der Schlundkrone schaltet eine mächtige Boss-Entsendung frei, die auf einer Wunsch-Lane als Belagerungskoloss gegen den feindlichen Ancient vorrückt.'
},
blue_ancient: {
id: 'blue_ancient',
title: 'Mondherz-Ancient (Team Blau)',
type: 'Festungskern West',
length: 'Hauptquartier',
elevation: 'Basisplateau (+6 m)',
threat: '10.000 HP · 16 Verteidiger',
preview: 'assets/img/ancient.png',
desc: 'Der pulsierende Saumkern des blauen Bündnisses, geschützt durch 2 Basistürme und drei Torriegel. Fällt dieser Kristall im Kampf, endet die Schlacht augenblicklich mit der Niederlage von Team Blau.'
},
red_ancient: {
id: 'red_ancient',
title: 'Mondherz-Ancient (Team Rot)',
type: 'Festungskern Ost',
length: 'Hauptquartier',
elevation: 'Basisplateau (+6 m)',
threat: '10.000 HP · 16 Verteidiger',
preview: 'assets/img/ancient-fall.jpg',
desc: 'Das exakt spiegelgleiche Gegenstück im Osten. Identische Verteidigungsradien, Schussfrequenzen der Türme und Aufmarschlinien garantieren 100 % faire Wettkampfbedingungen ohne Seitenvorteil.'
}
};
function renderStudioRadar(containerId) {
const container = document.getElementById(containerId);
if (!container) return;
const svgNS = 'http://www.w3.org/2000/svg';
const svg = document.createElementNS(svgNS, 'svg');
svg.setAttribute('viewBox', '0 0 880 540');
svg.setAttribute('class', 'radar-hud-svg');
svg.setAttribute('aria-label', 'Taktische Radaransicht des 3-Lane Schlachtfelds');
svg.innerHTML = `
<defs>
<!-- Background Radial Gradient -->
<radialGradient id="radarDarkGrad" cx="50%" cy="50%" r="70%">
<stop offset="0%" stop-color="#070c16" />
<stop offset="60%" stop-color="#04060b" />
<stop offset="100%" stop-color="#020306" />
</radialGradient>
<!-- Hologram Chasm Gradient -->
<linearGradient id="chasmHoloGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="rgba(168, 85, 247, 0.45)" />
<stop offset="50%" stop-color="rgba(56, 232, 216, 0.25)" />
<stop offset="100%" stop-color="rgba(168, 85, 247, 0.45)" />
</linearGradient>
<!-- Glow Filters -->
<filter id="radarCyanGlow" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="5" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
<filter id="radarGoldGlow" x="-30%" y="-30%" width="160%" height="160%">
<feGaussianBlur stdDeviation="6" result="blur" />
<feComposite in="SourceGraphic" in2="blur" operator="over" />
</filter>
</defs>
<!-- Radar Background Grid -->
<rect width="880" height="540" fill="url(#radarDarkGrad)" rx="16" />
<!-- Elevation Contour Lines & Hex Accents -->
<g opacity="0.14" stroke="#38e8d8" stroke-width="0.75" fill="none">
<!-- Concentric Range Circles -->
<circle cx="440" cy="270" r="100" stroke-dasharray="2 4" />
<circle cx="440" cy="270" r="180" />
<circle cx="440" cy="270" r="260" stroke-dasharray="4 6" />
<circle cx="440" cy="270" r="340" />
<!-- Center Crosshairs -->
<line x1="60" y1="270" x2="820" y2="270" stroke-dasharray="6 4" />
<line x1="440" y1="40" x2="440" y2="500" stroke-dasharray="6 4" />
</g>
<!-- Central Mountain Chasm (Kluftlinie) -->
<path d="M 440 40 Q 415 190 450 270 T 430 500" fill="none" stroke="url(#chasmHoloGrad)" stroke-width="32" stroke-linecap="round" opacity="0.6" />
<path d="M 440 40 Q 415 190 450 270 T 430 500" fill="none" stroke="#22d3ee" stroke-width="2.5" stroke-dasharray="8 6" opacity="0.8" />
<!-- Lane Corridors -->
<!-- Top Lane (Hochland) -->
<path id="radarTopGlow" d="M 140 270 C 160 110, 340 85, 440 85 C 540 85, 720 110, 740 270"
fill="none" stroke="#38e8d8" stroke-width="10" stroke-linecap="round" opacity="0.25" />
<path d="M 140 270 C 160 110, 340 85, 440 85 C 540 85, 720 110, 740 270"
fill="none" stroke="#38e8d8" stroke-width="2.5" stroke-dasharray="8 6" opacity="0.9" />
<!-- Mid Lane (Zentrum) -->
<path id="radarMidGlow" d="M 140 270 L 740 270"
fill="none" stroke="#0ea5e9" stroke-width="14" stroke-linecap="round" opacity="0.25" />
<path d="M 140 270 L 740 270"
fill="none" stroke="#38bdf8" stroke-width="3" stroke-dasharray="10 6" opacity="0.95" />
<!-- Bottom Lane (Delta) -->
<path id="radarBotGlow" d="M 140 270 C 160 430, 340 455, 440 455 C 540 455, 720 430, 740 270"
fill="none" stroke="#818cf8" stroke-width="10" stroke-linecap="round" opacity="0.25" />
<path d="M 140 270 C 160 430, 340 455, 440 455 C 540 455, 720 430, 740 270"
fill="none" stroke="#818cf8" stroke-width="2.5" stroke-dasharray="8 6" opacity="0.9" />
<!-- Center Runestone Platform -->
<rect x="395" y="238" width="90" height="64" rx="8" fill="#0f172a" stroke="#38e8d8" stroke-width="1.8" filter="url(#radarCyanGlow)" opacity="0.85" />
<text x="440" y="275" font-family="'Cinzel', serif" font-size="11" font-weight="800" fill="#fff" text-anchor="middle" letter-spacing="1">MID-PLATEAU</text>
<!-- Boss Grotto "Mondschlund" -->
<g class="radar-interactive-target" data-target="grotto" cursor="pointer">
<circle cx="440" cy="150" r="34" fill="#1e142e" stroke="#f59e0b" stroke-width="3" filter="url(#radarGoldGlow)" />
<polygon points="440,132 454,160 426,160" fill="#f59e0b" />
<text x="440" y="200" font-family="'Cinzel', serif" font-size="11" font-weight="800" fill="#f59e0b" text-anchor="middle" letter-spacing="1">MONDSCHLUND</text>
</g>
<!-- Blue Ancient West -->
<g class="radar-interactive-target" data-target="blue_ancient" cursor="pointer">
<circle cx="140" cy="270" r="48" fill="#08203c" stroke="#0284c7" stroke-width="3.5" filter="url(#radarCyanGlow)" />
<polygon points="140,248 158,270 140,292 122,270" fill="#38e8d8" />
<text x="140" y="338" font-family="'Cinzel', serif" font-size="11" font-weight="800" fill="#38e8d8" text-anchor="middle" letter-spacing="1">BLAUER ANCIENT</text>
</g>
<!-- Red Ancient East -->
<g class="radar-interactive-target" data-target="red_ancient" cursor="pointer">
<circle cx="740" cy="270" r="48" fill="#360d1a" stroke="#e11d48" stroke-width="3.5" filter="url(#radarCyanGlow)" />
<polygon points="740,248 758,270 740,292 722,270" fill="#f43f5e" />
<text x="740" y="338" font-family="'Cinzel', serif" font-size="11" font-weight="800" fill="#f43f5e" text-anchor="middle" letter-spacing="1">ROTER ANCIENT</text>
</g>
<!-- Tower Indicators -->
<!-- Top Lane Towers -->
<circle cx="280" cy="122" r="9" fill="#0284c7" stroke="#fff" stroke-width="1.8" />
<circle cx="600" cy="122" r="9" fill="#e11d48" stroke="#fff" stroke-width="1.8" />
<!-- Mid Lane Towers -->
<circle cx="310" cy="270" r="9" fill="#0284c7" stroke="#fff" stroke-width="1.8" />
<circle cx="570" cy="270" r="9" fill="#e11d48" stroke="#fff" stroke-width="1.8" />
<!-- Bot Lane Towers -->
<circle cx="280" cy="418" r="9" fill="#0284c7" stroke="#fff" stroke-width="1.8" />
<circle cx="600" cy="418" r="9" fill="#e11d48" stroke="#fff" stroke-width="1.8" />
<!-- Invisible broad click paths -->
<path class="radar-interactive-target" data-target="top" d="M 160 250 C 180 120, 340 75, 440 75 C 540 75, 700 120, 720 250"
fill="none" stroke="transparent" stroke-width="44" cursor="pointer" />
<line class="radar-interactive-target" data-target="mid" x1="180" y1="270" x2="700" y2="270"
stroke="transparent" stroke-width="48" cursor="pointer" />
<path class="radar-interactive-target" data-target="bot" d="M 160 290 C 180 420, 340 465, 440 465 C 540 465, 700 420, 720 290"
fill="none" stroke="transparent" stroke-width="44" cursor="pointer" />
`;
container.appendChild(svg);
// Inspector logic
const titleEl = document.getElementById('dossierTitle');
const badgeEl = document.getElementById('dossierBadge');
const lengthEl = document.getElementById('dossierLength');
const elevationEl = document.getElementById('dossierElevation');
const descEl = document.getElementById('dossierDesc');
const previewEl = document.getElementById('dossierPreviewImg');
function updateDossier(key) {
const data = TACTICAL_REGIONS[key];
if (!data) return;
if (titleEl) titleEl.textContent = data.title;
if (badgeEl) badgeEl.textContent = data.type;
if (lengthEl) lengthEl.textContent = data.length;
if (elevationEl) elevationEl.textContent = data.elevation;
if (descEl) descEl.textContent = data.desc;
if (previewEl && data.preview) previewEl.src = data.preview;
document.querySelectorAll('.radar-zone-btn').forEach(btn => {
btn.classList.toggle('is-active', btn.dataset.target === key);
});
}
svg.querySelectorAll('.radar-interactive-target').forEach(node => {
node.addEventListener('click', () => {
updateDossier(node.dataset.target);
});
});
document.querySelectorAll('.radar-zone-btn').forEach(btn => {
btn.addEventListener('click', () => {
updateDossier(btn.dataset.target);
});
});
}
document.addEventListener('DOMContentLoaded', () => {
renderStudioRadar('radarScreenContainer');
});