feat: v2 React frontend is now primary at /

- v1 vanilla JS frontend moved to /classic (static/classic/)
- v2 React app now serves at / (root)
- Vite base changed from /v2/ to /
- Assets at /assets/, service worker at /sw.js
- /classic still works — all v1 files preserved with relative paths
- /v2 still works as before (build output unchanged)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-14 12:17:23 +02:00
parent 69678a9426
commit 9f7686681b
41 changed files with 9080 additions and 175 deletions

View file

@ -10,5 +10,5 @@ createRoot(document.getElementById('root')!).render(
// Register service worker for asset caching // Register service worker for asset caching
if ('serviceWorker' in navigator) { if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/v2/sw.js').catch(() => {}); navigator.serviceWorker.register('/sw.js').catch(() => {});
} }

View file

@ -3,9 +3,9 @@ import react from '@vitejs/plugin-react'
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
base: '/v2/', base: '/',
build: { build: {
outDir: '../static/v2', outDir: '../static/v2', // still build to v2/ first, then we copy
emptyOutDir: true, emptyOutDir: true,
chunkSizeWarningLimit: 300, chunkSizeWarningLimit: 300,
rollupOptions: { rollupOptions: {

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
import{u as c,j as r,D as d}from"./index-BfJ04YaG.js";import"./react-DlyoauG8.js";const p=({id:n,zIndex:i,characters:a})=>{const{openWindow:s}=c(),e=Array.from(a.keys()).sort();return r.jsx(d,{id:n,title:"Combat Stats — Select Character",zIndex:i,width:300,height:400,children:r.jsx("div",{style:{flex:1,overflowY:"auto",padding:6},children:e.length===0?r.jsx("div",{style:{padding:12,color:"#666",textAlign:"center",fontSize:"0.8rem"},children:"No characters online"}):e.map(o=>r.jsx("div",{style:{padding:"5px 8px",cursor:"pointer",borderBottom:"1px solid #222",color:"#ccc",fontSize:"0.82rem"},onMouseEnter:t=>t.currentTarget.style.background="#2a2a2a",onMouseLeave:t=>t.currentTarget.style.background="",onClick:()=>s(`combat-${o}`,`Combat: ${o}`,o),children:o},o))})})};export{p as CombatPickerWindow}; import{u as c,j as r,D as d}from"./index-DzGubmvT.js";import"./react-DlyoauG8.js";const p=({id:n,zIndex:i,characters:a})=>{const{openWindow:s}=c(),e=Array.from(a.keys()).sort();return r.jsx(d,{id:n,title:"Combat Stats — Select Character",zIndex:i,width:300,height:400,children:r.jsx("div",{style:{flex:1,overflowY:"auto",padding:6},children:e.length===0?r.jsx("div",{style:{padding:12,color:"#666",textAlign:"center",fontSize:"0.8rem"},children:"No characters online"}):e.map(o=>r.jsx("div",{style:{padding:"5px 8px",cursor:"pointer",borderBottom:"1px solid #222",color:"#ccc",fontSize:"0.82rem"},onMouseEnter:t=>t.currentTarget.style.background="#2a2a2a",onMouseLeave:t=>t.currentTarget.style.background="",onClick:()=>s(`combat-${o}`,`Combat: ${o}`,o),children:o},o))})})};export{p as CombatPickerWindow};

View file

@ -0,0 +1 @@
import{u as c,j as r,D as d}from"./index-DnlpBAAU.js";import"./react-DlyoauG8.js";const p=({id:n,zIndex:i,characters:a})=>{const{openWindow:s}=c(),e=Array.from(a.keys()).sort();return r.jsx(d,{id:n,title:"Combat Stats — Select Character",zIndex:i,width:300,height:400,children:r.jsx("div",{style:{flex:1,overflowY:"auto",padding:6},children:e.length===0?r.jsx("div",{style:{padding:12,color:"#666",textAlign:"center",fontSize:"0.8rem"},children:"No characters online"}):e.map(o=>r.jsx("div",{style:{padding:"5px 8px",cursor:"pointer",borderBottom:"1px solid #222",color:"#ccc",fontSize:"0.82rem"},onMouseEnter:t=>t.currentTarget.style.background="#2a2a2a",onMouseLeave:t=>t.currentTarget.style.background="",onClick:()=>s(`combat-${o}`,`Combat: ${o}`,o),children:o},o))})})};export{p as CombatPickerWindow};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
import{r as o,j as t,D as d}from"./index-BfJ04YaG.js";import"./react-DlyoauG8.js";const c=[{title:"Kills per Hour",id:1},{title:"Memory (MB)",id:2},{title:"CPU (%)",id:3},{title:"Mem Handles",id:4}],m=[{label:"1H",value:"now-1h"},{label:"6H",value:"now-6h"},{label:"24H",value:"now-24h"},{label:"7D",value:"now-7d"}],v=({id:s,charName:a,zIndex:i})=>{const[l,r]=o.useState("now-24h"),n=e=>`/grafana/d-solo/dereth-tracker/dereth-tracker-dashboard?panelId=${e}&var-character=${encodeURIComponent(a)}&from=${l}&to=now&theme=light`;return t.jsxs(d,{id:s,title:`Stats: ${a}`,zIndex:i,width:750,height:480,children:[t.jsx("div",{className:"ml-stats-controls",children:m.map(e=>t.jsx("button",{className:`ml-stats-range-btn ${l===e.value?"active":""}`,onClick:()=>r(e.value),children:e.label},e.value))}),t.jsx("div",{className:"ml-stats-grid",children:c.map(e=>t.jsx("div",{className:"ml-stats-panel",children:t.jsx("iframe",{src:n(e.id),width:"100%",height:"100%",frameBorder:"0",title:e.title})},e.id))})]})};export{v as StatsWindow}; import{r as o,j as t,D as d}from"./index-DzGubmvT.js";import"./react-DlyoauG8.js";const c=[{title:"Kills per Hour",id:1},{title:"Memory (MB)",id:2},{title:"CPU (%)",id:3},{title:"Mem Handles",id:4}],m=[{label:"1H",value:"now-1h"},{label:"6H",value:"now-6h"},{label:"24H",value:"now-24h"},{label:"7D",value:"now-7d"}],v=({id:s,charName:a,zIndex:i})=>{const[l,r]=o.useState("now-24h"),n=e=>`/grafana/d-solo/dereth-tracker/dereth-tracker-dashboard?panelId=${e}&var-character=${encodeURIComponent(a)}&from=${l}&to=now&theme=light`;return t.jsxs(d,{id:s,title:`Stats: ${a}`,zIndex:i,width:750,height:480,children:[t.jsx("div",{className:"ml-stats-controls",children:m.map(e=>t.jsx("button",{className:`ml-stats-range-btn ${l===e.value?"active":""}`,onClick:()=>r(e.value),children:e.label},e.value))}),t.jsx("div",{className:"ml-stats-grid",children:c.map(e=>t.jsx("div",{className:"ml-stats-panel",children:t.jsx("iframe",{src:n(e.id),width:"100%",height:"100%",frameBorder:"0",title:e.title})},e.id))})]})};export{v as StatsWindow};

View file

@ -0,0 +1 @@
import{r as o,j as t,D as d}from"./index-DnlpBAAU.js";import"./react-DlyoauG8.js";const c=[{title:"Kills per Hour",id:1},{title:"Memory (MB)",id:2},{title:"CPU (%)",id:3},{title:"Mem Handles",id:4}],m=[{label:"1H",value:"now-1h"},{label:"6H",value:"now-6h"},{label:"24H",value:"now-24h"},{label:"7D",value:"now-7d"}],v=({id:s,charName:a,zIndex:i})=>{const[l,r]=o.useState("now-24h"),n=e=>`/grafana/d-solo/dereth-tracker/dereth-tracker-dashboard?panelId=${e}&var-character=${encodeURIComponent(a)}&from=${l}&to=now&theme=light`;return t.jsxs(d,{id:s,title:`Stats: ${a}`,zIndex:i,width:750,height:480,children:[t.jsx("div",{className:"ml-stats-controls",children:m.map(e=>t.jsx("button",{className:`ml-stats-range-btn ${l===e.value?"active":""}`,onClick:()=>r(e.value),children:e.label},e.value))}),t.jsx("div",{className:"ml-stats-grid",children:c.map(e=>t.jsx("div",{className:"ml-stats-panel",children:t.jsx("iframe",{src:n(e.id),width:"100%",height:"100%",frameBorder:"0",title:e.title})},e.id))})]})};export{v as StatsWindow};

View file

@ -1 +1 @@
import{r as n,j as t,D as x,a as m}from"./index-BfJ04YaG.js";import"./react-DlyoauG8.js";const v=({id:o,zIndex:c})=>{const[a,d]=n.useState([]);n.useEffect(()=>{const e=async()=>{try{const s=await m("/vital-sharing/peers");d(s.peers??[])}catch{}};e();const l=setInterval(e,5e3);return()=>clearInterval(l)},[]);const h=(e,l)=>l>0?Math.min(100,e/l*100):0;return t.jsx(x,{id:o,title:"Vital Sharing Network",zIndex:c,width:520,height:450,children:t.jsx("div",{style:{flex:1,overflowY:"auto",padding:6,fontSize:"0.75rem"},children:a.length===0?t.jsx("div",{style:{padding:16,color:"#666",textAlign:"center"},children:"No vital-sharing peers connected"}):a.map(e=>{var l,s,r;return t.jsxs("div",{style:{padding:"6px 8px",marginBottom:4,background:"#1f1f1f",borderRadius:3,border:"1px solid #333"},children:[t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:3},children:[t.jsx("span",{style:{color:e.plugin_connected?"#4c4":"#a33",fontSize:"0.8rem"},children:"●"}),t.jsx("strong",{style:{flex:1},children:e.character_name}),e.subscribed&&t.jsx("span",{style:{color:"#6bf",fontSize:"0.65rem"},children:"[subscribed]"})]}),t.jsxs("div",{style:{color:"#666",fontSize:"0.68rem",marginBottom:3},children:["tags: ",((l=e.tags)==null?void 0:l.join(", "))||"none"]}),e.vitals&&e.vitals.max_health>0&&t.jsx("div",{style:{display:"flex",flexDirection:"column",gap:2},children:[{label:"HP",cur:e.vitals.current_health,max:e.vitals.max_health,bg:"#330000",fill:"#c44"},{label:"STA",cur:e.vitals.current_stamina,max:e.vitals.max_stamina,bg:"#331a00",fill:"#ca0"},{label:"MANA",cur:e.vitals.current_mana,max:e.vitals.max_mana,bg:"#001433",fill:"#48f"}].map(i=>t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[t.jsx("span",{style:{width:32,color:"#888",fontSize:"0.65rem"},children:i.label}),t.jsx("div",{style:{flex:1,height:6,background:i.bg,borderRadius:3,overflow:"hidden"},children:t.jsx("div",{style:{width:`${h(i.cur,i.max)}%`,height:"100%",background:i.fill,borderRadius:3}})}),t.jsxs("span",{style:{width:60,textAlign:"right",fontSize:"0.65rem",color:"#888"},children:[i.cur,"/",i.max]})]},i.label))}),e.position&&t.jsxs("div",{style:{color:"#555",fontSize:"0.65rem",marginTop:2},children:[(s=e.position.ns)==null?void 0:s.toFixed(1),"N, ",(r=e.position.ew)==null?void 0:r.toFixed(1),"E"]})]},e.character_name)})})})};export{v as VitalSharingWindow}; import{r as n,j as t,D as x,a as m}from"./index-DzGubmvT.js";import"./react-DlyoauG8.js";const v=({id:o,zIndex:c})=>{const[a,d]=n.useState([]);n.useEffect(()=>{const e=async()=>{try{const s=await m("/vital-sharing/peers");d(s.peers??[])}catch{}};e();const l=setInterval(e,5e3);return()=>clearInterval(l)},[]);const h=(e,l)=>l>0?Math.min(100,e/l*100):0;return t.jsx(x,{id:o,title:"Vital Sharing Network",zIndex:c,width:520,height:450,children:t.jsx("div",{style:{flex:1,overflowY:"auto",padding:6,fontSize:"0.75rem"},children:a.length===0?t.jsx("div",{style:{padding:16,color:"#666",textAlign:"center"},children:"No vital-sharing peers connected"}):a.map(e=>{var l,s,r;return t.jsxs("div",{style:{padding:"6px 8px",marginBottom:4,background:"#1f1f1f",borderRadius:3,border:"1px solid #333"},children:[t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:3},children:[t.jsx("span",{style:{color:e.plugin_connected?"#4c4":"#a33",fontSize:"0.8rem"},children:"●"}),t.jsx("strong",{style:{flex:1},children:e.character_name}),e.subscribed&&t.jsx("span",{style:{color:"#6bf",fontSize:"0.65rem"},children:"[subscribed]"})]}),t.jsxs("div",{style:{color:"#666",fontSize:"0.68rem",marginBottom:3},children:["tags: ",((l=e.tags)==null?void 0:l.join(", "))||"none"]}),e.vitals&&e.vitals.max_health>0&&t.jsx("div",{style:{display:"flex",flexDirection:"column",gap:2},children:[{label:"HP",cur:e.vitals.current_health,max:e.vitals.max_health,bg:"#330000",fill:"#c44"},{label:"STA",cur:e.vitals.current_stamina,max:e.vitals.max_stamina,bg:"#331a00",fill:"#ca0"},{label:"MANA",cur:e.vitals.current_mana,max:e.vitals.max_mana,bg:"#001433",fill:"#48f"}].map(i=>t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[t.jsx("span",{style:{width:32,color:"#888",fontSize:"0.65rem"},children:i.label}),t.jsx("div",{style:{flex:1,height:6,background:i.bg,borderRadius:3,overflow:"hidden"},children:t.jsx("div",{style:{width:`${h(i.cur,i.max)}%`,height:"100%",background:i.fill,borderRadius:3}})}),t.jsxs("span",{style:{width:60,textAlign:"right",fontSize:"0.65rem",color:"#888"},children:[i.cur,"/",i.max]})]},i.label))}),e.position&&t.jsxs("div",{style:{color:"#555",fontSize:"0.65rem",marginTop:2},children:[(s=e.position.ns)==null?void 0:s.toFixed(1),"N, ",(r=e.position.ew)==null?void 0:r.toFixed(1),"E"]})]},e.character_name)})})})};export{v as VitalSharingWindow};

