soccercloud-rust/index.html

506 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SoccerCloud Web</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Sora:wght@500;600;700&display=swap" rel="stylesheet" />
<style>
:root {
--bg: #f4f6f8;
--surface: #ffffff;
--surface-soft: #f9fbff;
--text: #111827;
--muted: #667085;
--line: #d0d8e2;
--brand: #0f766e;
--brand-strong: #0b5f58;
--warn: #b42318;
--pending: #6b7280;
--running: #1d4ed8;
--completed: #047857;
--shadow: 0 24px 64px rgba(15, 23, 42, 0.12);
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
font-family: "Space Grotesk", "Segoe UI", sans-serif;
color: var(--text);
background:
radial-gradient(1200px 640px at -8% -12%, rgba(15, 118, 110, 0.12), transparent 58%),
radial-gradient(980px 680px at 110% -18%, rgba(29, 78, 216, 0.11), transparent 55%),
linear-gradient(180deg, #eef2f8 0%, #f8fafc 100%);
}
.app {
max-width: 1200px;
margin: 0 auto;
padding: 28px 18px 42px;
}
.hero {
display: grid;
grid-template-columns: 1.2fr 0.8fr;
gap: 18px;
margin-bottom: 18px;
}
.hero-card {
background: linear-gradient(145deg, #ffffff, #f7fbff);
border: 1px solid rgba(15, 23, 42, 0.08);
border-radius: 18px;
padding: 20px;
box-shadow: var(--shadow);
animation: rise 420ms ease both;
}
.eyebrow {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.14em;
color: var(--brand);
margin: 0 0 8px;
font-weight: 700;
}
h1 {
font-family: "Sora", "Space Grotesk", sans-serif;
margin: 0;
font-size: clamp(28px, 5vw, 42px);
line-height: 1.05;
letter-spacing: -0.02em;
}
.lead {
margin: 12px 0 0;
color: var(--muted);
font-size: 15px;
max-width: 56ch;
}
.stat-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.stat {
border: 1px solid var(--line);
background: var(--surface-soft);
border-radius: 12px;
padding: 10px 12px;
}
.stat .label {
color: var(--muted);
font-size: 12px;
margin-bottom: 6px;
}
.stat .value {
font-family: "Sora", "Space Grotesk", sans-serif;
font-size: 24px;
line-height: 1;
}
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin: 18px 0;
}
.status-text {
margin: 0;
color: var(--muted);
font-size: 13px;
}
.btn {
border: 1px solid transparent;
background: var(--brand);
color: white;
border-radius: 10px;
padding: 10px 14px;
font-family: inherit;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: background-color 180ms ease, border-color 180ms ease, box-shadow 180ms ease, opacity 180ms ease;
box-shadow: 0 12px 24px rgba(15, 118, 110, 0.25);
}
.btn:hover {
background: rgba(15, 118, 110, 0.72);
border-color: rgba(255, 255, 255, 0.45);
box-shadow: 0 10px 26px rgba(15, 118, 110, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.28);
backdrop-filter: blur(8px) saturate(135%);
-webkit-backdrop-filter: blur(8px) saturate(135%);
}
.btn:active {
opacity: 0.96;
box-shadow: 0 6px 16px rgba(15, 118, 110, 0.2);
}
.btn.secondary {
background: white;
color: var(--text);
border-color: var(--line);
box-shadow: none;
}
.btn.secondary:hover {
background: rgba(255, 255, 255, 0.66);
border-color: rgba(148, 163, 184, 0.7);
box-shadow: 0 8px 18px rgba(15, 23, 42, 0.12), inset 0 1px 0 rgba(255, 255, 255, 0.34);
}
.btn.warn {
background: white;
color: var(--warn);
border-color: rgba(180, 35, 24, 0.34);
box-shadow: none;
}
.btn.warn:hover {
background: rgba(255, 255, 255, 0.66);
border-color: rgba(180, 35, 24, 0.45);
box-shadow: 0 8px 18px rgba(180, 35, 24, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.34);
}
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 14px;
}
.card {
background: var(--surface);
border: 1px solid rgba(15, 23, 42, 0.08);
border-radius: 14px;
padding: 14px;
box-shadow: 0 10px 28px rgba(15, 23, 42, 0.08);
}
.card-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 8px;
margin-bottom: 10px;
}
.card-title {
margin: 0;
font-size: 16px;
line-height: 1.2;
font-family: "Sora", "Space Grotesk", sans-serif;
}
.card-id {
margin: 6px 0 0;
color: var(--muted);
font-size: 12px;
}
.pill {
border-radius: 999px;
padding: 4px 10px;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
color: #fff;
}
.pill.pending { background: var(--pending); }
.pill.running { background: var(--running); }
.pill.completed { background: var(--completed); }
.card-line {
margin: 8px 0;
color: var(--muted);
font-size: 13px;
min-height: 1.3em;
}
.card-score {
margin: 8px 0;
font-size: 14px;
color: var(--text);
}
.card-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 12px;
}
.empty {
background: rgba(255, 255, 255, 0.72);
border: 1px dashed var(--line);
border-radius: 14px;
padding: 26px;
text-align: center;
color: var(--muted);
}
.modal {
position: fixed;
inset: 0;
display: none;
align-items: center;
justify-content: center;
background: rgba(4, 10, 22, 0.62);
backdrop-filter: blur(4px);
z-index: 40;
padding: 16px;
}
.modal.open { display: flex; }
.modal-panel {
width: min(980px, 100%);
max-height: 92vh;
overflow: auto;
background: var(--surface);
border-radius: 16px;
border: 1px solid rgba(15, 23, 42, 0.08);
box-shadow: 0 24px 64px rgba(2, 6, 23, 0.28);
animation: pop 220ms ease both;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 10px;
border-bottom: 1px solid var(--line);
padding: 14px 16px;
position: sticky;
top: 0;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(2px);
z-index: 3;
}
.modal-title {
margin: 0;
font-family: "Sora", "Space Grotesk", sans-serif;
font-size: 18px;
}
.modal-body {
padding: 14px 16px 18px;
}
.field-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 12px;
}
label {
display: flex;
flex-direction: column;
gap: 6px;
font-size: 13px;
color: var(--muted);
}
select,
input[type="checkbox"] {
font: inherit;
}
select {
padding: 10px;
border-radius: 10px;
border: 1px solid var(--line);
background: white;
color: var(--text);
}
.inline {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 12px;
font-size: 14px;
color: var(--muted);
}
.detail-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.panel {
border: 1px solid var(--line);
border-radius: 12px;
padding: 10px;
background: var(--surface-soft);
}
.panel h3 {
margin: 0 0 8px;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--brand);
}
.mono {
margin: 0;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 12px;
line-height: 1.42;
white-space: pre-wrap;
color: #122033;
max-height: 260px;
overflow: auto;
}
.scoreboard {
margin: 0 0 12px;
font-family: "Sora", "Space Grotesk", sans-serif;
font-size: 17px;
padding: 10px 12px;
border: 1px solid var(--line);
border-radius: 12px;
background: white;
}
@keyframes rise {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes pop {
from { opacity: 0; transform: translateY(10px) scale(0.98); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
@media (max-width: 920px) {
.hero,
.detail-grid,
.field-grid {
grid-template-columns: 1fr;
}
.app { padding: 18px 12px 32px; }
}
</style>
</head>
<body>
<main class="app">
<section class="hero">
<article class="hero-card">
<p class="eyebrow">Rust Backend</p>
<h1>SoccerCloud Web Control Room</h1>
<p class="lead">A modern web frontend backed by the same Rust simulation engine used in CLI and TUI modes.</p>
</article>
<article class="hero-card">
<div class="stat-grid">
<div class="stat">
<div class="label">Instances</div>
<div class="value" id="statInstances">0</div>
</div>
<div class="stat">
<div class="label">Running</div>
<div class="value" id="statRunning">0</div>
</div>
<div class="stat">
<div class="label">Completed</div>
<div class="value" id="statCompleted">0</div>
</div>
<div class="stat">
<div class="label">Available Teams</div>
<div class="value" id="statTeams">0</div>
</div>
</div>
</article>
</section>
<div class="toolbar">
<p class="status-text" id="statusText">Connecting to backend...</p>
<button class="btn" id="openCreateBtn" type="button">Create Simulation</button>
</div>
<section class="dashboard" id="dashboard"></section>
</main>
<div class="modal" id="createModal" aria-hidden="true">
<div class="modal-panel">
<div class="modal-header">
<h2 class="modal-title">Create Simulation</h2>
<button class="btn secondary" data-close="createModal" type="button">Close</button>
</div>
<div class="modal-body">
<div class="field-grid">
<label>
Mode
<select id="modeSelect">
<option value="single">Single Match</option>
<option value="league4">4-Team League</option>
<option value="knockout4">4-Team Knockout</option>
</select>
</label>
<label>
Team Count
<select id="teamCount" disabled>
<option value="2">2</option>
</select>
</label>
</div>
<label class="inline">
<input id="autoFill" type="checkbox" checked />
Auto-fill missing teams using deterministic Rust seed
</label>
<div class="field-grid" id="teamSelectWrap"></div>
<button class="btn" id="createBtn" type="button">Create Instance</button>
</div>
</div>
</div>
<div class="modal" id="detailModal" aria-hidden="true">
<div class="modal-panel">
<div class="modal-header">
<h2 class="modal-title" id="detailTitle">Simulation Detail</h2>
<button class="btn secondary" data-close="detailModal" type="button">Close</button>
</div>
<div class="modal-body">
<p class="scoreboard" id="detailScoreboard">Waiting for updates...</p>
<div class="detail-grid">
<section class="panel">
<h3>Match Log</h3>
<pre class="mono" id="detailLogs"></pre>
</section>
<section class="panel">
<h3>Stats</h3>
<pre class="mono" id="detailStats"></pre>
</section>
<section class="panel">
<h3>Competition</h3>
<pre class="mono" id="detailCompetition"></pre>
</section>
<section class="panel">
<h3>History</h3>
<pre class="mono" id="detailHistory"></pre>
</section>
</div>
</div>
</div>
</div>
<script src="data.js"></script>
</body>
</html>