agoara update
This commit is contained in:
672
templates/agora.html
Normal file
672
templates/agora.html
Normal file
@@ -0,0 +1,672 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="el">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Αγορά — Grepolis Remote</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--bg: #0f0f1a;
|
||||
--panel: #181824;
|
||||
--border: #2a2a3e;
|
||||
--purple: #b482dc;
|
||||
--purple-dim: rgba(180,130,220,0.12);
|
||||
--gold: #c8a44a;
|
||||
--green: #4acc64;
|
||||
--red: #e05555;
|
||||
--yellow: #e0b847;
|
||||
--text: #e0e0e0;
|
||||
--muted: #666;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
font-family: 'Inter', sans-serif;
|
||||
color: var(--text);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ── Top bar ── */
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.9rem 1.5rem;
|
||||
background: #13131f;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.topbar-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 800;
|
||||
color: var(--purple);
|
||||
flex: 1;
|
||||
}
|
||||
.topbar a {
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
font-size: 0.82rem;
|
||||
transition: color .2s;
|
||||
}
|
||||
.topbar a:hover { color: var(--text); }
|
||||
.status-dot {
|
||||
width: 8px; height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--muted);
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.status-dot.online { background: var(--green); box-shadow: 0 0 6px var(--green); }
|
||||
|
||||
/* ── Main layout ── */
|
||||
.main {
|
||||
display: grid;
|
||||
grid-template-columns: 300px 1fr;
|
||||
grid-template-rows: 1fr auto;
|
||||
gap: 0;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
height: calc(100vh - 52px);
|
||||
}
|
||||
|
||||
/* ── Left panel ── */
|
||||
.left-panel {
|
||||
background: var(--panel);
|
||||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
.panel-header {
|
||||
padding: 1rem 1.2rem 0.6rem;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .08em;
|
||||
color: var(--muted);
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.search-box {
|
||||
padding: 0.7rem 1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.search-box input {
|
||||
width: 100%;
|
||||
background: #1e1e2e;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
padding: 6px 10px;
|
||||
color: var(--text);
|
||||
font-size: 0.82rem;
|
||||
outline: none;
|
||||
transition: border-color .2s;
|
||||
}
|
||||
.search-box input:focus { border-color: var(--purple); }
|
||||
|
||||
.town-list {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
.town-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
padding: 0.65rem 1.1rem;
|
||||
border-bottom: 1px solid #1e1e2e;
|
||||
cursor: pointer;
|
||||
transition: background .15s;
|
||||
position: relative;
|
||||
}
|
||||
.town-row:hover { background: #1e1e30; }
|
||||
.town-row.active { background: var(--purple-dim); border-left: 3px solid var(--purple); }
|
||||
.town-row .t-name { font-size: 0.84rem; font-weight: 600; flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.town-row .t-dots { display: flex; gap: 4px; }
|
||||
.t-dot {
|
||||
width: 8px; height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--muted);
|
||||
}
|
||||
.t-dot.available { background: var(--green); }
|
||||
.t-dot.cooldown { background: var(--yellow); }
|
||||
.t-dot.unavailable { background: #333; }
|
||||
|
||||
/* ── Right panel ── */
|
||||
.right-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
.cards-area {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1.5rem;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1.2rem;
|
||||
align-content: start;
|
||||
}
|
||||
.no-selection {
|
||||
grid-column: 1 / -1;
|
||||
text-align: center;
|
||||
color: var(--muted);
|
||||
font-size: 0.9rem;
|
||||
padding: 4rem 0;
|
||||
}
|
||||
|
||||
/* ── Celebration cards ── */
|
||||
.cel-card {
|
||||
background: #1e1e30;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
padding: 1.4rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.9rem;
|
||||
transition: border-color .2s;
|
||||
}
|
||||
.cel-card.available { border-color: rgba(180,130,220,0.35); }
|
||||
.cel-card.cooldown { border-color: rgba(224,184,71,0.35); }
|
||||
.cel-card-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
color: var(--purple);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.cel-card.cooldown .cel-card-title { color: var(--yellow); }
|
||||
|
||||
.cost-row {
|
||||
display: flex;
|
||||
gap: 0.8rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.cost-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.cost-item.ok { color: var(--green); }
|
||||
.cost-item.bad { color: var(--red); }
|
||||
.cost-item.neutral { color: var(--text); }
|
||||
|
||||
.reason-box {
|
||||
font-size: 0.78rem;
|
||||
color: var(--yellow);
|
||||
padding: 6px 10px;
|
||||
background: rgba(224,184,71,0.08);
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(224,184,71,0.2);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.reason-box.error {
|
||||
color: var(--red);
|
||||
background: rgba(224,85,85,0.08);
|
||||
border-color: rgba(224,85,85,0.2);
|
||||
}
|
||||
|
||||
.cel-btn {
|
||||
padding: 9px 16px;
|
||||
border-radius: 9px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 0.84rem;
|
||||
font-weight: 700;
|
||||
transition: all .2s;
|
||||
align-self: flex-start;
|
||||
}
|
||||
.cel-btn.primary {
|
||||
background: var(--purple);
|
||||
color: #fff;
|
||||
}
|
||||
.cel-btn.primary:hover { background: #c99ef0; transform: translateY(-1px); }
|
||||
.cel-btn:disabled {
|
||||
background: #2a2a3e;
|
||||
color: var(--muted);
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* ── Log panel ── */
|
||||
.log-panel {
|
||||
border-top: 1px solid var(--border);
|
||||
background: #13131f;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.log-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
.log-header span { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
|
||||
.log-body {
|
||||
overflow-y: auto;
|
||||
max-height: 170px;
|
||||
padding: 0.5rem 1rem;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.77rem;
|
||||
display: none;
|
||||
}
|
||||
.log-body.open { display: block; }
|
||||
.log-entry {
|
||||
display: flex;
|
||||
gap: 0.8rem;
|
||||
padding: 3px 0;
|
||||
border-bottom: 1px solid #1a1a28;
|
||||
align-items: center;
|
||||
}
|
||||
.log-time { color: var(--muted); min-width: 55px; }
|
||||
.log-town { color: var(--purple); min-width: 140px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.log-type { color: var(--text); min-width: 150px; }
|
||||
.log-status.success { color: var(--green); }
|
||||
.log-status.failed { color: var(--red); }
|
||||
.log-status.pending { color: var(--yellow); }
|
||||
.log-msg { color: var(--muted); font-size: 0.72rem; flex: 1; }
|
||||
.log-empty { color: var(--muted); padding: 0.6rem 0; font-size: 0.8rem; }
|
||||
|
||||
/* ── Confirm Modal ── */
|
||||
.modal-overlay {
|
||||
display: none;
|
||||
position: fixed; inset: 0;
|
||||
background: rgba(0,0,0,0.7);
|
||||
z-index: 100;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.modal-overlay.open { display: flex; }
|
||||
.modal-box {
|
||||
background: #1e1e30;
|
||||
border: 1px solid var(--purple);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
min-width: 340px;
|
||||
max-width: 420px;
|
||||
box-shadow: 0 20px 60px rgba(180,130,220,0.2);
|
||||
}
|
||||
.modal-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 800;
|
||||
color: var(--purple);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.modal-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.85rem;
|
||||
padding: 5px 0;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
.modal-row span:first-child { color: var(--muted); }
|
||||
.modal-row span:last-child { font-weight: 600; }
|
||||
.modal-actions {
|
||||
display: flex;
|
||||
gap: 0.8rem;
|
||||
margin-top: 1.4rem;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.btn-cancel {
|
||||
padding: 8px 18px;
|
||||
border-radius: 8px;
|
||||
background: #2a2a3e;
|
||||
border: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
font-size: 0.84rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.btn-cancel:hover { border-color: var(--purple); }
|
||||
.btn-confirm {
|
||||
padding: 8px 20px;
|
||||
border-radius: 8px;
|
||||
background: var(--purple);
|
||||
border: none;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
font-size: 0.84rem;
|
||||
font-weight: 700;
|
||||
transition: background .2s;
|
||||
}
|
||||
.btn-confirm:hover { background: #c99ef0; }
|
||||
.btn-confirm:disabled { background: #444; color: var(--muted); cursor: not-allowed; }
|
||||
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 1.5rem;
|
||||
right: 1.5rem;
|
||||
padding: 10px 18px;
|
||||
border-radius: 10px;
|
||||
font-size: 0.84rem;
|
||||
font-weight: 600;
|
||||
z-index: 200;
|
||||
opacity: 0;
|
||||
transition: opacity .3s;
|
||||
pointer-events: none;
|
||||
}
|
||||
.toast.show { opacity: 1; }
|
||||
.toast.ok { background: rgba(74,204,100,0.15); border: 1px solid var(--green); color: var(--green); }
|
||||
.toast.err { background: rgba(224,85,85,0.15); border: 1px solid var(--red); color: var(--red); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Top Bar -->
|
||||
<div class="topbar">
|
||||
<div class="topbar-title">🎭 Αγορά</div>
|
||||
<span id="online-indicator"><span class="status-dot" id="status-dot"></span><span id="status-text">Φόρτωση...</span></span>
|
||||
<a href="/player/{{ player_id }}/{{ world_id }}">← Hub</a>
|
||||
</div>
|
||||
|
||||
<!-- Main Layout -->
|
||||
<div class="main">
|
||||
|
||||
<!-- Left: Town List -->
|
||||
<div class="left-panel">
|
||||
<div class="panel-header">Πόλεις (<span id="town-count">0</span>)</div>
|
||||
<div class="search-box">
|
||||
<input type="text" id="search" placeholder="Αναζήτηση πόλης...">
|
||||
</div>
|
||||
<div class="town-list" id="town-list">
|
||||
<div style="padding:1.5rem;color:#555;font-size:0.82rem">Φόρτωση...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: Cards + Log -->
|
||||
<div class="right-panel">
|
||||
|
||||
<div class="cards-area" id="cards-area">
|
||||
<div class="no-selection">⬅ Επέλεξε πόλη για να δεις τις διαθέσιμες εορτές</div>
|
||||
</div>
|
||||
|
||||
<!-- Log Panel -->
|
||||
<div class="log-panel">
|
||||
<div class="log-header" onclick="toggleLog()">
|
||||
<span>📜 Αρχείο Αγοράς</span>
|
||||
<span id="log-toggle-icon">▲</span>
|
||||
</div>
|
||||
<div class="log-body open" id="log-body">
|
||||
<div class="log-empty" id="log-empty">Δεν υπάρχουν εγγραφές ακόμα.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Confirm Modal -->
|
||||
<div class="modal-overlay" id="confirm-modal">
|
||||
<div class="modal-box">
|
||||
<div class="modal-title" id="modal-title">Επιβεβαίωση Εορτής</div>
|
||||
<div id="modal-rows"></div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn-cancel" onclick="closeModal()">Ακύρωση</button>
|
||||
<button class="btn-confirm" id="btn-confirm" onclick="fireCommand()">⚡ Εκτέλεση</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast"></div>
|
||||
|
||||
<script>
|
||||
const PLAYER_ID = '{{ player_id }}';
|
||||
const WORLD_ID = '{{ world_id }}';
|
||||
|
||||
const TYPE_LABELS = { party: 'Γιορτή πόλης 🎉', triumph: 'Παρέλαση θριάμβου ⚔️' };
|
||||
const PARTY_COSTS = { wood: 15000, stone: 18000, iron: 15000 };
|
||||
|
||||
let agoraData = [];
|
||||
let selectedId = null;
|
||||
let pendingCmd = null; // { town, cel_type }
|
||||
let logOpen = true;
|
||||
|
||||
// ── Fetch agora data ─────────────────────────────────────────────
|
||||
async function fetchAgora() {
|
||||
try {
|
||||
const r = await fetch(`/dashboard/agora?player_id=${PLAYER_ID}&world_id=${WORLD_ID}`);
|
||||
const d = await r.json();
|
||||
agoraData = d.towns || [];
|
||||
document.getElementById('town-count').textContent = agoraData.length;
|
||||
renderTownList();
|
||||
if (selectedId) renderCards(agoraData.find(t => t.town_id === selectedId));
|
||||
} catch (e) { /* silent */ }
|
||||
}
|
||||
|
||||
// ── Render town list ─────────────────────────────────────────────
|
||||
function renderTownList() {
|
||||
const q = document.getElementById('search').value.toLowerCase();
|
||||
const list = document.getElementById('town-list');
|
||||
const filtered = agoraData.filter(t => t.town_name.toLowerCase().includes(q));
|
||||
|
||||
if (!filtered.length) {
|
||||
list.innerHTML = '<div style="padding:1rem;color:#555;font-size:0.82rem">Δεν βρέθηκαν πόλεις.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = filtered.map(t => `
|
||||
<div class="town-row ${t.town_id === selectedId ? 'active' : ''}"
|
||||
onclick="selectTown('${t.town_id}')">
|
||||
<span class="t-name">${t.town_name}</span>
|
||||
<span class="t-dots">
|
||||
<span class="t-dot ${t.party.status}" title="Γιορτή πόλης: ${t.party.status}"></span>
|
||||
<span class="t-dot ${t.triumph.status}" title="Παρέλαση: ${t.triumph.status}"></span>
|
||||
</span>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function selectTown(townId) {
|
||||
selectedId = townId;
|
||||
renderTownList();
|
||||
renderCards(agoraData.find(t => t.town_id === townId));
|
||||
}
|
||||
|
||||
// ── Render celebration cards ─────────────────────────────────────
|
||||
function renderCards(town) {
|
||||
const area = document.getElementById('cards-area');
|
||||
if (!town) {
|
||||
area.innerHTML = '<div class="no-selection">⬅ Επέλεξε πόλη για να δεις τις διαθέσιμες εορτές</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
area.innerHTML = `
|
||||
${celebCard(town, 'party')}
|
||||
${celebCard(town, 'triumph')}
|
||||
`;
|
||||
}
|
||||
|
||||
function celebCard(town, type) {
|
||||
const cel = town[type];
|
||||
const label = TYPE_LABELS[type];
|
||||
|
||||
let costsHtml = '';
|
||||
if (type === 'party') {
|
||||
const r = town.resources;
|
||||
costsHtml = `
|
||||
<div class="cost-row">
|
||||
<span class="cost-item ${r.wood >= PARTY_COSTS.wood ? 'ok' : 'bad'}">🪵 ${fmt(r.wood)} / ${fmt(PARTY_COSTS.wood)}</span>
|
||||
<span class="cost-item ${r.stone >= PARTY_COSTS.stone ? 'ok' : 'bad'}">🪨 ${fmt(r.stone)} / ${fmt(PARTY_COSTS.stone)}</span>
|
||||
<span class="cost-item ${r.iron >= PARTY_COSTS.iron ? 'ok' : 'bad'}">⚙️ ${fmt(r.iron)} / ${fmt(PARTY_COSTS.iron)}</span>
|
||||
</div>`;
|
||||
} else {
|
||||
const needed = 300;
|
||||
costsHtml = `
|
||||
<div class="cost-row">
|
||||
<span class="cost-item ${town.battle_points >= needed ? 'ok' : 'bad'}">⚔️ ${fmt(town.battle_points)} / ${needed} πόντοι</span>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
const reasonHtml = cel.reason
|
||||
? `<div class="reason-box ${cel.status === 'cooldown' ? '' : 'error'}">${cel.reason}</div>`
|
||||
: '';
|
||||
|
||||
const btnLabel = cel.status === 'cooldown' ? `⏰ ${cel.reason}`
|
||||
: cel.status === 'unavailable' ? '✖ Μη διαθέσιμο'
|
||||
: `▶ Εκκίνηση ${label}`;
|
||||
|
||||
return `
|
||||
<div class="cel-card ${cel.status}">
|
||||
<div class="cel-card-title">${label}</div>
|
||||
${costsHtml}
|
||||
${reasonHtml}
|
||||
<button class="cel-btn primary"
|
||||
${cel.available ? '' : 'disabled'}
|
||||
onclick="openModal('${town.town_id}','${town.town_name}','${type}')">
|
||||
${cel.available ? `▶ Εκκίνηση` : (cel.status === 'cooldown' ? `⏰ Σε αναμονή` : `✖ Μη διαθέσιμο`)}
|
||||
</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function fmt(n) {
|
||||
if (n >= 1000) return (n / 1000).toFixed(1) + 'k';
|
||||
return n;
|
||||
}
|
||||
|
||||
// ── Modal ────────────────────────────────────────────────────────
|
||||
function openModal(townId, townName, celType) {
|
||||
pendingCmd = { townId, townName, celType };
|
||||
document.getElementById('modal-title').textContent = `Επιβεβαίωση — ${TYPE_LABELS[celType]}`;
|
||||
|
||||
let rows = `
|
||||
<div class="modal-row"><span>Πόλη</span><span>${townName}</span></div>`;
|
||||
|
||||
if (celType === 'party') {
|
||||
rows += `
|
||||
<div class="modal-row"><span>Ξύλο</span><span>-15.000</span></div>
|
||||
<div class="modal-row"><span>Πέτρα</span><span>-18.000</span></div>
|
||||
<div class="modal-row"><span>Σίδερο</span><span>-15.000</span></div>`;
|
||||
} else {
|
||||
rows += `<div class="modal-row"><span>Πόντοι Μάχης</span><span>-300</span></div>`;
|
||||
}
|
||||
|
||||
document.getElementById('modal-rows').innerHTML = rows;
|
||||
document.getElementById('btn-confirm').disabled = false;
|
||||
document.getElementById('confirm-modal').classList.add('open');
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('confirm-modal').classList.remove('open');
|
||||
pendingCmd = null;
|
||||
}
|
||||
|
||||
async function fireCommand() {
|
||||
if (!pendingCmd) return;
|
||||
document.getElementById('btn-confirm').disabled = true;
|
||||
|
||||
try {
|
||||
const r = await fetch('/dashboard/culture-command', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
player_id: PLAYER_ID,
|
||||
world_id: WORLD_ID,
|
||||
town_id: pendingCmd.townId,
|
||||
town_name: pendingCmd.townName,
|
||||
celebration_type: pendingCmd.celType
|
||||
})
|
||||
});
|
||||
const d = await r.json();
|
||||
closeModal();
|
||||
if (r.ok && d.ok) {
|
||||
showToast(`✅ Εντολή στάλθηκε — ${TYPE_LABELS[pendingCmd.celType]}`, 'ok');
|
||||
fetchLog();
|
||||
setTimeout(fetchAgora, 3000);
|
||||
} else {
|
||||
showToast(`❌ ${d.message || d.error || 'Αποτυχία'}`, 'err');
|
||||
}
|
||||
} catch (e) {
|
||||
showToast(`❌ Σφάλμα: ${e}`, 'err');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Log ──────────────────────────────────────────────────────────
|
||||
function toggleLog() {
|
||||
logOpen = !logOpen;
|
||||
document.getElementById('log-body').classList.toggle('open', logOpen);
|
||||
document.getElementById('log-toggle-icon').textContent = logOpen ? '▲' : '▼';
|
||||
}
|
||||
|
||||
async function fetchLog() {
|
||||
try {
|
||||
const r = await fetch(`/dashboard/culture-log?player_id=${PLAYER_ID}&world_id=${WORLD_ID}`);
|
||||
const rows = await r.json();
|
||||
const body = document.getElementById('log-body');
|
||||
const empty = document.getElementById('log-empty');
|
||||
|
||||
if (!rows.length) {
|
||||
if (empty) empty.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
if (empty) empty.style.display = 'none';
|
||||
|
||||
const existing = new Set([...body.querySelectorAll('.log-entry')].map(el => el.dataset.id));
|
||||
|
||||
rows.forEach(row => {
|
||||
if (existing.has(String(row.id))) return;
|
||||
const el = document.createElement('div');
|
||||
el.className = 'log-entry';
|
||||
el.dataset.id = row.id;
|
||||
|
||||
const t = new Date(row.fired_at + 'Z');
|
||||
const time = t.toLocaleTimeString('el-GR', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||
|
||||
const costStr = row.cost_battle_pts
|
||||
? `-${row.cost_battle_pts} πόντοι`
|
||||
: `-${fmt(row.cost_wood)} ξύλο / -${fmt(row.cost_stone)} πέτρα / -${fmt(row.cost_iron)} σίδερο`;
|
||||
|
||||
el.innerHTML = `
|
||||
<span class="log-time">${time}</span>
|
||||
<span class="log-town">${row.town_name}</span>
|
||||
<span class="log-type">${TYPE_LABELS[row.celebration_type] || row.celebration_type}</span>
|
||||
<span class="log-status ${row.status}">${row.status === 'success' ? '✅' : row.status === 'failed' ? '❌' : '⏳'}</span>
|
||||
<span class="log-msg">${costStr}${row.result_msg ? ' — ' + row.result_msg : ''}</span>`;
|
||||
body.insertBefore(el, body.firstChild);
|
||||
});
|
||||
} catch (e) { /* silent */ }
|
||||
}
|
||||
|
||||
// ── Online status ────────────────────────────────────────────────
|
||||
async function checkStatus() {
|
||||
try {
|
||||
const r = await fetch(`/dashboard/client-status?player_id=${PLAYER_ID}`);
|
||||
const d = await r.json();
|
||||
const dot = document.getElementById('status-dot');
|
||||
const text = document.getElementById('status-text');
|
||||
dot.className = 'status-dot' + (d.online ? ' online' : '');
|
||||
text.textContent = d.online ? 'Online' : 'Offline';
|
||||
} catch (e) { /* silent */ }
|
||||
}
|
||||
|
||||
// ── Toast ────────────────────────────────────────────────────────
|
||||
function showToast(msg, type) {
|
||||
const t = document.getElementById('toast');
|
||||
t.textContent = msg;
|
||||
t.className = `toast ${type} show`;
|
||||
setTimeout(() => { t.className = 'toast'; }, 3500);
|
||||
}
|
||||
|
||||
// ── Search ───────────────────────────────────────────────────────
|
||||
document.getElementById('search').addEventListener('input', renderTownList);
|
||||
|
||||
// ── Init ─────────────────────────────────────────────────────────
|
||||
fetchAgora();
|
||||
fetchLog();
|
||||
checkStatus();
|
||||
setInterval(fetchAgora, 30000);
|
||||
setInterval(fetchLog, 15000);
|
||||
setInterval(checkStatus, 30000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -100,6 +100,11 @@
|
||||
.hub-card.tracker::before { background: radial-gradient(circle at top left, rgba(111,207,207,0.08), transparent 70%); }
|
||||
.hub-card.tracker:hover { border-color: #6fcfcf; box-shadow: 0 12px 40px rgba(111,207,207,0.15); }
|
||||
|
||||
/* Αγορά — purple/gold */
|
||||
.hub-card.agora { border-color: #2d1f3f; }
|
||||
.hub-card.agora::before { background: radial-gradient(circle at top left, rgba(180,130,220,0.08), transparent 70%); }
|
||||
.hub-card.agora:hover { border-color: #b482dc; box-shadow: 0 12px 40px rgba(180,130,220,0.18); }
|
||||
|
||||
.card-icon {
|
||||
font-size: 2.8rem;
|
||||
margin-bottom: 1rem;
|
||||
@@ -113,6 +118,7 @@
|
||||
.hub-card.admin .card-title { color: #c8a44a; }
|
||||
.hub-card.farm .card-title { color: #4acc64; }
|
||||
.hub-card.tracker .card-title { color: #6fcfcf; }
|
||||
.hub-card.agora .card-title { color: #b482dc; }
|
||||
|
||||
.card-desc {
|
||||
font-size: 0.875rem;
|
||||
@@ -173,6 +179,12 @@
|
||||
<div class="card-desc">Παρακολούθηση κινήσεων στρατού σε πραγματικό χρόνο. Εισερχόμενες επιθέσεις και ενισχύσεις.</div>
|
||||
</a>
|
||||
|
||||
<a href="/player/{{ player_id }}/{{ world_id }}/agora" class="hub-card agora">
|
||||
<span class="card-icon">🎭</span>
|
||||
<div class="card-title">Αγορά</div>
|
||||
<div class="card-desc">Εκκίνηση Γιορτής πόλης και Παρέλασης θριάμβου ανά πόλη. Έλεγχος πόρων, cooldown και ιστορικό εκτελέσεων.</div>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
<a href="/" class="back-link">← Επιστροφή στην επιλογή παίκτη</a>
|
||||
|
||||
Reference in New Issue
Block a user