View file

@ -0,0 +1 @@
import{r as n,j as t,D as x,a as m}from"./index-DnlpBAAU.js";import"./react-DlyoauG8.js";const v=({id:o,zIndex:c})=>{const[a,d]=n.useState([]);n.useEffect(()=>{const e=async()=>{try{const s=await m("/vital-sharing/peers");d(s.peers??[])}catch{}};e();const l=setInterval(e,5e3);return()=>clearInterval(l)},[]);const h=(e,l)=>l>0?Math.min(100,e/l*100):0;return t.jsx(x,{id:o,title:"Vital Sharing Network",zIndex:c,width:520,height:450,children:t.jsx("div",{style:{flex:1,overflowY:"auto",padding:6,fontSize:"0.75rem"},children:a.length===0?t.jsx("div",{style:{padding:16,color:"#666",textAlign:"center"},children:"No vital-sharing peers connected"}):a.map(e=>{var l,s,r;return t.jsxs("div",{style:{padding:"6px 8px",marginBottom:4,background:"#1f1f1f",borderRadius:3,border:"1px solid #333"},children:[t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:3},children:[t.jsx("span",{style:{color:e.plugin_connected?"#4c4":"#a33",fontSize:"0.8rem"},children:"●"}),t.jsx("strong",{style:{flex:1},children:e.character_name}),e.subscribed&&t.jsx("span",{style:{color:"#6bf",fontSize:"0.65rem"},children:"[subscribed]"})]}),t.jsxs("div",{style:{color:"#666",fontSize:"0.68rem",marginBottom:3},children:["tags: ",((l=e.tags)==null?void 0:l.join(", "))||"none"]}),e.vitals&&e.vitals.max_health>0&&t.jsx("div",{style:{display:"flex",flexDirection:"column",gap:2},children:[{label:"HP",cur:e.vitals.current_health,max:e.vitals.max_health,bg:"#330000",fill:"#c44"},{label:"STA",cur:e.vitals.current_stamina,max:e.vitals.max_stamina,bg:"#331a00",fill:"#ca0"},{label:"MANA",cur:e.vitals.current_mana,max:e.vitals.max_mana,bg:"#001433",fill:"#48f"}].map(i=>t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[t.jsx("span",{style:{width:32,color:"#888",fontSize:"0.65rem"},children:i.label}),t.jsx("div",{style:{flex:1,height:6,background:i.bg,borderRadius:3,overflow:"hidden"},children:t.jsx("div",{style:{width:`${h(i.cur,i.max)}%`,height:"100%",background:i.fill,borderRadius:3}})}),t.jsxs("span",{style:{width:60,textAlign:"right",fontSize:"0.65rem",color:"#888"},children:[i.cur,"/",i.max]})]},i.label))}),e.position&&t.jsxs("div",{style:{color:"#555",fontSize:"0.65rem",marginTop:2},children:[(s=e.position.ns)==null?void 0:s.toFixed(1),"N, ",(r=e.position.ew)==null?void 0:r.toFixed(1),"E"]})]},e.character_name)})})})};export{v as VitalSharingWindow};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

