Files
EldenRingDeathCounter/desktop.html
s0wlz (Matthias Puchstein) 45f94b1bdf init: Elden Ring death counter with multi-client server
Deno HTTP + WebSocket server serving three pages:
- / desktop with YOU DIED overlay and keyboard controls
- /mobile touch-optimized control page
- /obs transparent browser source for OBS

Count persisted to counter.json, synced in real time across all
connected clients. Compiles to a self-contained Windows .exe via
deno compile.
2026-03-18 02:37:01 +01:00

474 lines
13 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Elden Deathcounter (Römisch)</title>
<style>
:root{
--bg0:#06060a;
--bg1:#0b0b12;
--panel: rgba(12, 12, 18, .72);
--border: rgba(212, 175, 55, .30); /* Gold */
--border2: rgba(212, 175, 55, .15);
--text: rgba(245, 238, 210, .92);
--muted: rgba(245, 238, 210, .62);
--gold: rgba(212, 175, 55, .95);
--goldGlow: rgba(212, 175, 55, .38);
--shadow: 0 26px 80px rgba(0,0,0,.55);
--radius: 18px;
}
*{ box-sizing:border-box; }
body{
margin:0;
min-height:100vh;
display:grid;
place-items:center;
color:var(--text);
/* "altehrwürdig": Serif + Smallcaps-Look über CSS */
font-family: ui-serif, Georgia, "Times New Roman", Times, serif;
background:
radial-gradient(1200px 650px at 50% 18%, rgba(212,175,55,.10), transparent 55%),
radial-gradient(900px 500px at 20% 70%, rgba(180,120,40,.08), transparent 60%),
linear-gradient(180deg, var(--bg1), var(--bg0));
overflow:hidden;
}
/* dezente Asche-Körnigkeit */
body::before{
content:"";
position:fixed;
inset:0;
pointer-events:none;
background-image: radial-gradient(rgba(255,255,255,.06) 1px, transparent 1px);
background-size: 3px 3px;
opacity:.08;
mix-blend-mode: overlay;
filter: blur(.2px);
}
.frame{
width:min(780px, 92vw);
padding:26px;
border-radius: var(--radius);
background: var(--panel);
border:1px solid var(--border2);
box-shadow: var(--shadow);
backdrop-filter: blur(10px);
position:relative;
z-index:1;
}
.frame::after{
content:"";
position:absolute;
inset:-2px;
border-radius: calc(var(--radius) + 2px);
pointer-events:none;
background: radial-gradient(620px 300px at 50% 0%,
rgba(212,175,55,.18),
transparent 60%);
opacity:.95;
}
header{
display:flex;
justify-content:space-between;
align-items:baseline;
gap:14px;
position:relative;
z-index:2;
margin-bottom:16px;
}
h1{
margin:0;
font-size: 18px;
letter-spacing: 1.2px;
font-weight: 800;
color: rgba(245,238,210,.88);
text-transform: uppercase;
font-variant: small-caps;
}
.subtitle{
margin:0;
font-size: 12px;
color: var(--muted);
text-align:right;
line-height:1.25;
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
}
.bigBox{
position:relative;
z-index:2;
border:1px solid var(--border);
border-radius: 20px;
padding: 26px 18px;
display:grid;
place-items:center;
min-height: 220px;
background:
radial-gradient(740px 300px at 50% 30%, rgba(212,175,55,.12), transparent 70%),
linear-gradient(180deg, rgba(255,255,255,.04), rgba(0,0,0,.12));
box-shadow: inset 0 0 0 1px rgba(0,0,0,.35);
overflow:hidden;
}
.roman{
font-size: clamp(48px, 7.4vw, 92px);
font-weight: 900;
letter-spacing: 4px;
color: rgba(245,238,210,.94);
text-shadow:
0 0 16px rgba(212,175,55,.12),
0 0 34px rgba(212,175,55,.08);
user-select:text;
text-align:center;
padding: 0 10px;
font-variant: small-caps;
}
.arabic{
margin-top:10px;
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
font-size: 13px;
color: var(--muted);
letter-spacing:.2px;
}
.controls{
position:relative;
z-index:2;
display:flex;
gap:12px;
justify-content:center;
align-items:center;
margin-top:18px;
flex-wrap:wrap;
}
button{
appearance:none;
border:1px solid var(--border);
color: var(--text);
background: linear-gradient(180deg, rgba(212,175,55,.16), rgba(0,0,0,.18));
padding: 12px 16px;
border-radius: 14px;
font-size: 15px;
font-weight: 800;
letter-spacing:.6px;
cursor:pointer;
box-shadow: 0 10px 22px rgba(0,0,0,.35);
transition: transform .08s ease, filter .12s ease, border-color .12s ease;
min-width: 110px;
font-variant: small-caps;
font-family: ui-serif, Georgia, "Times New Roman", Times, serif;
}
button:hover{
border-color: rgba(212,175,55,.60);
filter: brightness(1.08);
}
button:active{ transform: translateY(1px) scale(0.99); }
.btnGhost{
border-color: rgba(245,238,210,.18);
background: linear-gradient(180deg, rgba(255,255,255,.05), rgba(0,0,0,.18));
min-width: 130px;
}
.mini{
margin-top:14px;
display:flex;
justify-content:space-between;
align-items:center;
gap:10px;
flex-wrap:wrap;
position:relative;
z-index:2;
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
font-size: 12px;
color: var(--muted);
}
.pill{
border:1px solid rgba(245,238,210,.14);
border-radius:999px;
padding:6px 10px;
background: rgba(255,255,255,.03);
}
kbd{
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 11px;
padding: 2px 6px;
border-radius: 6px;
border: 1px solid rgba(245,238,210,.18);
background: rgba(0,0,0,.25);
color: rgba(245,238,210,.9);
}
/* =========================
"YOU DIED" Overlay + Gold-Flame Glow
========================= */
.overlay{
position:fixed;
inset:0;
display:grid;
place-items:center;
pointer-events:none;
opacity:0;
z-index:50;
}
.overlay::before{
/* dunkle Vignette */
content:"";
position:absolute;
inset:0;
background:
radial-gradient(900px 420px at 50% 50%, rgba(0,0,0,.35), rgba(0,0,0,.78));
opacity:0;
}
.youDiedBox{
position:relative;
width:min(980px, 92vw);
padding: 34px 22px 28px;
border-top: 1px solid rgba(245,238,210,.12);
border-bottom: 1px solid rgba(245,238,210,.12);
text-align:center;
transform: translateY(10px) scale(.98);
opacity:0;
}
.youDiedMain{
margin:0;
font-size: clamp(34px, 5.2vw, 66px);
letter-spacing: 3px;
text-transform: uppercase;
font-weight: 900;
color: rgba(170, 30, 25, .92); /* dunkles Rot wie "YOU DIED" */
text-shadow:
0 0 18px rgba(0,0,0,.65),
0 0 40px rgba(170,30,25,.25);
font-variant: small-caps;
}
.youDiedSub{
margin: 10px 0 0;
font-size: clamp(16px, 2.2vw, 22px);
letter-spacing: 1.6px;
color: rgba(245,238,210,.82);
text-shadow: 0 0 18px rgba(0,0,0,.65);
font-variant: small-caps;
}
.goldFlame{
/* goldene "Flammen"-Aura */
position:absolute;
inset:-60px -40px -60px -40px;
background:
radial-gradient(340px 160px at 30% 55%, rgba(212,175,55,.22), transparent 70%),
radial-gradient(360px 170px at 70% 55%, rgba(212,175,55,.20), transparent 72%),
radial-gradient(260px 130px at 50% 45%, rgba(212,175,55,.16), transparent 72%);
filter: blur(10px);
opacity:0;
mix-blend-mode: screen;
pointer-events:none;
}
/* Animation-Trigger */
.overlay.show{
animation: overlayFade 900ms ease forwards;
}
.overlay.show::before{
animation: vignetteIn 900ms ease forwards;
}
.overlay.show .youDiedBox{
animation: boxIn 900ms ease forwards;
}
.overlay.show .goldFlame{
animation: flamePulse 900ms ease forwards;
}
@keyframes overlayFade{
0%{ opacity:0; }
12%{ opacity:1; }
78%{ opacity:1; }
100%{ opacity:0; }
}
@keyframes vignetteIn{
0%{ opacity:0; }
15%{ opacity:1; }
80%{ opacity:1; }
100%{ opacity:0; }
}
@keyframes boxIn{
0%{ opacity:0; transform: translateY(14px) scale(.985); }
18%{ opacity:1; transform: translateY(0) scale(1); }
75%{ opacity:1; transform: translateY(0) scale(1); }
100%{ opacity:0; transform: translateY(-6px) scale(1.01); }
}
@keyframes flamePulse{
0%{ opacity:0; transform: scale(.98); }
18%{ opacity:.95; transform: scale(1); }
40%{ opacity:.55; transform: scale(1.02); }
75%{ opacity:.75; transform: scale(1.01); }
100%{ opacity:0; transform: scale(1.03); }
}
/* Respektiert "Reduce Motion" */
@media (prefers-reduced-motion: reduce){
.overlay.show, .overlay.show::before, .overlay.show .youDiedBox, .overlay.show .goldFlame{
animation: none !important;
}
}
</style>
</head>
<body>
<main class="frame">
<header>
<h1>Deathcounter</h1>
<p class="subtitle">
Persistenz: <b>Server</b> (synchronisiert über WebSocket)<br>
<span style="color: rgba(212,175,55,.9)">Tasten:</span> <kbd>+</kbd>/<kbd>-</kbd> oder ↑↓ • <kbd>R</kbd> Reset
</p>
</header>
<section class="bigBox" aria-live="polite">
<div class="roman" id="roman"></div>
<div class="arabic" id="arabic">Tode: 0</div>
</section>
<div class="controls">
<button id="minus">-1</button>
<button id="plus">+1</button>
<button class="btnGhost" id="reset" title="Setzt auf 0 zurück">Reset</button>
</div>
<div class="mini">
<span class="pill" id="status">Verbinde…</span>
<span class="pill">Römisch: 13999 (0 wird als — angezeigt)</span>
</div>
</main>
<!-- Overlay -->
<div class="overlay" id="overlay" aria-hidden="true">
<div class="youDiedBox">
<div class="goldFlame"></div>
<p class="youDiedMain">YOU DIED</p>
<p class="youDiedSub">Ein weiterer Nadelstich!</p>
</div>
</div>
<script>
function toRoman(n) {
const map = [
{ val: 1000, sym: "M" },
{ val: 900, sym: "CM" },
{ val: 500, sym: "D" },
{ val: 400, sym: "CD" },
{ val: 100, sym: "C" },
{ val: 90, sym: "XC" },
{ val: 50, sym: "L" },
{ val: 40, sym: "XL" },
{ val: 10, sym: "X" },
{ val: 9, sym: "IX" },
{ val: 5, sym: "V" },
{ val: 4, sym: "IV" },
{ val: 1, sym: "I" }
];
let res = "";
for (const { val, sym } of map) {
while (n >= val) { res += sym; n -= val; }
}
return res;
}
const romanEl = document.getElementById("roman");
const arabicEl = document.getElementById("arabic");
const statusEl = document.getElementById("status");
const overlay = document.getElementById("overlay");
let currentCount = 0;
function clamp(n, min, max){ return Math.max(min, Math.min(max, n)); }
function showOverlay(){
overlay.classList.remove("show");
void overlay.offsetWidth;
overlay.classList.add("show");
}
function render(count) {
const prev = currentCount;
currentCount = clamp(count, 0, 3999);
romanEl.textContent = currentCount === 0 ? "—" : toRoman(currentCount);
arabicEl.textContent = `Tode: ${currentCount}`;
if (currentCount > prev) showOverlay();
}
// WebSocket setup with auto-reconnect
let ws;
function connect() {
ws = new WebSocket(`ws://${location.host}/ws`);
ws.onopen = () => {
statusEl.textContent = "Verbunden ✓";
statusEl.style.borderColor = "rgba(212,175,55,.35)";
setTimeout(() => (statusEl.style.borderColor = "rgba(245,238,210,.14)"), 1500);
};
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
if (typeof msg.count === "number") render(msg.count);
};
ws.onclose = () => {
statusEl.textContent = "Getrennt — verbinde neu…";
statusEl.style.borderColor = "rgba(170,30,25,.5)";
setTimeout(connect, 2000);
};
ws.onerror = () => ws.close();
}
function send(action) {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ action }));
}
}
document.getElementById("plus").addEventListener("click", () => send("increment"));
document.getElementById("minus").addEventListener("click", () => send("decrement"));
document.getElementById("reset").addEventListener("click", () => send("reset"));
window.addEventListener("keydown", (e) => {
const k = e.key;
if (k === "+" || k === "=" || k === "ArrowUp") send("increment");
else if (k === "-" || k === "_" || k === "ArrowDown") send("decrement");
else if (k.toLowerCase() === "r") send("reset");
});
connect();
</script>
</body>
</html>