From 8c4f83fb4b80aa0fa4c022fb0b9a587b4eec333a Mon Sep 17 00:00:00 2001 From: Daniel Dolezal Date: Mon, 6 Apr 2026 23:47:31 +0200 Subject: [PATCH] move css styleing into own files and allow to open the game with a url on the battlesnake side --- server/blueprints/dashboard.py | 1 + templates/files/css/root.css | 76 ++++ templates/files/css/styles.css | 738 +++++++++++++++++++++++++++++++++ templates/side/dashboard.htm | 692 +------------------------------ 4 files changed, 818 insertions(+), 689 deletions(-) create mode 100644 templates/files/css/root.css create mode 100644 templates/files/css/styles.css diff --git a/server/blueprints/dashboard.py b/server/blueprints/dashboard.py index 427f7c4..e2c4263 100644 --- a/server/blueprints/dashboard.py +++ b/server/blueprints/dashboard.py @@ -25,6 +25,7 @@ def create_dashboard_blueprint(server:'Server') -> Blueprint: initial_game_id=initial_game_id, initial_summary=initial_summary, initial_games=initial_games, + battlesnake_url=os.getenv('BATTLESNAKE_GAMEBOARD_URL', 'https://play.battlesnake.com/game') ) @blueprint.get('/dashboard/customizations/') diff --git a/templates/files/css/root.css b/templates/files/css/root.css new file mode 100644 index 0000000..8f9fe1c --- /dev/null +++ b/templates/files/css/root.css @@ -0,0 +1,76 @@ +:root { + color-scheme: light; + --bg-1: #f2eee6; + --bg-2: #e7dcc8; + --panel: #fffcf6; + --line: #d9ccb6; + --ink: #252119; + --muted: #6f6657; + --accent: #146a4b; + --accent-soft: #e5f2ed; + --danger: #b0492a; + --surface: #ffffff; + --surface-soft: #fffdf8; + --row-hover: #fdf4e7; + --row-active: #edf8f3; + --shadow: rgba(41, 29, 11, 0.08); + --you: #1a7a56; + --enemy: #bf5b33; + --snake-1: #bf5b33; + --snake-2: #2f6fdd; + --snake-3: #8d4ad6; + --snake-4: #cc7a11; + --snake-5: #0f8f84; + --snake-6: #be3f70; + --snake-7: #6b8a12; + --snake-8: #9a4a2f; + --snake-9: #2e8698; + --snake-10: #7f5fdd; + --food: #cca100; + --hazard: #6a5a9b; + --grid: #e6dbc8; + --cell: #ffffff; + --head-ring: #111111; + --mono-bg: #1d1b18; + --mono-ink: #ecdfcb; + --mono-vh-offset: 430px; +} + +@media (prefers-color-scheme: dark) { + :root { + color-scheme: dark; + --bg-1: #151819; + --bg-2: #1b2022; + --panel: #1f2527; + --line: #374144; + --ink: #e6e8e9; + --muted: #a8b1b3; + --accent: #4ec894; + --accent-soft: #233e35; + --danger: #d1734f; + --surface: #232b2e; + --surface-soft: #273134; + --row-hover: #2b3538; + --row-active: #224338; + --shadow: rgba(0, 0, 0, 0.35); + --you: #4ec894; + --enemy: #e2815a; + --snake-1: #e2815a; + --snake-2: #7ea8ff; + --snake-3: #c198ff; + --snake-4: #f2b857; + --snake-5: #67d2c8; + --snake-6: #ea86ad; + --snake-7: #b8d86b; + --snake-8: #e29d83; + --snake-9: #75c9da; + --snake-10: #b7a0ff; + --food: #ebc14b; + --hazard: #9b86d8; + --grid: #3b464a; + --cell: #1a2022; + --head-ring: #f3f5f6; + --mono-bg: #101416; + --mono-ink: #dce7e9; + } +} diff --git a/templates/files/css/styles.css b/templates/files/css/styles.css new file mode 100644 index 0000000..c923c46 --- /dev/null +++ b/templates/files/css/styles.css @@ -0,0 +1,738 @@ +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + height: 100%; + overflow: hidden; + color: var(--ink); + font-family: "IBM Plex Sans", "Segoe UI", sans-serif; + background: linear-gradient(180deg, var(--bg-1), var(--bg-2)); +} + +.page { + height: 100vh; + display: grid; + grid-template-rows: auto 1fr; + gap: 12px; + padding: 12px; + overflow: hidden; +} + +.topbar { + display: grid; + grid-template-columns: 1fr auto; + gap: 12px; + align-items: center; + background: var(--panel); + border: 1px solid var(--line); + border-radius: 12px; + padding: 10px 12px; + box-shadow: 0 8px 28px var(--shadow); +} + +.title { + margin: 0; + font-size: 1.12rem; + letter-spacing: 0.01em; +} + +.subtitle { + margin: 4px 0 0; + color: var(--muted); + font-size: 0.86rem; +} + +.stats { + display: grid; + grid-template-columns: repeat(6, minmax(90px, 1fr)); + gap: 8px; + min-width: 520px; +} + +.stat { + border: 1px solid #eadfcd; + border-radius: 8px; + background: var(--surface); + padding: 8px; + text-align: center; +} + +.stat .k { + font-size: 0.72rem; + color: var(--muted); + display: block; +} + +.stat .v { + font-size: 1.05rem; + font-weight: 700; +} + +.main { + min-height: 0; + display: grid; + grid-template-columns: 330px 1fr; + gap: 12px; +} + +.panel { + background: var(--panel); + border: 1px solid var(--line); + border-radius: 12px; + box-shadow: 0 8px 28px var(--shadow); + min-height: 0; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.panel-header { + padding: 10px 12px; + border-bottom: 1px solid #eadfcd; +} + +.panel-header h2 { + margin: 0; + font-size: 1rem; +} + +.panel-header p { + margin: 4px 0 0; + font-size: 0.8rem; + color: var(--muted); +} + +.games { + overflow-y: auto; + overflow-x: hidden; + flex: 1; + min-height: 0; +} + +table { + width: 100%; + border-collapse: collapse; + font-size: 0.88rem; +} + +thead th { + position: sticky; + top: 0; + background: var(--bg); + z-index: 1; +} + +th, +td { + text-align: left; + vertical-align: top; + padding: 8px 10px; + border-bottom: 1px solid #efe5d5; +} + +tbody tr { + cursor: pointer; +} + +tbody tr:hover { + background: var(--row-hover); +} + +tbody tr.active { + background: var(--row-active); +} + +#games-body tr:last-child td { + border-bottom: none; +} + +.right { + min-height: 0; + display: flex; + flex-direction: column; +} + +.controls { + display: flex; + gap: 6px; + flex-wrap: nowrap; + align-items: center; + padding: 2px 8px 1px; + border-bottom: 1px solid #eadfcd; + overflow-x: auto; + scrollbar-width: thin; + line-height: 1; + min-height: 0; + height: 34px; + margin: 0; +} + +.controls>* { + margin: 0; + align-self: center; +} + +button { + border: 1px solid #d2c3ab; + background: var(--surface); + color: var(--ink); + border-radius: 6px; + padding: 2px 7px; + font-weight: 600; + font-size: 0.8rem; + line-height: 1.2; + cursor: pointer; +} + +.controls label { + display: flex; + align-items: center; + gap: 4px; + font-size: 0.8rem; + white-space: nowrap; + margin: 0; + line-height: 1; +} + +.controls label span { + display: inline-block; + line-height: 1; +} + +.controls select { + height: 24px; + font-size: 0.8rem; + padding: 0 4px; +} + +.controls input[type="range"] { + width: 150px; + min-width: 130px; + margin: 0; + height: 12px; + padding: 0; +} + +button.primary { + background: var(--accent); + border-color: #0f5a3f; + color: #fff; +} + +#prev-btn, +#next-btn { + width: 52px; + min-width: 52px; + text-align: center; +} + +#play-btn { + width: 62px; + min-width: 62px; + text-align: center; +} + +select, +input[type="range"] { + accent-color: var(--accent); +} + +.turn-badge { + margin-left: auto; + font-size: 0.76rem; + font-weight: 700; + color: var(--accent); + white-space: nowrap; + line-height: 1; +} + +.content { + min-height: 0; + display: grid; + grid-template-columns: minmax(320px, 42%) 1fr; + gap: 12px; + padding: 8px 12px 12px; + align-items: stretch; + margin: 0; + flex: 1 1 auto; +} + +.board-wrap { + min-height: 0; + display: grid; + grid-template-rows: auto 1fr; + gap: 8px; + min-height: 520px; +} + +.legend { + display: flex; + gap: 8px; + flex-wrap: nowrap; + overflow-x: auto; + white-space: nowrap; + scrollbar-width: thin; + font-size: 0.74rem; + color: var(--muted); + padding-top: 5px; +} + +.dot { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 2px; + margin-right: 5px; + vertical-align: baseline; +} + +.board { + min-height: 0; + height: auto; + width: 100%; + display: grid; + gap: 2px; + background: var(--grid); + border: 1px solid var(--line); + border-radius: 10px; + padding: 6px; + align-content: start; +} + +.cell { + background: var(--cell); + width: 100%; + min-width: 0; + min-height: 0; + aspect-ratio: 1 / 1; + position: relative; + border-radius: 2px; +} + +.snake-turn-cell::after { + content: ""; + position: absolute; + inset: 0; + background: var(--turn-color, transparent); + z-index: 0; + pointer-events: none; +} + +/* 50% = quarter-circle at the inner corner of the bend */ +.snake-turn-cell.snake-turn-ur::after { + border-top-right-radius: 50%; +} + +.snake-turn-cell.snake-turn-ul::after { + border-top-left-radius: 50%; +} + +.snake-turn-cell.snake-turn-dr::after { + border-bottom-right-radius: 50%; +} + +.snake-turn-cell.snake-turn-dl::after { + border-bottom-left-radius: 50%; +} + +.food { + background-image: radial-gradient(circle at center, #d73a31 0 45%, transparent 48%); + background-repeat: no-repeat; + background-position: center; + background-size: 78% 78%; +} + +.hazard { + background-color: var(--hazard); +} + +.hazard::before { + content: ""; + position: absolute; + inset: 0; + background: rgba(20, 10, 50, 0.38) repeating-linear-gradient(135deg, + rgba(80, 60, 140, 0.6) 0, + rgba(80, 60, 140, 0.6) 2px, + transparent 2px, + transparent 6px); + z-index: 4; + pointer-events: none; + border-radius: inherit; +} + +.snake-you { + background: var(--you); +} + +.snake-enemy { + background: var(--enemy); +} + +.snake-head { + outline: 2px solid var(--head-ring); + outline-offset: -2px; +} + +.snake-head::after { + content: ""; + position: absolute; + left: 30%; + top: 30%; + width: 40%; + height: 40%; + border-radius: 50%; + background: var(--head-ring); + opacity: 0.9; + z-index: 2; +} + +.snake-head.head-style-1::after { + border-radius: 50%; +} + +.snake-head.head-style-2::after { + border-radius: 2px; + transform: rotate(45deg); +} + +.snake-head.head-style-3::after { + width: 52%; + height: 28%; + top: 36%; + left: 24%; + border-radius: 999px; +} + +.snake-head.head-style-4::after { + width: 24%; + height: 56%; + top: 22%; + left: 38%; + border-radius: 999px; +} + +.snake-head.head-style-5::after { + width: 46%; + height: 46%; + top: 27%; + left: 27%; + clip-path: polygon(50% 0, 100% 100%, 0 100%); + border-radius: 0; +} + +.snake-head.has-head-icon::after { + display: none; +} + +.snake-head.has-head-icon { + outline: none; +} + +.snake-tail-you::after, +.snake-tail-enemy::after { + content: ""; + position: absolute; + right: 6%; + top: 32%; + width: 38%; + height: 36%; + border-radius: 3px; + background: rgba(255, 255, 255, 0.55); + z-index: 2; +} + +.snake-tail-you.tail-style-1::after, +.snake-tail-enemy.tail-style-1::after { + width: 38%; + height: 36%; +} + +.snake-tail-you.tail-style-2::after, +.snake-tail-enemy.tail-style-2::after { + width: 24%; + height: 56%; + right: 10%; + top: 22%; + border-radius: 999px; +} + +.snake-tail-you.tail-style-3::after, +.snake-tail-enemy.tail-style-3::after { + width: 44%; + height: 24%; + right: 8%; + top: 38%; + border-radius: 999px; +} + +.snake-tail-you.tail-style-4::after, +.snake-tail-enemy.tail-style-4::after { + width: 34%; + height: 34%; + right: 10%; + top: 32%; + transform: rotate(45deg); + border-radius: 1px; +} + +.snake-tail-you.tail-style-5::after, +.snake-tail-enemy.tail-style-5::after { + width: 42%; + height: 42%; + right: 8%; + top: 29%; + clip-path: polygon(100% 50%, 0 0, 0 100%); + border-radius: 0; +} + +.snake-tail-you.has-tail-icon::after, +.snake-tail-enemy.has-tail-icon::after { + display: none; +} + +.snake-tail-you.has-tail-icon, +.snake-tail-enemy.has-tail-icon { + box-shadow: none; +} + +.icon-layer { + position: absolute; + inset: 2%; + background: var(--icon-color, currentColor); + -webkit-mask-image: var(--icon-url); + -webkit-mask-repeat: no-repeat; + -webkit-mask-position: center; + -webkit-mask-size: contain; + mask-image: var(--icon-url); + mask-repeat: no-repeat; + mask-position: center; + mask-size: contain; + transform: var(--icon-transform, rotate(0deg)); + transform-origin: center; + pointer-events: none; + z-index: 2; +} + +.icon-layer--tail { + z-index: 2; + opacity: 0.92; +} + +.icon-layer--head { + z-index: 3; + opacity: 1; + background: none; + -webkit-mask-image: none; + mask-image: none; +} + +.icon-layer--head>svg { + width: 100%; + height: 100%; + display: block; + overflow: visible; +} + +.thinking { + min-height: 0; + overflow: auto; + border: 1px solid #e8dcc8; + border-radius: 10px; + background: var(--surface); + padding: 10px; + display: flex; + flex-direction: column; + gap: 10px; + min-height: 420px; +} + +.raw-block { + display: flex; + flex-direction: column; + gap: 6px; + min-height: 0; + flex: 1 1 auto; +} + +.think-grid { + display: grid; + grid-template-columns: repeat(3, minmax(120px, 1fr)); + gap: 8px; +} + +.chip { + border: 1px solid #ebdfcb; + border-radius: 8px; + padding: 8px; + background: var(--surface-soft); +} + +.chip .k { + display: block; + font-size: 0.72rem; + color: var(--muted); +} + +.chip .v { + font-size: 0.95rem; + font-weight: 700; +} + +.section-title { + margin: 0; + font-size: 0.86rem; + color: var(--muted); + font-weight: 700; + letter-spacing: 0.01em; +} + +.reason-list { + margin: 0; + padding-left: 18px; + font-size: 0.85rem; +} + +.score-table { + width: 100%; + border-collapse: collapse; + font-size: 0.84rem; + table-layout: fixed; +} + +.score-table td, +.score-table th { + border-bottom: 1px solid #f0e7d7; + padding: 6px; + white-space: normal; + overflow-wrap: anywhere; +} + +.snake-row { + background: var(--snake-row-bg, transparent); + cursor: pointer; + transition: opacity 0.15s, filter 0.15s; +} + +.snake-row td { + color: var(--ink); +} + +.snake-row td:first-child { + border-left: 4px solid var(--snake-row-color, transparent); + padding-left: 8px; +} + +.snake-row.highlighted { + outline: 2px solid var(--snake-row-color, var(--line)); + outline-offset: -1px; +} + +.snakes-section.has-highlight .snake-row:not(.highlighted) { + opacity: 0.25; + filter: grayscale(0.6); +} + +.snake-row.dead-row { + filter: grayscale(0.55); + opacity: 0.78; +} + +.name-cell { + word-break: break-word; +} + +.num-cell { + white-space: nowrap; +} + +.health-wrap { + display: inline-block; + width: 120px; + height: 10px; + border-radius: 999px; + background: rgba(120, 120, 120, 0.18); + overflow: hidden; + position: relative; + vertical-align: middle; +} + +.health-fill { + display: block; + height: 100%; + border-radius: inherit; + transition: width 120ms linear; +} + +.health-text { + margin-left: 6px; + font-size: 0.74rem; + color: inherit; + opacity: 0.82; + vertical-align: middle; +} + +.mono { + margin: 0; + font-family: "IBM Plex Mono", "Consolas", monospace; + font-size: 0.75rem; + background: var(--mono-bg); + color: var(--mono-ink); + border-radius: 8px; + padding: 8px; + overflow: auto; + width: -webkit-fill-available; + height: -webkit-fill-available; + min-height: 0; + flex: 1 1 auto; + resize: none; + max-height: calc(100vh - var(--mono-vh-offset)); +} + +@media (max-width: 1100px) { + .topbar { + grid-template-columns: 1fr; + } + + .stats { + min-width: 0; + grid-template-columns: repeat(6, minmax(80px, 1fr)); + } + + .main { + grid-template-columns: 1fr; + } + + .games { + min-height: 200px; + } + + .content { + grid-template-columns: 1fr; + } + + .board-wrap { + min-height: 360px; + } + + .turn-badge { + margin-left: 0; + } + + .controls { + flex-wrap: wrap; + } + + .think-grid { + grid-template-columns: repeat(2, minmax(120px, 1fr)); + } +} + +@media (max-width: 700px) { + .think-grid { + grid-template-columns: 1fr; + } +} \ No newline at end of file diff --git a/templates/side/dashboard.htm b/templates/side/dashboard.htm index a2d84fc..7d62887 100644 --- a/templates/side/dashboard.htm +++ b/templates/side/dashboard.htm @@ -4,694 +4,8 @@ Snake Dashboard - + +
@@ -829,7 +143,7 @@ function renderGamesTable(games) { gamesBodyEl.innerHTML = games.map((g) => ` - ${shortId(g.game_id)}
${safeString(displayGameTypeOrMap(g))} + ${shortId(g.game_id)}
${safeString(displayGameTypeOrMap(g))} ${toTitle(g.status)} ${g.status === "running" ? "-" : g.winner_you ? "Win" : "Loss"} ${safeString(g.final_turn)}