17
static/assets/react-DlyoauG8.js vendored Normal file

File diff suppressed because one or more lines are too long

161
static/classic/index.html Normal file
View file

@ -0,0 +1,161 @@
<!--
Dereth Tracker Single-Page Application
Displays live player locations, trails, and statistics on a map.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Dereth Tracker</title>
<!-- Link to main stylesheet -->
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- Version display -->
<div id="versionDisplay" style="position:fixed;top:4px;left:4px;z-index:9999;color:#fff;font-size:0.65rem;font-family:monospace;opacity:0.7;"></div>
<!-- Sidebar for active players list and filters -->
<aside id="sidebar">
<h2 id="activePlayersHeader">Active Mosswart Enjoyers</h2>
<!-- Server Status -->
<div id="serverStatus" class="server-status-container">
<h3>Coldeve Server Status</h3>
<div class="status-indicator">
<span class="status-dot" id="statusDot"></span>
<span id="statusText">Checking...</span>
</div>
<div class="status-details">
<div>Players: <span id="playerCount">-</span></div>
<div>Latency: <span id="latencyMs">-</span> ms</div>
<div>Uptime: <span id="uptime">-</span></div>
<div>Last Restart: <span id="lastRestart">-</span></div>
</div>
</div>
<!-- Total rares counter -->
<div id="totalRaresCounter" class="total-rares-counter">
🔥 Total Rares: <span id="totalRaresCount">Loading...</span>
</div>
<!-- Server KPH counter -->
<div id="serverKphCounter" class="server-kph-counter">
⚡ Server KPH: <span id="serverKphCount">Loading...</span>
</div>
<!-- Total kills counter -->
<div id="totalKillsCounter" class="total-kills-counter">
⚔️ Total Kills: <span id="totalKillsCount">Loading...</span>
</div>
<!-- Heat map toggle -->
<div class="heatmap-toggle">
<label>
<input type="checkbox" id="heatmapToggle">
🔥 Show Spawn Heat Map
</label>
</div>
<!-- Portal toggle -->
<div class="portal-toggle">
<label>
<input type="checkbox" id="portalToggle">
🌀 Show Portals
</label>
</div>
<!-- Inventory search link -->
<div class="inventory-search-link">
<a href="#" id="inventorySearchBtn" onclick="openInventorySearch()">
📦 Inventory Search
</a>
</div>
<!-- Suitbuilder link -->
<div class="suitbuilder-link">
<a href="#" id="suitbuilderBtn" onclick="openSuitbuilder()">
🛡️ Suitbuilder
</a>
</div>
<!-- Player Debug link -->
<div class="debug-link">
<a href="#" id="playerDebugBtn" onclick="openPlayerDebug()">
🔍 Player Debug
</a>
</div>
<!-- Quest Status link -->
<div class="quest-status-link">
<a href="#" id="questStatusBtn" onclick="openQuestStatus()">
⏰ Quest Status
</a>
</div>
<!-- Player Dashboard link -->
<div class="player-dashboard-link">
<a href="#" id="playerDashboardBtn" onclick="openPlayerDashboard()">
👥 Player Dashboard
</a>
</div>
<!-- Issues Board link -->
<div class="quest-status-link">
<a href="#" id="issuesBoardBtn" onclick="showIssuesWindow()">
📋 Issues Board
</a>
</div>
<!-- Vital Sharing Network link -->
<div class="quest-status-link">
<a href="#" id="vitalSharingBtn" onclick="showVitalSharingWindow()">
🤝 Vital Sharing
</a>
</div>
<!-- Combat Stats link -->
<div class="quest-status-link">
<a href="#" id="combatStatsBtn" onclick="showCombatStatsWindow()">
Combat Stats
</a>
</div>
<!-- Container for sort and filter controls -->
<div id="sortButtons" class="sort-buttons"></div>
<!-- Text input to filter active players by name -->
<input type="text" id="playerFilter" class="player-filter" placeholder="Filter players..." />
<ul id="playerList"></ul>
<!-- User info section (populated by script.js after /me fetch) -->
<div id="userInfo" class="user-info" style="display:none;">
<span id="currentUsername" class="user-info-name"></span>
<a href="#" id="adminLink" class="user-info-admin" style="display:none;" onclick="window.open('/admin/users','_blank')">Admin</a>
<a href="/logout" class="user-info-logout">Logout</a>
</div>
</aside>
<!-- Epic rare notifications container -->
<div id="rareNotifications" class="rare-notifications"></div>
<!-- Fireworks container -->
<div id="fireworksContainer" class="fireworks-container"></div>
<!-- Main map container showing terrain and player data -->
<div id="mapContainer">
<div id="mapGroup">
<img id="map" src="dereth.png" alt="Dereth map">
<canvas id="heatmapCanvas"></canvas>
<svg id="trails"></svg>
<div id="dots"></div>
<div id="portals"></div>
</div>
<div id="tooltip" class="tooltip"></div>
<div id="coordinates" class="coordinates"></div>
</div>
<!-- Main JavaScript file for WebSocket communication and UI logic -->
<script src="script.js" defer></script>
</body>
</html>

