echo6-docs/projects/mmud/last-ember.html
Matt Johnson e9231ac24a Migration: consolidate Echo6 docs to cortex with full infrastructure cleanup sync
- Documents recent infrastructure cleanup (8 CTs destroyed, 35 DNS records removed, Headscale cleanup)
- Adds 24 new runbooks covering Authentik, PeerTube, Meshtastic, RECON, Proxmox, Mailcow, Internet Archive, GPU routing
- Adds project documentation for headscale, vaultwarden, peertube, matrix, mmud, advbbs, arr stack
- Updates services.md, environment.md, caddy.md, authentik.md to match live infrastructure
- Removes 4 deprecated runbook duplicates (canonical versions live in projects/)
- Adds .gitignore for binary archives and editor temp files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 06:02:16 +00:00

1442 lines
38 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="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>The Last Ember — meshMUD</title>
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700&family=Crimson+Text:ital,wght@0,400;0,600;1,400&family=JetBrains+Mono:wght@300;400;500&display=swap" rel="stylesheet">
<style>
:root {
--ember: #e8713a;
--ember-glow: #ff9d5c;
--ember-deep: #c44e1a;
--ash: #1a1714;
--charcoal: #0d0b09;
--smoke: #2a2520;
--smoke-light: #3d3630;
--parchment: #d4c4a8;
--parchment-dark: #b8a88c;
--parchment-faded: #a89878;
--bone: #c8b898;
--blood: #8b2020;
--blood-bright: #cc3333;
--gold: #c4a44a;
--gold-dim: #8a7a3a;
--frost: #7a9ab0;
--poison: #5a8a4a;
--text-bright: #e8dcc8;
--text-dim: #9a8e78;
--text-ghost: #5a5244;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
background: var(--charcoal);
color: var(--text-bright);
font-family: 'Crimson Text', Georgia, serif;
min-height: 100vh;
overflow-x: hidden;
}
/* ═══ EMBER PARTICLE CANVAS ═══ */
#ember-canvas {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
pointer-events: none;
z-index: 0;
opacity: 0.6;
}
/* ═══ TEXTURE OVERLAY ═══ */
body::before {
content: '';
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background:
repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0,0,0,0.03) 2px,
rgba(0,0,0,0.03) 4px
);
pointer-events: none;
z-index: 1;
}
.page-wrap {
position: relative;
z-index: 2;
max-width: 1000px;
margin: 0 auto;
padding: 0 24px;
}
/* ═══ HEADER ═══ */
.tavern-header {
text-align: center;
padding: 60px 0 20px;
position: relative;
}
.tavern-sigil {
width: 48px;
height: 48px;
margin: 0 auto 20px;
position: relative;
}
.tavern-sigil::before {
content: '🜂';
font-size: 42px;
display: block;
filter: drop-shadow(0 0 12px rgba(232, 113, 58, 0.6));
animation: sigil-pulse 4s ease-in-out infinite;
}
@keyframes sigil-pulse {
0%, 100% { opacity: 0.7; filter: drop-shadow(0 0 8px rgba(232, 113, 58, 0.4)); }
50% { opacity: 1; filter: drop-shadow(0 0 16px rgba(232, 113, 58, 0.8)); }
}
.tavern-name {
font-family: 'Cinzel', serif;
font-size: clamp(28px, 5vw, 44px);
font-weight: 700;
letter-spacing: 0.12em;
color: var(--parchment);
text-shadow:
0 0 40px rgba(232, 113, 58, 0.3),
0 2px 4px rgba(0,0,0,0.5);
margin-bottom: 6px;
}
.tavern-subtitle {
font-family: 'Cinzel', serif;
font-size: 13px;
letter-spacing: 0.3em;
color: var(--text-ghost);
text-transform: uppercase;
}
.divider {
display: flex;
align-items: center;
gap: 16px;
margin: 28px 0;
color: var(--text-ghost);
font-size: 11px;
letter-spacing: 0.2em;
}
.divider::before, .divider::after {
content: '';
flex: 1;
height: 1px;
background: linear-gradient(90deg, transparent, var(--smoke-light), transparent);
}
/* ═══ EPOCH STATUS BAR ═══ */
.epoch-bar {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
gap: 20px;
padding: 16px 24px;
background: linear-gradient(135deg, rgba(26,23,20,0.9), rgba(42,37,32,0.7));
border: 1px solid var(--smoke-light);
border-radius: 2px;
margin-bottom: 32px;
position: relative;
}
.epoch-bar::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, var(--ember), transparent);
opacity: 0.4;
}
.epoch-stat {
text-align: center;
}
.epoch-stat .label {
font-family: 'Cinzel', serif;
font-size: 9px;
letter-spacing: 0.25em;
color: var(--text-ghost);
text-transform: uppercase;
margin-bottom: 4px;
}
.epoch-stat .value {
font-family: 'JetBrains Mono', monospace;
font-size: 18px;
font-weight: 500;
color: var(--parchment);
}
.epoch-stat .value.ember { color: var(--ember-glow); }
.epoch-stat .value.gold { color: var(--gold); }
.epoch-center-divider {
width: 1px;
height: 36px;
background: var(--smoke-light);
}
.epoch-mode-badge {
display: inline-block;
padding: 3px 12px;
border: 1px solid var(--gold-dim);
font-family: 'Cinzel', serif;
font-size: 11px;
letter-spacing: 0.15em;
color: var(--gold);
margin-top: 4px;
}
.breach-status {
display: inline-flex;
align-items: center;
gap: 6px;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--text-ghost);
margin-top: 4px;
}
.breach-status .dot {
width: 6px; height: 6px;
border-radius: 50%;
background: var(--text-ghost);
}
.breach-status .dot.open {
background: var(--ember);
box-shadow: 0 0 6px var(--ember);
animation: dot-pulse 2s ease-in-out infinite;
}
.breach-status .dot.sealed {
background: var(--smoke-light);
}
@keyframes dot-pulse {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; }
}
/* ═══ MAIN GRID ═══ */
.board-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
margin-bottom: 32px;
}
@media (max-width: 700px) {
.board-grid { grid-template-columns: 1fr; }
}
.board-panel {
background: linear-gradient(180deg, rgba(26,23,20,0.95), rgba(13,11,9,0.95));
border: 1px solid var(--smoke-light);
border-radius: 2px;
overflow: hidden;
position: relative;
}
.board-panel::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, var(--smoke-light), transparent);
}
.panel-header {
padding: 14px 18px 10px;
border-bottom: 1px solid rgba(90,82,68,0.2);
display: flex;
align-items: center;
justify-content: space-between;
}
.panel-title {
font-family: 'Cinzel', serif;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.2em;
color: var(--parchment-faded);
text-transform: uppercase;
}
.panel-icon {
font-size: 14px;
opacity: 0.5;
}
.panel-body {
padding: 14px 18px 18px;
}
/* ═══ LEADERBOARD ═══ */
.leaderboard-entry {
display: grid;
grid-template-columns: 20px 1fr auto;
align-items: center;
gap: 10px;
padding: 8px 0;
border-bottom: 1px solid rgba(90,82,68,0.1);
}
.leaderboard-entry:last-child { border-bottom: none; }
.lb-rank {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--text-ghost);
text-align: center;
}
.lb-rank.first { color: var(--gold); }
.lb-rank.second { color: var(--parchment-dark); }
.lb-rank.third { color: var(--ember-deep); }
.lb-name {
font-family: 'Crimson Text', serif;
font-size: 15px;
color: var(--text-bright);
}
.lb-title {
font-size: 12px;
font-style: italic;
color: var(--text-ghost);
}
.lb-stats {
display: flex;
gap: 12px;
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--text-dim);
}
.lb-stat-val { color: var(--parchment-dark); }
.class-badge {
display: inline-block;
padding: 1px 5px;
border-radius: 1px;
font-family: 'JetBrains Mono', monospace;
font-size: 9px;
letter-spacing: 0.05em;
margin-left: 6px;
vertical-align: middle;
}
.class-badge.fighter { background: rgba(139,32,32,0.3); color: var(--blood-bright); border: 1px solid rgba(139,32,32,0.4); }
.class-badge.caster { background: rgba(90,138,74,0.2); color: var(--poison); border: 1px solid rgba(90,138,74,0.3); }
.class-badge.rogue { background: rgba(122,154,176,0.2); color: var(--frost); border: 1px solid rgba(122,154,176,0.3); }
/* ═══ BROADCAST LOG ═══ */
.broadcast-log {
max-height: 320px;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: var(--smoke-light) transparent;
}
.broadcast-entry {
padding: 7px 0;
border-bottom: 1px solid rgba(90,82,68,0.08);
display: flex;
gap: 8px;
align-items: flex-start;
animation: broadcast-in 0.4s ease-out;
}
@keyframes broadcast-in {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
.broadcast-entry:last-child { border-bottom: none; }
.bc-icon { font-size: 13px; flex-shrink: 0; margin-top: 1px; }
.bc-text {
font-size: 14px;
line-height: 1.4;
color: var(--text-dim);
}
.bc-text .bc-name { color: var(--parchment); font-weight: 600; }
.bc-text .bc-highlight { color: var(--ember-glow); }
.bc-text .bc-item { color: var(--gold); font-style: italic; }
.bc-time {
font-family: 'JetBrains Mono', monospace;
font-size: 9px;
color: var(--text-ghost);
margin-left: auto;
flex-shrink: 0;
margin-top: 3px;
}
/* ═══ BOUNTY BOARD ═══ */
.bounty-card {
padding: 12px 0;
border-bottom: 1px solid rgba(90,82,68,0.15);
}
.bounty-card:last-child { border-bottom: none; }
.bounty-name {
font-family: 'Cinzel', serif;
font-size: 13px;
color: var(--parchment);
margin-bottom: 2px;
}
.bounty-location {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--text-ghost);
margin-bottom: 8px;
}
.hp-bar-wrap {
height: 8px;
background: var(--smoke);
border-radius: 1px;
overflow: hidden;
position: relative;
margin-bottom: 4px;
}
.hp-bar-fill {
height: 100%;
border-radius: 1px;
transition: width 1s ease;
position: relative;
}
.hp-bar-fill.high { background: linear-gradient(90deg, var(--blood), var(--blood-bright)); }
.hp-bar-fill.mid { background: linear-gradient(90deg, var(--ember-deep), var(--ember)); }
.hp-bar-fill.low { background: linear-gradient(90deg, var(--gold-dim), var(--gold)); }
.hp-bar-fill::after {
content: '';
position: absolute;
right: 0; top: 0; bottom: 0;
width: 20px;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.15));
}
.bounty-hp-text {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--text-dim);
display: flex;
justify-content: space-between;
}
.bounty-contributors {
font-size: 11px;
color: var(--text-ghost);
margin-top: 4px;
font-style: italic;
}
/* ═══ MODE STATUS (FULL WIDTH) ═══ */
.board-full {
grid-column: 1 / -1;
}
/* Hold the Line bars */
.floor-row {
display: grid;
grid-template-columns: 70px 1fr 50px;
align-items: center;
gap: 12px;
padding: 8px 0;
}
.floor-label {
font-family: 'Cinzel', serif;
font-size: 11px;
color: var(--text-dim);
letter-spacing: 0.1em;
}
.floor-bar-wrap {
height: 14px;
background: var(--smoke);
border-radius: 1px;
overflow: hidden;
position: relative;
}
.floor-bar-fill {
height: 100%;
transition: width 1.5s ease;
position: relative;
}
.floor-bar-fill.f1 { background: linear-gradient(90deg, #2a4a2a, #4a8a4a); }
.floor-bar-fill.f2 { background: linear-gradient(90deg, #4a3a1a, #8a7a3a); }
.floor-bar-fill.f3 { background: linear-gradient(90deg, #5a2a1a, #c44e1a); }
.floor-bar-fill.f4 { background: linear-gradient(90deg, #3a1a1a, #8b2020); }
.floor-checkpoint {
position: absolute;
top: 0; bottom: 0;
width: 2px;
background: var(--gold);
opacity: 0.6;
}
.floor-checkpoint.locked {
opacity: 1;
box-shadow: 0 0 4px var(--gold);
}
.floor-pct {
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
color: var(--text-dim);
text-align: right;
}
/* Raid boss bar */
.raid-boss-section { text-align: center; padding: 8px 0; }
.raid-boss-name {
font-family: 'Cinzel', serif;
font-size: 18px;
font-weight: 600;
color: var(--parchment);
margin-bottom: 4px;
}
.raid-boss-phase {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--ember);
letter-spacing: 0.15em;
margin-bottom: 12px;
}
.raid-hp-bar-wrap {
height: 20px;
background: var(--smoke);
border-radius: 1px;
overflow: hidden;
position: relative;
margin-bottom: 6px;
}
.raid-hp-fill {
height: 100%;
background: linear-gradient(90deg, var(--blood), var(--blood-bright), var(--ember-deep));
transition: width 2s ease;
position: relative;
}
.raid-hp-fill::after {
content: '';
position: absolute;
inset: 0;
background: repeating-linear-gradient(
90deg,
transparent,
transparent 30px,
rgba(0,0,0,0.15) 30px,
rgba(0,0,0,0.15) 31px
);
}
.raid-hp-markers {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
pointer-events: none;
}
.raid-phase-marker {
position: absolute;
top: 0; bottom: 0;
width: 2px;
background: rgba(255,255,255,0.3);
}
.raid-hp-text {
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
color: var(--text-dim);
}
.raid-contributors {
font-size: 12px;
color: var(--text-ghost);
margin-top: 8px;
font-style: italic;
}
/* R&E carrier tracker */
.re-tracker {
display: flex;
align-items: center;
gap: 4px;
padding: 12px 0;
justify-content: center;
}
.re-floor {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.re-floor-label {
font-family: 'Cinzel', serif;
font-size: 9px;
color: var(--text-ghost);
letter-spacing: 0.1em;
}
.re-rooms {
display: flex;
gap: 2px;
}
.re-room {
width: 12px; height: 12px;
border: 1px solid var(--smoke-light);
border-radius: 1px;
background: var(--smoke);
position: relative;
transition: all 0.3s ease;
}
.re-room.carrier {
background: var(--ember);
border-color: var(--ember-glow);
box-shadow: 0 0 6px var(--ember);
}
.re-room.pursuer {
background: var(--blood);
border-color: var(--blood-bright);
box-shadow: 0 0 6px var(--blood);
}
.re-room.cleared {
background: rgba(74,138,74,0.3);
border-color: rgba(74,138,74,0.5);
}
.re-room.warded {
background: rgba(90,138,74,0.2);
border-color: var(--poison);
}
.re-floor-sep {
width: 20px;
height: 1px;
background: var(--smoke-light);
margin: 0 4px;
margin-top: 14px;
}
.re-legend {
display: flex;
gap: 16px;
justify-content: center;
margin-top: 12px;
font-family: 'JetBrains Mono', monospace;
font-size: 9px;
color: var(--text-ghost);
}
.re-legend-item {
display: flex;
align-items: center;
gap: 4px;
}
.re-legend-dot {
width: 8px; height: 8px;
border-radius: 1px;
}
.re-legend-dot.carrier-dot { background: var(--ember); }
.re-legend-dot.pursuer-dot { background: var(--blood); }
.re-legend-dot.cleared-dot { background: rgba(74,138,74,0.5); }
.re-status-text {
text-align: center;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--ember-glow);
margin-top: 8px;
}
/* ═══ NPC STRIP ═══ */
.npc-strip {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16px;
margin-bottom: 32px;
}
@media (max-width: 700px) {
.npc-strip { grid-template-columns: repeat(2, 1fr); }
}
.npc-card {
text-align: center;
padding: 16px 12px;
background: linear-gradient(180deg, rgba(42,37,32,0.4), rgba(13,11,9,0.6));
border: 1px solid rgba(90,82,68,0.15);
border-radius: 2px;
transition: border-color 0.3s ease;
}
.npc-card:hover {
border-color: rgba(232,113,58,0.3);
}
.npc-sigil {
font-size: 24px;
margin-bottom: 8px;
opacity: 0.7;
}
.npc-name {
font-family: 'Cinzel', serif;
font-size: 13px;
font-weight: 600;
color: var(--parchment);
letter-spacing: 0.1em;
margin-bottom: 2px;
}
.npc-role {
font-family: 'JetBrains Mono', monospace;
font-size: 9px;
color: var(--text-ghost);
letter-spacing: 0.15em;
text-transform: uppercase;
}
/* ═══ FOOTER ═══ */
.tavern-footer {
text-align: center;
padding: 24px 0 48px;
border-top: 1px solid rgba(90,82,68,0.15);
}
.footer-mesh {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--text-ghost);
letter-spacing: 0.15em;
margin-bottom: 16px;
}
.footer-mesh a {
color: var(--text-ghost);
text-decoration: none;
transition: color 0.2s;
}
.footer-mesh a:hover { color: var(--ember); }
.admin-link {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 20px;
background: transparent;
border: 1px solid var(--smoke-light);
color: var(--text-ghost);
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
letter-spacing: 0.15em;
text-decoration: none;
text-transform: uppercase;
cursor: pointer;
transition: all 0.3s ease;
border-radius: 1px;
}
.admin-link:hover {
border-color: var(--ember-deep);
color: var(--ember);
background: rgba(232,113,58,0.05);
}
.admin-lock {
font-size: 11px;
opacity: 0.5;
}
/* ═══ ADMIN MODAL ═══ */
.modal-overlay {
display: none;
position: fixed;
inset: 0;
background: rgba(0,0,0,0.85);
z-index: 100;
justify-content: center;
align-items: center;
backdrop-filter: blur(4px);
}
.modal-overlay.active { display: flex; }
.modal-box {
background: var(--ash);
border: 1px solid var(--smoke-light);
padding: 36px 32px;
width: 340px;
text-align: center;
position: relative;
animation: modal-in 0.3s ease-out;
}
@keyframes modal-in {
from { opacity: 0; transform: translateY(12px); }
to { opacity: 1; transform: translateY(0); }
}
.modal-box::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, var(--ember), transparent);
opacity: 0.5;
}
.modal-title {
font-family: 'Cinzel', serif;
font-size: 14px;
letter-spacing: 0.2em;
color: var(--parchment-faded);
margin-bottom: 24px;
}
.modal-input {
width: 100%;
padding: 10px 14px;
background: var(--charcoal);
border: 1px solid var(--smoke-light);
color: var(--text-bright);
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
outline: none;
margin-bottom: 12px;
border-radius: 1px;
transition: border-color 0.2s;
}
.modal-input:focus {
border-color: var(--ember-deep);
}
.modal-input::placeholder {
color: var(--text-ghost);
}
.modal-btn {
width: 100%;
padding: 10px;
background: linear-gradient(135deg, var(--ember-deep), var(--ember));
border: none;
color: var(--charcoal);
font-family: 'Cinzel', serif;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.15em;
cursor: pointer;
transition: all 0.2s;
border-radius: 1px;
}
.modal-btn:hover {
filter: brightness(1.1);
transform: translateY(-1px);
}
.modal-close {
position: absolute;
top: 12px; right: 14px;
background: none;
border: none;
color: var(--text-ghost);
font-size: 18px;
cursor: pointer;
transition: color 0.2s;
}
.modal-close:hover { color: var(--text-bright); }
.modal-error {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--blood-bright);
margin-top: 8px;
min-height: 14px;
}
/* ═══ DAILY DIGEST ═══ */
.digest-bar {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 18px;
background: rgba(196,164,74,0.05);
border: 1px solid rgba(196,164,74,0.15);
border-radius: 2px;
margin-bottom: 24px;
font-size: 13px;
color: var(--text-dim);
}
.digest-icon { font-size: 14px; }
.digest-text .digest-hl { color: var(--parchment); font-weight: 600; }
/* ═══ SECRETS COUNTER ═══ */
.secrets-bar {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 12px;
padding-top: 10px;
border-top: 1px solid rgba(90,82,68,0.1);
}
.secrets-label {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
color: var(--text-ghost);
letter-spacing: 0.1em;
}
.secrets-dots {
display: flex;
gap: 3px;
}
.secret-dot {
width: 6px; height: 6px;
border-radius: 50%;
background: var(--smoke);
border: 1px solid var(--smoke-light);
transition: all 0.3s;
}
.secret-dot.found {
background: var(--gold);
border-color: var(--gold);
box-shadow: 0 0 4px rgba(196,164,74,0.4);
}
/* scrollbar */
.broadcast-log::-webkit-scrollbar { width: 4px; }
.broadcast-log::-webkit-scrollbar-track { background: transparent; }
.broadcast-log::-webkit-scrollbar-thumb { background: var(--smoke-light); border-radius: 2px; }
/* ═══ LOADING STATES ═══ */
.skeleton {
background: linear-gradient(90deg, var(--smoke) 25%, var(--smoke-light) 50%, var(--smoke) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 2px;
}
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
</style>
</head>
<body>
<canvas id="ember-canvas"></canvas>
<div class="page-wrap">
<!-- HEADER -->
<header class="tavern-header">
<div class="tavern-sigil"></div>
<h1 class="tavern-name">The Last Ember</h1>
<div class="tavern-subtitle">meshMUD</div>
</header>
<div class="divider"></div>
<!-- EPOCH STATUS BAR -->
<div class="epoch-bar">
<div class="epoch-stat">
<div class="label">Epoch Day</div>
<div class="value ember" id="epoch-day">17</div>
<div class="breach-status">
<span class="dot open" id="breach-dot"></span>
<span id="breach-text">BREACH OPEN</span>
</div>
</div>
<div class="epoch-center-divider"></div>
<div class="epoch-stat">
<div class="label">Active Mode</div>
<div class="epoch-mode-badge" id="epoch-mode">HOLD THE LINE</div>
<div style="margin-top:6px">
<span class="breach-status">
<span style="font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--text-ghost);" id="players-online">7 adventurers this epoch</span>
</span>
</div>
</div>
<div class="epoch-center-divider"></div>
<div class="epoch-stat">
<div class="label">Days Remain</div>
<div class="value gold" id="days-remain">13</div>
<div class="breach-status">
<span style="font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--text-ghost);" id="epoch-timer">ends Mar 10</span>
</div>
</div>
</div>
<!-- DAILY DIGEST -->
<div class="digest-bar">
<span class="digest-icon">📜</span>
<span class="digest-text">
<span class="digest-hl">Day 16:</span> 3 battles fought, 1 death. The Bounty Troll fell at last.
<span class="digest-hl">Kael</span> leads at Lv8. Floor 2 holding at 71%.
</span>
</div>
<!-- MAIN GRID -->
<div class="board-grid">
<!-- LEADERBOARD -->
<div class="board-panel">
<div class="panel-header">
<span class="panel-title">Leaderboard</span>
<span class="panel-icon"></span>
</div>
<div class="panel-body">
<div class="leaderboard-entry">
<span class="lb-rank first">1</span>
<div>
<span class="lb-name">Kael</span>
<span class="class-badge fighter">FGT</span>
<div class="lb-title">the Unyielding</div>
</div>
<div class="lb-stats">
<span>Lv<span class="lb-stat-val">8</span></span>
<span>K:<span class="lb-stat-val">47</span></span>
<span>S:<span class="lb-stat-val">9</span></span>
</div>
</div>
<div class="leaderboard-entry">
<span class="lb-rank second">2</span>
<div>
<span class="lb-name">Mira</span>
<span class="class-badge caster">CST</span>
<div class="lb-title">the Keen-Eyed</div>
</div>
<div class="lb-stats">
<span>Lv<span class="lb-stat-val">7</span></span>
<span>K:<span class="lb-stat-val">31</span></span>
<span>S:<span class="lb-stat-val">12</span></span>
</div>
</div>
<div class="leaderboard-entry">
<span class="lb-rank third">3</span>
<div>
<span class="lb-name">Torr</span>
<span class="class-badge rogue">ROG</span>
<div class="lb-title">the Quiet</div>
</div>
<div class="lb-stats">
<span>Lv<span class="lb-stat-val">6</span></span>
<span>K:<span class="lb-stat-val">28</span></span>
<span>S:<span class="lb-stat-val">7</span></span>
</div>
</div>
<div class="leaderboard-entry">
<span class="lb-rank">4</span>
<div>
<span class="lb-name">Sable</span>
<span class="class-badge fighter">FGT</span>
</div>
<div class="lb-stats">
<span>Lv<span class="lb-stat-val">5</span></span>
<span>K:<span class="lb-stat-val">19</span></span>
<span>S:<span class="lb-stat-val">4</span></span>
</div>
</div>
<div class="leaderboard-entry">
<span class="lb-rank">5</span>
<div>
<span class="lb-name">Dren</span>
<span class="class-badge caster">CST</span>
</div>
<div class="lb-stats">
<span>Lv<span class="lb-stat-val">4</span></span>
<span>K:<span class="lb-stat-val">12</span></span>
<span>S:<span class="lb-stat-val">3</span></span>
</div>
</div>
<!-- Secrets found (server-wide) -->
<div class="secrets-bar">
<span class="secrets-label">SECRETS FOUND: 11/20</span>
<div class="secrets-dots" id="secret-dots">
<!-- filled by JS -->
</div>
</div>
</div>
</div>
<!-- BROADCAST LOG -->
<div class="board-panel">
<div class="panel-header">
<span class="panel-title">Broadcasts</span>
<span class="panel-icon">📡</span>
</div>
<div class="panel-body">
<div class="broadcast-log" id="broadcast-log">
<div class="broadcast-entry">
<span class="bc-icon">🏰</span>
<span class="bc-text">Floor 2 <span class="bc-highlight">Checkpoint Alpha</span> established. The darkness cannot pass.</span>
<span class="bc-time">2h</span>
</div>
<div class="broadcast-entry">
<span class="bc-icon">🎯</span>
<span class="bc-text"><span class="bc-name">Kael</span> finished the <span class="bc-highlight">Bounty Troll</span>. Contributors: Kael, Mira, Torr.</span>
<span class="bc-time">4h</span>
</div>
<div class="broadcast-entry">
<span class="bc-icon">🔍</span>
<span class="bc-text"><span class="bc-name">Mira</span> activated the <span class="bc-item">Ancient Ward</span>! Dungeon regen halved for 24h.</span>
<span class="bc-time">5h</span>
</div>
<div class="broadcast-entry">
<span class="bc-icon">💀</span>
<span class="bc-text"><span class="bc-name">Sable</span> fell on Floor 2. 34g lost to the depths.</span>
<span class="bc-time">6h</span>
</div>
<div class="broadcast-entry">
<span class="bc-icon"></span>
<span class="bc-text">The Breach stirs. Strange light pulses from between Floors 2 and 3.</span>
<span class="bc-time">8h</span>
</div>
<div class="broadcast-entry">
<span class="bc-icon"></span>
<span class="bc-text"><span class="bc-name">Torr</span> reached <span class="bc-highlight">Level 6</span>. New ability unlocked: <span class="bc-item">Ambush</span>.</span>
<span class="bc-time">11h</span>
</div>
<div class="broadcast-entry">
<span class="bc-icon"></span>
<span class="bc-text">Floor 2 lost 2 rooms. Frontline at Room 9. Next regen in ~6h.</span>
<span class="bc-time">14h</span>
</div>
<div class="broadcast-entry">
<span class="bc-icon">🔍</span>
<span class="bc-text"><span class="bc-name">Dren</span> found a hidden shortcut on Floor 1.</span>
<span class="bc-time">1d</span>
</div>
<div class="broadcast-entry">
<span class="bc-icon">🎯</span>
<span class="bc-text">New bounty: <span class="bc-highlight">Clear the Spider Nest</span> on Floor 2 (0/6 spiders).</span>
<span class="bc-time">1d</span>
</div>
</div>
</div>
</div>
<!-- BOUNTY BOARD -->
<div class="board-panel">
<div class="panel-header">
<span class="panel-title">Bounty Board</span>
<span class="panel-icon">🎯</span>
</div>
<div class="panel-body">
<div class="bounty-card">
<div class="bounty-name">Ironhide Basilisk</div>
<div class="bounty-location">Floor 2 · Room 14 · The Sunken Gallery</div>
<div class="hp-bar-wrap">
<div class="hp-bar-fill mid" style="width:62%"></div>
</div>
<div class="bounty-hp-text">
<span>124 / 200 HP</span>
<span>regen 10hp/8h</span>
</div>
<div class="bounty-contributors">Kael, Mira, Sable contributing</div>
</div>
<div class="bounty-card">
<div class="bounty-name">Spider Nest</div>
<div class="bounty-location">Floor 2 · Eastern Branch</div>
<div class="hp-bar-wrap">
<div class="hp-bar-fill high" style="width:83%"></div>
</div>
<div class="bounty-hp-text">
<span>5 / 6 spiders remain</span>
<span>kill bounty</span>
</div>
<div class="bounty-contributors">Torr contributing</div>
</div>
<div class="bounty-card" style="opacity:0.4">
<div class="bounty-name" style="text-decoration:line-through">Bounty Troll</div>
<div class="bounty-location">Floor 1 · Room 8 · Completed</div>
<div class="hp-bar-wrap">
<div class="hp-bar-fill low" style="width:0%"></div>
</div>
<div class="bounty-hp-text">
<span>0 / 200 HP — SLAIN</span>
<span></span>
</div>
<div class="bounty-contributors">Finished by Kael · 4h ago</div>
</div>
</div>
</div>
<!-- MODE STATUS: HOLD THE LINE -->
<div class="board-panel">
<div class="panel-header">
<span class="panel-title">Hold the Line</span>
<span class="panel-icon">🏰</span>
</div>
<div class="panel-body">
<div class="floor-row">
<span class="floor-label">Floor 1</span>
<div class="floor-bar-wrap">
<div class="floor-bar-fill f1" style="width:100%">
<div class="floor-checkpoint locked" style="left:30%"></div>
<div class="floor-checkpoint locked" style="left:60%"></div>
<div class="floor-checkpoint locked" style="left:95%"></div>
</div>
</div>
<span class="floor-pct">100%</span>
</div>
<div class="floor-row">
<span class="floor-label">Floor 2</span>
<div class="floor-bar-wrap">
<div class="floor-bar-fill f2" style="width:71%">
<div class="floor-checkpoint locked" style="left:42%"></div>
<div class="floor-checkpoint" style="left:85%"></div>
</div>
</div>
<span class="floor-pct">71%</span>
</div>
<div class="floor-row">
<span class="floor-label">Floor 3</span>
<div class="floor-bar-wrap">
<div class="floor-bar-fill f3" style="width:18%">
<div class="floor-checkpoint" style="left:33%"></div>
</div>
</div>
<span class="floor-pct">18%</span>
</div>
<div class="floor-row">
<span class="floor-label">Floor 4</span>
<div class="floor-bar-wrap">
<div class="floor-bar-fill f4" style="width:0%"></div>
</div>
<span class="floor-pct" style="color:var(--text-ghost)">locked</span>
</div>
<div style="text-align:center;margin-top:14px;">
<span style="font-family:'JetBrains Mono',monospace;font-size:10px;color:var(--text-ghost);letter-spacing:0.1em;">
NEXT REGEN TICK: ~4H · FLOOR 2 LOSES 1 ROOM
</span>
</div>
</div>
</div>
</div>
<!-- NPC STRIP -->
<div class="divider">THE REGULARS</div>
<div class="npc-strip">
<div class="npc-card">
<div class="npc-sigil">🍺</div>
<div class="npc-name">Grist</div>
<div class="npc-role">Barkeep</div>
</div>
<div class="npc-card">
<div class="npc-sigil">🩸</div>
<div class="npc-name">Maren</div>
<div class="npc-role">Healer</div>
</div>
<div class="npc-card">
<div class="npc-sigil"></div>
<div class="npc-name">Torval</div>
<div class="npc-role">Merchant</div>
</div>
<div class="npc-card">
<div class="npc-sigil">👁</div>
<div class="npc-name">Whisper</div>
<div class="npc-role">Sage</div>
</div>
</div>
<!-- FOOTER -->
<div class="tavern-footer">
<div class="footer-mesh">
meshMUD · LoRa text adventure · <a href="#">meshtastic</a>
</div>
<a class="admin-link" id="admin-btn">
<span class="admin-lock">🔒</span>
Operator Console
</a>
</div>
</div>
<!-- ADMIN LOGIN MODAL -->
<div class="modal-overlay" id="admin-modal">
<div class="modal-box">
<button class="modal-close" id="modal-close">×</button>
<div class="modal-title">OPERATOR CONSOLE</div>
<input type="text" class="modal-input" placeholder="callsign" id="admin-user" autocomplete="off" spellcheck="false">
<input type="password" class="modal-input" placeholder="passphrase" id="admin-pass">
<button class="modal-btn" id="admin-submit">AUTHENTICATE</button>
<div class="modal-error" id="admin-error"></div>
</div>
</div>
<script>
// ═══ EMBER PARTICLES ═══
const canvas = document.getElementById('ember-canvas');
const ctx = canvas.getContext('2d');
let embers = [];
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resize();
window.addEventListener('resize', resize);
class Ember {
constructor() {
this.reset();
}
reset() {
this.x = Math.random() * canvas.width;
this.y = canvas.height + 10;
this.size = Math.random() * 2.5 + 0.5;
this.speedY = -(Math.random() * 0.4 + 0.1);
this.speedX = (Math.random() - 0.5) * 0.3;
this.opacity = Math.random() * 0.5 + 0.2;
this.decay = Math.random() * 0.001 + 0.0005;
this.wobble = Math.random() * Math.PI * 2;
this.wobbleSpeed = Math.random() * 0.02 + 0.005;
// Color between deep orange and pale gold
const t = Math.random();
this.r = Math.floor(200 + t * 55);
this.g = Math.floor(80 + t * 80);
this.b = Math.floor(20 + t * 30);
}
update() {
this.wobble += this.wobbleSpeed;
this.x += this.speedX + Math.sin(this.wobble) * 0.15;
this.y += this.speedY;
this.opacity -= this.decay;
if (this.opacity <= 0 || this.y < -20) this.reset();
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${this.r},${this.g},${this.b},${this.opacity})`;
ctx.fill();
// glow
ctx.beginPath();
ctx.arc(this.x, this.y, this.size * 3, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${this.r},${this.g},${this.b},${this.opacity * 0.15})`;
ctx.fill();
}
}
for (let i = 0; i < 40; i++) {
const e = new Ember();
e.y = Math.random() * canvas.height;
embers.push(e);
}
function animateEmbers() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
embers.forEach(e => { e.update(); e.draw(); });
requestAnimationFrame(animateEmbers);
}
animateEmbers();
// ═══ SECRET DOTS ═══
const secretContainer = document.getElementById('secret-dots');
const found = 11;
for (let i = 0; i < 20; i++) {
const dot = document.createElement('div');
dot.className = 'secret-dot' + (i < found ? ' found' : '');
secretContainer.appendChild(dot);
}
// ═══ ADMIN MODAL ═══
const adminBtn = document.getElementById('admin-btn');
const modal = document.getElementById('admin-modal');
const modalClose = document.getElementById('modal-close');
const adminSubmit = document.getElementById('admin-submit');
const adminError = document.getElementById('admin-error');
adminBtn.addEventListener('click', () => {
modal.classList.add('active');
document.getElementById('admin-user').focus();
});
modalClose.addEventListener('click', () => modal.classList.remove('active'));
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.classList.remove('active');
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') modal.classList.remove('active');
});
adminSubmit.addEventListener('click', () => {
const user = document.getElementById('admin-user').value;
const pass = document.getElementById('admin-pass').value;
if (!user || !pass) {
adminError.textContent = 'Credentials required.';
return;
}
// In production this would POST to /api/auth or redirect to Authentik
adminError.textContent = '';
adminSubmit.textContent = 'AUTHENTICATING...';
setTimeout(() => {
// Simulated redirect — replace with actual auth endpoint
window.location.href = '/admin';
}, 800);
});
// Enter key submits
document.getElementById('admin-pass').addEventListener('keydown', (e) => {
if (e.key === 'Enter') adminSubmit.click();
});
</script>
</body>
</html>