Files
snake-python/templates/files/css/styles.css
T
daniel156161 f14d780f29
Build and Push Docker Container / build-and-push (push) Successful in 7m53s
feat: add live replays and PostgreSQL maintenance tools
- Stream compact live replay updates across local and clustered dashboards.
- Render responsive snake bodies as SVG paths with aligned custom icons.
- Add cache-busted assets, replay fallback routes, and live-follow playback.
- Support PostgreSQL benchmark sampling and idempotent SQLite migration.
- Add dry-run cleanup for old low-quality PostgreSQL replay payloads.
- Reward safe perimeter lanes and bump Prism to version 1.5.0.
- Add backend, migration, dashboard, and perimeter regression coverage.
2026-08-02 00:50:46 +02:00

744 lines
12 KiB
CSS

* {
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-width: 0;
min-height: 0;
display: flex;
align-items: flex-start;
justify-content: center;
overflow: hidden;
}
.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 {
position: relative;
min-width: 0;
min-height: 0;
display: grid;
gap: 2px;
flex: none;
background: var(--grid);
border: 1px solid var(--line);
border-radius: 10px;
padding: 6px;
overflow: hidden;
}
/* Snake bodies are drawn as one rounded polyline per snake on top of the grid,
so bends and cell gaps need no per-cell patching. */
.snake-layer {
position: absolute;
inset: 0;
z-index: 1;
pointer-events: none;
}
.cell {
background: var(--cell);
width: 100%;
height: 100%;
min-width: 0;
min-height: 0;
position: relative;
border-radius: 2px;
}
.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);
}
/* The hatch overlay (::before, z-index 4) still paints above the body stroke,
but the opaque fill would hide it. */
.hazard.hazard-over-snake {
background-color: transparent;
}
.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;
}
/* Head/tail markers and icon layers use z-index >= 2 so they paint above
.snake-layer. The cells themselves stay unstacked, keeping their background
below the body stroke. */
.snake-head {
outline: none;
}
.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;
}
/* Spans the full cell along the travel axis and the body stroke width across
it, so the icon is exactly as thick as the body and its leading edge lands on
the cell border where the stroke ends. */
.icon-layer {
position: absolute;
left: 0;
right: 0;
top: var(--icon-cross-inset, 7%);
bottom: var(--icon-cross-inset, 7%);
transform: var(--icon-transform, rotate(0deg));
transform-origin: center;
pointer-events: none;
z-index: 2;
}
.icon-layer--head {
z-index: 3;
}
/* Fallback for artwork that has not been fetched yet. An SVG used as a mask
image keeps its own preserveAspectRatio and letterboxes inside this
non-square box, so the inlined variant above is preferred. */
.icon-layer--masked {
background: var(--icon-color, currentColor);
-webkit-mask-image: var(--icon-url);
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
-webkit-mask-size: 100% 100%;
mask-image: var(--icon-url);
mask-repeat: no-repeat;
mask-position: center;
mask-size: 100% 100%;
}
.icon-layer>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) {
html,
body {
height: auto;
min-height: 100%;
overflow: auto;
}
.page {
height: auto;
min-height: 100vh;
overflow: visible;
}
.topbar {
grid-template-columns: 1fr;
}
.stats {
min-width: 0;
grid-template-columns: repeat(6, minmax(80px, 1fr));
}
.main {
grid-template-columns: 1fr;
overflow: visible;
}
.panel,
.right {
overflow: visible;
}
.games {
min-height: 200px;
}
.content {
grid-template-columns: 1fr;
}
.thinking {
overflow: visible;
}
.board-wrap {
min-height: min(70vw, 520px);
}
.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;
}
}