4940
static/classic/script.js Normal file

File diff suppressed because it is too large Load diff

739
static/classic/style-ac.css Normal file
View file

@ -0,0 +1,739 @@
/*
* style-ac.css - Asheron's Call themed styles for Dereth Tracker
*
* Recreates the classic AC UI with stone textures, beveled edges,
* golden accents, and medieval fantasy aesthetics.
*/
/* CSS Custom Properties for AC theme colors and sizing */
:root {
--sidebar-width: 340px;
/* AC Color Palette */
--ac-black: #0a0a0a;
--ac-dark-stone: #1a1a1a;
--ac-medium-stone: #2a2a2a;
--ac-light-stone: #3a3a3a;
--ac-border-dark: #000;
--ac-border-light: #4a4a4a;
--ac-gold: #d4af37;
--ac-gold-bright: #ffd700;
--ac-gold-dark: #b8941f;
--ac-green: #00ff00;
--ac-cyan: #00ffff;
--ac-text: #e0e0e0;
--ac-text-dim: #a0a0a0;
/* Backgrounds */
--bg-main: var(--ac-black);
--bg-side: var(--ac-dark-stone);
--card: var(--ac-medium-stone);
--card-hov: var(--ac-light-stone);
--text: var(--ac-text);
--accent: var(--ac-gold);
}
/* Placeholder text in chat input */
.chat-input::placeholder {
color: var(--ac-text-dim);
opacity: 0.7;
}
html {
margin: 0;
height: 100%;
width: 100%;
}
body {
margin: 0;
height: 100%;
display: flex;
overflow: hidden;
font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
background: var(--bg-main);
color: var(--text);
background-image:
repeating-linear-gradient(
45deg,
transparent,
transparent 10px,
rgba(255, 255, 255, 0.01) 10px,
rgba(255, 255, 255, 0.01) 20px
);
}
/* AC-style stone textured panels with beveled edges */
.ac-panel {
background: linear-gradient(135deg, var(--ac-medium-stone) 0%, var(--ac-dark-stone) 100%);
border: 2px solid var(--ac-border-dark);
box-shadow:
inset 2px 2px 3px rgba(255, 255, 255, 0.1),
inset -2px -2px 3px rgba(0, 0, 0, 0.5),
0 2px 5px rgba(0, 0, 0, 0.8);
border-radius: 0;
}
/* Sort buttons - AC style */
.sort-buttons {
display: flex;
gap: 3px;
margin: 12px 16px 8px;
padding: 8px;
background: var(--ac-dark-stone);
border: 1px solid var(--ac-border-dark);
box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.5);
}
.sort-buttons .btn {
flex: 1;
padding: 5px 8px;
background: linear-gradient(180deg, var(--ac-light-stone) 0%, var(--ac-medium-stone) 100%);
color: var(--ac-text-dim);
border: 1px solid var(--ac-border-dark);
border-radius: 2px;
text-align: center;
cursor: pointer;
user-select: none;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
transition: all 0.15s;
box-shadow:
1px 1px 2px rgba(0, 0, 0, 0.5),
inset 1px 1px 2px rgba(255, 255, 255, 0.1);
}
.sort-buttons .btn:hover {
background: linear-gradient(180deg, var(--ac-light-stone) 0%, var(--ac-light-stone) 100%);
color: var(--ac-gold-bright);
box-shadow:
1px 1px 2px rgba(0, 0, 0, 0.5),
inset 1px 1px 2px rgba(255, 255, 255, 0.2),
0 0 5px rgba(212, 175, 55, 0.3);
}
.sort-buttons .btn:active {
box-shadow:
inset 2px 2px 3px rgba(0, 0, 0, 0.7),
inset -1px -1px 2px rgba(255, 255, 255, 0.1);
}
.sort-buttons .btn.active {
background: linear-gradient(180deg, var(--ac-gold) 0%, var(--ac-gold-dark) 100%);
color: var(--ac-black);
border-color: var(--ac-gold-dark);
font-weight: 700;
position: relative;
box-shadow:
1px 1px 2px rgba(0, 0, 0, 0.5),
inset 1px 1px 2px rgba(255, 255, 255, 0.3),
0 0 10px rgba(212, 175, 55, 0.5);
}
.sort-buttons .btn.active:hover {
background: linear-gradient(180deg, var(--ac-gold-bright) 0%, var(--ac-gold) 100%);
color: var(--ac-black);
}
/* Sort direction indicators */
.sort-buttons .btn.active::after {
content: '';
position: absolute;
top: 3px;
right: 3px;
width: 0;
height: 0;
border-left: 3px solid transparent;
border-right: 3px solid transparent;
}
/* Most sorts are descending (down arrow) */
.sort-buttons .btn.active::after {
border-top: 4px solid var(--ac-black);
}
/* Name and KPR are ascending (up arrow) */
.sort-buttons .btn.active[data-value="name"]::after,
.sort-buttons .btn.active[data-value="kpr"]::after {
border-top: none;
border-bottom: 4px solid var(--ac-black);
}
/* Sidebar - AC stone panel style */
#sidebar {
width: var(--sidebar-width);
scrollbar-width: thin;
scrollbar-color: var(--ac-gold-dark) var(--ac-dark-stone);
background: linear-gradient(180deg, var(--ac-dark-stone) 0%, var(--ac-black) 100%);
border-right: 3px solid var(--ac-border-dark);
box-shadow:
inset -2px 0 5px rgba(0, 0, 0, 0.5),
2px 0 5px rgba(0, 0, 0, 0.8);
box-sizing: border-box;
padding: 18px 16px;
overflow-y: auto;
}
#sidebar h2 {
margin: 8px 0 12px;
font-size: 1.25rem;
color: var(--ac-gold);
text-shadow:
2px 2px 3px rgba(0, 0, 0, 0.8),
0 0 10px rgba(212, 175, 55, 0.3);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
}
#playerList {
list-style: none;
margin: 0;
padding: 0;
}
/* Filter input - AC style */
.player-filter {
width: 100%;
padding: 6px 10px;
margin-bottom: 12px;
background: var(--ac-dark-stone);
color: var(--ac-gold);
border: 2px solid var(--ac-border-dark);
border-radius: 2px;
font-size: 0.9rem;
box-sizing: border-box;
box-shadow:
inset 2px 2px 3px rgba(0, 0, 0, 0.5),
inset -1px -1px 2px rgba(255, 255, 255, 0.05);
font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
}
.player-filter:focus {
outline: none;
border-color: var(--ac-gold-dark);
box-shadow:
inset 2px 2px 3px rgba(0, 0, 0, 0.5),
0 0 5px rgba(212, 175, 55, 0.5);
}
/* Map container */
#mapContainer {
flex: 1;
position: relative;
overflow: hidden;
background: var(--bg-main);
box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.8);
}
/* Player list items - AC stone panels */
#playerList li {
display: grid;
grid-template-columns: 1fr auto auto auto auto auto;
grid-template-rows: auto auto auto auto;
grid-template-areas:
"name name name name name name"
"kills totalkills kph kph kph kph"
"rares kpr meta meta meta meta"
"onlinetime deaths tapers tapers tapers tapers";
gap: 4px 8px;
margin: 6px 0;
padding: 10px 12px;
background: linear-gradient(135deg, var(--ac-medium-stone) 0%, var(--ac-dark-stone) 100%);
border: 2px solid var(--ac-border-dark);
border-left: 4px solid var(--ac-gold-dark);
transition: all 0.2s;
box-shadow:
1px 1px 3px rgba(0, 0, 0, 0.5),
inset 1px 1px 2px rgba(255, 255, 255, 0.05);
}
/* Grid assignments */
.player-name {
grid-area: name;
font-weight: 700;
color: var(--ac-gold);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
}
.coordinates-inline {
font-size: 0.75rem;
color: var(--ac-text-dim);
font-weight: 400;
margin-left: 8px;
}
.stat.kills { grid-area: kills; }
.stat.total-kills { grid-area: totalkills; }
.stat.kph { grid-area: kph; }
.stat.rares { grid-area: rares; }
.stat.kpr { grid-area: kpr; }
.stat.meta { grid-area: meta; }
.stat.onlinetime { grid-area: onlinetime; }
.stat.deaths { grid-area: deaths; }
.stat.tapers { grid-area: tapers; }
/* Stat pills - AC style */
#playerList li .stat {
background: linear-gradient(180deg, rgba(0, 0, 0, 0.3) 0%, rgba(0, 0, 0, 0.5) 100%);
padding: 4px 8px;
border-radius: 2px;
display: inline-block;
font-size: 0.75rem;
white-space: nowrap;
color: var(--ac-text);
border: 1px solid rgba(0, 0, 0, 0.5);
box-shadow:
1px 1px 2px rgba(0, 0, 0, 0.3),
inset 1px 1px 1px rgba(255, 255, 255, 0.05);
}
/* Icons & suffixes */
.stat.kills::before { content: "⚔️ "; }
.stat.total-kills::before { content: "🏆 "; }
.stat.kph::after { content: " KPH"; font-size:0.7em; color: var(--ac-text-dim); }
.stat.rares::before { content: "💎 "; }
.stat.rares::after { content: " Rares"; font-size:0.7em; color: var(--ac-text-dim); }
.stat.kpr::before { content: "📊 "; }
.stat.kpr::after { content: " KPR"; font-size:0.7em; color: var(--ac-text-dim); }
/* Metastate pills */
#playerList li .stat.meta {
background: linear-gradient(180deg, var(--ac-gold) 0%, var(--ac-gold-dark) 100%);
color: var(--ac-black);
border-color: var(--ac-gold-dark);
}
#playerList li .stat.meta.green {
background: linear-gradient(180deg, #4ade80 0%, #22c55e 100%);
color: var(--ac-black);
border-color: #16a34a;
}
#playerList li .stat.meta.red {
background: linear-gradient(180deg, #f87171 0%, #ef4444 100%);
color: #fff;
border-color: #dc2626;
}
/* Chat/Stats/Inventory buttons - AC style */
.chat-btn, .stats-btn, .inventory-btn {
margin-top: 4px;
margin-right: 4px;
padding: 3px 8px;
background: linear-gradient(180deg, var(--ac-gold) 0%, var(--ac-gold-dark) 100%);
color: var(--ac-black);
border: 1px solid var(--ac-gold-dark);
border-radius: 2px;
font-size: 0.75rem;
font-weight: 600;
cursor: pointer;
text-transform: uppercase;
letter-spacing: 0.5px;
box-shadow:
1px 1px 2px rgba(0, 0, 0, 0.5),
inset 1px 1px 1px rgba(255, 255, 255, 0.3);
transition: all 0.15s;
}
.chat-btn:hover, .stats-btn:hover, .inventory-btn:hover {
background: linear-gradient(180deg, var(--ac-gold-bright) 0%, var(--ac-gold) 100%);
box-shadow:
1px 1px 2px rgba(0, 0, 0, 0.5),
inset 1px 1px 1px rgba(255, 255, 255, 0.4),
0 0 5px rgba(212, 175, 55, 0.5);
}
/* Windows - AC stone panel style */
.chat-window, .stats-window, .inventory-window {
position: absolute;
top: 10px;
left: calc(var(--sidebar-width) + 10px);
width: 760px;
height: 300px;
background: linear-gradient(135deg, var(--ac-medium-stone) 0%, var(--ac-dark-stone) 100%);
border: 3px solid var(--ac-border-dark);
display: flex;
flex-direction: column;
z-index: 10000;
box-shadow:
0 5px 20px rgba(0, 0, 0, 0.8),
inset 2px 2px 3px rgba(255, 255, 255, 0.05);
border-radius: 2px;
}
.window-content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 0;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
background: linear-gradient(180deg, var(--ac-light-stone) 0%, var(--ac-medium-stone) 100%);
border-bottom: 2px solid var(--ac-border-dark);
color: var(--ac-gold);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1px;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.5);
}
.chat-close-btn {
background: linear-gradient(180deg, #ef4444 0%, #dc2626 100%);
color: #fff;
border: 1px solid #991b1b;
border-radius: 2px;
padding: 2px 8px;
font-size: 1rem;
cursor: pointer;
font-weight: 700;
box-shadow:
1px 1px 2px rgba(0, 0, 0, 0.5),
inset 1px 1px 1px rgba(255, 255, 255, 0.3);
}
.chat-close-btn:hover {
background: linear-gradient(180deg, #f87171 0%, #ef4444 100%);
box-shadow:
1px 1px 2px rgba(0, 0, 0, 0.5),
inset 1px 1px 1px rgba(255, 255, 255, 0.4),
0 0 5px rgba(239, 68, 68, 0.5);
}
.chat-messages {
flex: 1;
padding: 10px 15px;
overflow-y: auto;
background: rgba(0, 0, 0, 0.3);
color: var(--ac-green);
font-family: "Courier New", Courier, monospace;
font-size: 0.9rem;
line-height: 1.4;
box-shadow: inset 2px 2px 5px rgba(0, 0, 0, 0.5);
}
.chat-messages::-webkit-scrollbar {
width: 10px;
}
.chat-messages::-webkit-scrollbar-track {
background: var(--ac-dark-stone);
box-shadow: inset 1px 1px 2px rgba(0, 0, 0, 0.5);
}
.chat-messages::-webkit-scrollbar-thumb {
background: var(--ac-gold-dark);
border-radius: 2px;
box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}
.chat-form {
display: flex;
padding: 10px 15px;
background: var(--ac-dark-stone);
border-top: 2px solid var(--ac-border-dark);
box-shadow: 0 -2px 3px rgba(0, 0, 0, 0.5);
}
.chat-input {
flex: 1;
padding: 6px 10px;
background: rgba(0, 0, 0, 0.5);
color: var(--ac-green);
border: 2px solid var(--ac-border-dark);
border-radius: 2px;
font-family: "Courier New", Courier, monospace;
font-size: 0.9rem;
box-shadow: inset 2px 2px 3px rgba(0, 0, 0, 0.5);
}
.chat-input:focus {
outline: none;
border-color: var(--ac-gold-dark);
box-shadow:
inset 2px 2px 3px rgba(0, 0, 0, 0.5),
0 0 5px rgba(212, 175, 55, 0.3);
}
/* Map elements */
#mapGroup {
transform-origin: top left;
position: relative;
}
#map {
display: block;
user-select: none;
-webkit-user-drag: none;
filter: brightness(0.9) contrast(1.1);
}
.dot {
position: absolute;
width: 10px;
height: 10px;
border-radius: 50%;
transform: translate(-50%, -50%);
pointer-events: auto;
cursor: pointer;
z-index: 10;
box-shadow:
0 0 5px rgba(0, 0, 0, 0.8),
0 0 10px currentColor;
border: 1px solid rgba(0, 0, 0, 0.5);
}
.dot.highlight {
animation: pulse 2s infinite;
z-index: 20;
}
@keyframes pulse {
0% { box-shadow: 0 0 5px rgba(0, 0, 0, 0.8), 0 0 10px currentColor; }
50% { box-shadow: 0 0 10px rgba(0, 0, 0, 0.8), 0 0 20px currentColor, 0 0 30px currentColor; }
100% { box-shadow: 0 0 5px rgba(0, 0, 0, 0.8), 0 0 10px currentColor; }
}
/* Tooltip - AC style */
.tooltip {
position: absolute;
display: none;
background: linear-gradient(135deg, rgba(26, 26, 26, 0.95) 0%, rgba(10, 10, 10, 0.95) 100%);
color: var(--ac-gold);
padding: 6px 10px;
border: 2px solid var(--ac-gold-dark);
border-radius: 2px;
font-size: 0.8rem;
pointer-events: none;
white-space: nowrap;
z-index: 1000;
box-shadow:
0 2px 10px rgba(0, 0, 0, 0.8),
inset 1px 1px 2px rgba(255, 255, 255, 0.1);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
}
/* Coordinate display - AC style */
.coordinates {
position: absolute;
display: none;
background: linear-gradient(135deg, rgba(0, 50, 100, 0.95) 0%, rgba(0, 30, 60, 0.95) 100%);
color: var(--ac-cyan);
padding: 4px 8px;
border: 2px solid rgba(0, 100, 150, 0.8);
border-radius: 2px;
font-size: 0.75rem;
font-family: "Courier New", Courier, monospace;
font-weight: 700;
pointer-events: none;
white-space: nowrap;
z-index: 999;
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.8),
inset 1px 1px 2px rgba(255, 255, 255, 0.1);
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
}
/* Hover states */
#playerList li:hover {
background: linear-gradient(135deg, var(--ac-light-stone) 0%, var(--ac-medium-stone) 100%);
border-left-color: var(--ac-gold-bright);
box-shadow:
2px 2px 5px rgba(0, 0, 0, 0.5),
inset 1px 1px 2px rgba(255, 255, 255, 0.1),
0 0 10px rgba(212, 175, 55, 0.2);
}
#playerList li.selected {
background: linear-gradient(135deg, var(--ac-gold-dark) 0%, var(--ac-medium-stone) 100%);
border-left-color: var(--ac-gold-bright);
box-shadow:
2px 2px 5px rgba(0, 0, 0, 0.5),
inset 1px 1px 2px rgba(255, 255, 255, 0.2),
0 0 15px rgba(212, 175, 55, 0.3);
}
/* Trail paths */
#trails {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
opacity: 0.7;
}
.trail-path {
stroke-width: 2;
stroke-opacity: 0.8;
filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.8));
}
/* Stats window specific */
.stats-window {
height: auto;
}
.stats-window .chat-messages {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-auto-rows: auto;
gap: 10px;
padding: 10px;
overflow: visible;
background: var(--ac-dark-stone);
color: var(--ac-text);
}
.stats-window iframe {
width: 350px;
height: 200px;
border: 2px solid var(--ac-border-dark);
box-shadow:
inset 2px 2px 3px rgba(0, 0, 0, 0.5),
1px 1px 2px rgba(0, 0, 0, 0.3);
background: var(--ac-black);
}
/* Stats time controls - AC style */
.stats-controls {
display: flex;
gap: 8px;
padding: 10px 15px;
background: var(--ac-medium-stone);
border-bottom: 2px solid var(--ac-border-dark);
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.5);
}
.time-range-btn {
padding: 6px 12px;
background: linear-gradient(180deg, var(--ac-light-stone) 0%, var(--ac-medium-stone) 100%);
color: var(--ac-text-dim);
border: 1px solid var(--ac-border-dark);
border-radius: 2px;
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
text-transform: uppercase;
letter-spacing: 0.5px;
box-shadow:
1px 1px 2px rgba(0, 0, 0, 0.5),
inset 1px 1px 2px rgba(255, 255, 255, 0.1);
}
.time-range-btn:hover {
background: linear-gradient(180deg, var(--ac-light-stone) 0%, var(--ac-light-stone) 100%);
color: var(--ac-gold);
box-shadow:
1px 1px 2px rgba(0, 0, 0, 0.5),
inset 1px 1px 2px rgba(255, 255, 255, 0.2),
0 0 5px rgba(212, 175, 55, 0.2);
}
.time-range-btn.active {
background: linear-gradient(180deg, var(--ac-gold) 0%, var(--ac-gold-dark) 100%);
color: var(--ac-black);
border-color: var(--ac-gold-dark);
font-weight: 700;
box-shadow:
1px 1px 2px rgba(0, 0, 0, 0.5),
inset 1px 1px 2px rgba(255, 255, 255, 0.3),
0 0 10px rgba(212, 175, 55, 0.4);
}
/* Inventory window */
.inventory-content {
flex: 1;
padding: 15px;
background: var(--ac-dark-stone);
color: var(--ac-text);
overflow-y: auto;
box-shadow: inset 2px 2px 5px rgba(0, 0, 0, 0.5);
}
.inventory-placeholder {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 1.1rem;
color: var(--ac-text-dim);
font-style: italic;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.8);
}
.stat.onlinetime::before { content: "🕑 "; }
.stat.deaths::before { content: "💀 "; }
.stat.tapers::before {
content: "";
display: inline-block;
width: 16px;
height: 16px;
background-image: url('prismatic-taper-icon.png');
background-size: contain;
background-repeat: no-repeat;
margin-right: 4px;
vertical-align: text-bottom;
}
/* Disable text selection during drag */
.noselect {
user-select: none !important;
}
/* Custom scrollbar for sidebar */
#sidebar::-webkit-scrollbar {
width: 12px;
}
#sidebar::-webkit-scrollbar-track {
background: var(--ac-dark-stone);
box-shadow: inset 2px 2px 3px rgba(0, 0, 0, 0.5);
}
#sidebar::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, var(--ac-gold) 0%, var(--ac-gold-dark) 100%);
border-radius: 2px;
border: 1px solid var(--ac-gold-dark);
box-shadow:
1px 1px 2px rgba(0, 0, 0, 0.5),
inset 1px 1px 1px rgba(255, 255, 255, 0.3);
}
#sidebar::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, var(--ac-gold-bright) 0%, var(--ac-gold) 100%);
}
/* Map container special effects */
#mapContainer.dragging {
cursor: move;
}
/* Additional hover effects */
.player-item {
position: relative;
overflow: hidden;
}
.player-item::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent 0%, rgba(212, 175, 55, 0.2) 50%, transparent 100%);
transition: left 0.5s;
}
.player-item:hover::before {
left: 100%;
}

2890
static/classic/style.css Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,161 +1,18 @@
<!--
Dereth Tracker Single-Page Application
Displays live player locations, trails, and statistics on a map.
-->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="UTF-8" />
<title>Dereth Tracker</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Link to main stylesheet --> <title>Mosswart Overlord v2</title>
<link rel="stylesheet" href="style.css"> <link rel="icon" type="image/png" href="/icons/7735.png" />
</head> <link rel="preload" as="image" href="/dereth.png" />
<body> <link rel="preload" as="image" href="/icons/0600127E.png" />
<link rel="preload" as="fetch" href="/dungeon_tiles.json" crossorigin="anonymous" />
<!-- Version display --> <script type="module" crossorigin src="/assets/index-DzGubmvT.js"></script>
<div id="versionDisplay" style="position:fixed;top:4px;left:4px;z-index:9999;color:#fff;font-size:0.65rem;font-family:monospace;opacity:0.7;"></div> <link rel="modulepreload" crossorigin href="/assets/react-DlyoauG8.js">
<link rel="stylesheet" crossorigin href="/assets/index-CyLyPOVJ.css">
<!-- Sidebar for active players list and filters --> </head>
<aside id="sidebar"> <body>
<h2 id="activePlayersHeader">Active Mosswart Enjoyers</h2> <div id="root"></div>
</body>
<!-- Server Status -->
<div id="serverStatus" class="server-status-container">
<h3>Coldeve Server Status</h3>
<div class="status-indicator">
<span class="status-dot" id="statusDot"></span>
<span id="statusText">Checking...</span>
</div>
<div class="status-details">
<div>Players: <span id="playerCount">-</span></div>
<div>Latency: <span id="latencyMs">-</span> ms</div>
<div>Uptime: <span id="uptime">-</span></div>
<div>Last Restart: <span id="lastRestart">-</span></div>
</div>
</div>
<!-- Total rares counter -->
<div id="totalRaresCounter" class="total-rares-counter">
🔥 Total Rares: <span id="totalRaresCount">Loading...</span>
</div>
<!-- Server KPH counter -->
<div id="serverKphCounter" class="server-kph-counter">
⚡ Server KPH: <span id="serverKphCount">Loading...</span>
</div>
<!-- Total kills counter -->
<div id="totalKillsCounter" class="total-kills-counter">
⚔️ Total Kills: <span id="totalKillsCount">Loading...</span>
</div>
<!-- Heat map toggle -->
<div class="heatmap-toggle">
<label>
<input type="checkbox" id="heatmapToggle">
🔥 Show Spawn Heat Map
</label>
</div>
<!-- Portal toggle -->
<div class="portal-toggle">
<label>
<input type="checkbox" id="portalToggle">
🌀 Show Portals
</label>
</div>
<!-- Inventory search link -->
<div class="inventory-search-link">
<a href="#" id="inventorySearchBtn" onclick="openInventorySearch()">
📦 Inventory Search
</a>
</div>
<!-- Suitbuilder link -->
<div class="suitbuilder-link">
<a href="#" id="suitbuilderBtn" onclick="openSuitbuilder()">
🛡️ Suitbuilder
</a>
</div>
<!-- Player Debug link -->
<div class="debug-link">
<a href="#" id="playerDebugBtn" onclick="openPlayerDebug()">
🔍 Player Debug
</a>
</div>
<!-- Quest Status link -->
<div class="quest-status-link">
<a href="#" id="questStatusBtn" onclick="openQuestStatus()">
⏰ Quest Status
</a>
</div>
<!-- Player Dashboard link -->
<div class="player-dashboard-link">
<a href="#" id="playerDashboardBtn" onclick="openPlayerDashboard()">
👥 Player Dashboard
</a>
</div>
<!-- Issues Board link -->
<div class="quest-status-link">
<a href="#" id="issuesBoardBtn" onclick="showIssuesWindow()">
📋 Issues Board
</a>
</div>
<!-- Vital Sharing Network link -->
<div class="quest-status-link">
<a href="#" id="vitalSharingBtn" onclick="showVitalSharingWindow()">
🤝 Vital Sharing
</a>
</div>
<!-- Combat Stats link -->
<div class="quest-status-link">
<a href="#" id="combatStatsBtn" onclick="showCombatStatsWindow()">
Combat Stats
</a>
</div>
<!-- Container for sort and filter controls -->
<div id="sortButtons" class="sort-buttons"></div>
<!-- Text input to filter active players by name -->
<input type="text" id="playerFilter" class="player-filter" placeholder="Filter players..." />
<ul id="playerList"></ul>
<!-- User info section (populated by script.js after /me fetch) -->
<div id="userInfo" class="user-info" style="display:none;">
<span id="currentUsername" class="user-info-name"></span>
<a href="#" id="adminLink" class="user-info-admin" style="display:none;" onclick="window.open('/admin/users','_blank')">Admin</a>
<a href="/logout" class="user-info-logout">Logout</a>
</div>
</aside>
<!-- Epic rare notifications container -->
<div id="rareNotifications" class="rare-notifications"></div>
<!-- Fireworks container -->
<div id="fireworksContainer" class="fireworks-container"></div>
<!-- Main map container showing terrain and player data -->
<div id="mapContainer">
<div id="mapGroup">
<img id="map" src="dereth.png" alt="Dereth map">
<canvas id="heatmapCanvas"></canvas>
<svg id="trails"></svg>
<div id="dots"></div>
<div id="portals"></div>
</div>
<div id="tooltip" class="tooltip"></div>
<div id="coordinates" class="coordinates"></div>
</div>
<!-- Main JavaScript file for WebSocket communication and UI logic -->
<script src="script.js" defer></script>
</body>
</html> </html>

72
static/sw.js Normal file
View file

@ -0,0 +1,72 @@
// Service worker for MosswartOverlord v2 — caches static assets for instant repeat loads
const CACHE_NAME = 'mo-v2-cache-v1';
const STATIC_ASSETS = [
'/dereth.png',
'/dereth_highres.png',
'/prismatic-taper-icon.png',
'/icons/0600127E.png',
'/icons/06000133.png',
'/icons/06001080.png',
];
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then(cache => cache.addAll(STATIC_ASSETS))
);
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k)))
)
);
self.clients.claim();
});
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
// Cache icon images on first fetch
if (url.pathname.startsWith('/icons/') && event.request.method === 'GET') {
event.respondWith(
caches.match(event.request).then(cached => {
if (cached) return cached;
return fetch(event.request).then(response => {
if (response.ok) {
const clone = response.clone();
caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
}
return response;
});
})
);
return;
}
// Cache dungeon_tiles.json (large, rarely changes)
if (url.pathname === '/dungeon_tiles.json') {
event.respondWith(
caches.match(event.request).then(cached => {
if (cached) return cached;
return fetch(event.request).then(response => {
if (response.ok) {
const clone = response.clone();
caches.open(CACHE_NAME).then(cache => cache.put(event.request, clone));
}
return response;
});
})
);
return;
}
// Cache static assets (map images etc)
if (STATIC_ASSETS.some(a => url.pathname === a)) {
event.respondWith(
caches.match(event.request).then(cached => cached || fetch(event.request))
);
return;
}
});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import{u as c,j as r,D as d}from"./index-DzGubmvT.js";import"./react-DlyoauG8.js";const p=({id:n,zIndex:i,characters:a})=>{const{openWindow:s}=c(),e=Array.from(a.keys()).sort();return r.jsx(d,{id:n,title:"Combat Stats — Select Character",zIndex:i,width:300,height:400,children:r.jsx("div",{style:{flex:1,overflowY:"auto",padding:6},children:e.length===0?r.jsx("div",{style:{padding:12,color:"#666",textAlign:"center",fontSize:"0.8rem"},children:"No characters online"}):e.map(o=>r.jsx("div",{style:{padding:"5px 8px",cursor:"pointer",borderBottom:"1px solid #222",color:"#ccc",fontSize:"0.82rem"},onMouseEnter:t=>t.currentTarget.style.background="#2a2a2a",onMouseLeave:t=>t.currentTarget.style.background="",onClick:()=>s(`combat-${o}`,`Combat: ${o}`,o),children:o},o))})})};export{p as CombatPickerWindow};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
import{r as o,j as t,D as d}from"./index-DzGubmvT.js";import"./react-DlyoauG8.js";const c=[{title:"Kills per Hour",id:1},{title:"Memory (MB)",id:2},{title:"CPU (%)",id:3},{title:"Mem Handles",id:4}],m=[{label:"1H",value:"now-1h"},{label:"6H",value:"now-6h"},{label:"24H",value:"now-24h"},{label:"7D",value:"now-7d"}],v=({id:s,charName:a,zIndex:i})=>{const[l,r]=o.useState("now-24h"),n=e=>`/grafana/d-solo/dereth-tracker/dereth-tracker-dashboard?panelId=${e}&var-character=${encodeURIComponent(a)}&from=${l}&to=now&theme=light`;return t.jsxs(d,{id:s,title:`Stats: ${a}`,zIndex:i,width:750,height:480,children:[t.jsx("div",{className:"ml-stats-controls",children:m.map(e=>t.jsx("button",{className:`ml-stats-range-btn ${l===e.value?"active":""}`,onClick:()=>r(e.value),children:e.label},e.value))}),t.jsx("div",{className:"ml-stats-grid",children:c.map(e=>t.jsx("div",{className:"ml-stats-panel",children:t.jsx("iframe",{src:n(e.id),width:"100%",height:"100%",frameBorder:"0",title:e.title})},e.id))})]})};export{v as StatsWindow};

View file

@ -0,0 +1 @@
import{r as n,j as t,D as x,a as m}from"./index-DzGubmvT.js";import"./react-DlyoauG8.js";const v=({id:o,zIndex:c})=>{const[a,d]=n.useState([]);n.useEffect(()=>{const e=async()=>{try{const s=await m("/vital-sharing/peers");d(s.peers??[])}catch{}};e();const l=setInterval(e,5e3);return()=>clearInterval(l)},[]);const h=(e,l)=>l>0?Math.min(100,e/l*100):0;return t.jsx(x,{id:o,title:"Vital Sharing Network",zIndex:c,width:520,height:450,children:t.jsx("div",{style:{flex:1,overflowY:"auto",padding:6,fontSize:"0.75rem"},children:a.length===0?t.jsx("div",{style:{padding:16,color:"#666",textAlign:"center"},children:"No vital-sharing peers connected"}):a.map(e=>{var l,s,r;return t.jsxs("div",{style:{padding:"6px 8px",marginBottom:4,background:"#1f1f1f",borderRadius:3,border:"1px solid #333"},children:[t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:3},children:[t.jsx("span",{style:{color:e.plugin_connected?"#4c4":"#a33",fontSize:"0.8rem"},children:"●"}),t.jsx("strong",{style:{flex:1},children:e.character_name}),e.subscribed&&t.jsx("span",{style:{color:"#6bf",fontSize:"0.65rem"},children:"[subscribed]"})]}),t.jsxs("div",{style:{color:"#666",fontSize:"0.68rem",marginBottom:3},children:["tags: ",((l=e.tags)==null?void 0:l.join(", "))||"none"]}),e.vitals&&e.vitals.max_health>0&&t.jsx("div",{style:{display:"flex",flexDirection:"column",gap:2},children:[{label:"HP",cur:e.vitals.current_health,max:e.vitals.max_health,bg:"#330000",fill:"#c44"},{label:"STA",cur:e.vitals.current_stamina,max:e.vitals.max_stamina,bg:"#331a00",fill:"#ca0"},{label:"MANA",cur:e.vitals.current_mana,max:e.vitals.max_mana,bg:"#001433",fill:"#48f"}].map(i=>t.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[t.jsx("span",{style:{width:32,color:"#888",fontSize:"0.65rem"},children:i.label}),t.jsx("div",{style:{flex:1,height:6,background:i.bg,borderRadius:3,overflow:"hidden"},children:t.jsx("div",{style:{width:`${h(i.cur,i.max)}%`,height:"100%",background:i.fill,borderRadius:3}})}),t.jsxs("span",{style:{width:60,textAlign:"right",fontSize:"0.65rem",color:"#888"},children:[i.cur,"/",i.max]})]},i.label))}),e.position&&t.jsxs("div",{style:{color:"#555",fontSize:"0.65rem",marginTop:2},children:[(s=e.position.ns)==null?void 0:s.toFixed(1),"N, ",(r=e.position.ew)==null?void 0:r.toFixed(1),"E"]})]},e.character_name)})})})};export{v as VitalSharingWindow};

File diff suppressed because one or more lines are too long

View file

@ -8,9 +8,9 @@
<link rel="preload" as="image" href="/dereth.png" /> <link rel="preload" as="image" href="/dereth.png" />
<link rel="preload" as="image" href="/icons/0600127E.png" /> <link rel="preload" as="image" href="/icons/0600127E.png" />
<link rel="preload" as="fetch" href="/dungeon_tiles.json" crossorigin="anonymous" /> <link rel="preload" as="fetch" href="/dungeon_tiles.json" crossorigin="anonymous" />
<script type="module" crossorigin src="/v2/assets/index-BfJ04YaG.js"></script> <script type="module" crossorigin src="/assets/index-DzGubmvT.js"></script>
<link rel="modulepreload" crossorigin href="/v2/assets/react-DlyoauG8.js"> <link rel="modulepreload" crossorigin href="/assets/react-DlyoauG8.js">
<link rel="stylesheet" crossorigin href="/v2/assets/index-CyLyPOVJ.css"> <link rel="stylesheet" crossorigin href="/assets/index-CyLyPOVJ.css">
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>