Files
PenaltyTracker/web/style.css
T
CodingPhoenix 357888e310 Fix WebSocket status label not updating and move it into the topbar
The onopen/onclose handlers only toggled the CSS class on the status
dot, never the separate text span, so it stayed "offline" after a
reconnect. Replace the querySelector-based toggling with an
updateWsIndicator() helper that updates both the dot and its label
from stored element refs.

The indicator now lives in the topbar (via renderTopbar's extra slot)
instead of next to the competition name, with its own .ws-indicator
flex wrapper so the dot and label line up vertically.
2026-09-06 06:23:42 +02:00

551 lines
13 KiB
CSS

:root {
--accent: #2b6cb0;
--accent-hover: #2c5282;
--bg: #ffffff;
--fg: #111111;
--muted: #6b7280;
--border: #e5e7eb;
--row-hover: #f9fafb;
--danger: #b91c1c;
--radius: 0.375rem;
}
* { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--bg);
color: var(--fg);
font-size: 14px;
line-height: 1.45;
}
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-hover); text-decoration: underline; }
button, input, select, textarea {
font-family: inherit;
font-size: inherit;
color: inherit;
border-radius: var(--radius);
border: 1px solid var(--border);
background: #fff;
padding: 0.4rem 0.6rem;
outline: none;
}
button {
cursor: pointer;
background: #fff;
transition: background 0.1s ease, border-color 0.1s ease;
}
button:hover { border-color: var(--accent); }
button.primary {
background: var(--accent);
color: #fff;
border-color: var(--accent);
}
button.primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
button.danger {
background: #fff;
color: var(--danger);
border-color: var(--border);
}
button.danger:hover { border-color: var(--danger); }
button.ghost { border-color: transparent; background: transparent; }
input:focus, select:focus, textarea:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px rgba(43, 108, 176, 0.15);
}
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.75rem 1.25rem;
border-bottom: 1px solid var(--border);
background: #fff;
position: sticky;
top: 0;
z-index: 10;
}
.topbar .brand {
font-weight: 600;
font-size: 1rem;
color: var(--accent);
}
.topbar .nav {
display: flex;
gap: 0.5rem;
align-items: center;
}
.container {
padding: 1.25rem;
max-width: 1400px;
margin: 0 auto;
}
.card {
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1rem;
background: #fff;
margin-bottom: 1rem;
}
.card h2 {
margin: 0 0 0.75rem 0;
font-size: 1rem;
font-weight: 600;
}
.row { display: flex; gap: 0.5rem; flex-wrap: wrap; align-items: center; }
.col { display: flex; flex-direction: column; gap: 0.5rem; }
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 0.75rem;
}
.field { display: flex; flex-direction: column; gap: 0.25rem; }
.field label { font-size: 0.8rem; color: var(--muted); }
.login-wrap {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: 1rem;
}
.login-box {
width: 100%;
max-width: 360px;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.5rem;
background: #fff;
}
.login-box h1 {
margin: 0 0 1rem 0;
font-size: 1.125rem;
color: var(--accent);
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
th, td {
text-align: left;
padding: 0.5rem 0.6rem;
border-bottom: 1px solid var(--border);
vertical-align: top;
}
th {
font-weight: 600;
background: #fafafa;
position: sticky;
top: 0;
z-index: 2;
cursor: pointer;
user-select: none;
white-space: nowrap;
}
th .sort-ind {
color: var(--muted);
font-size: 0.7rem;
margin-left: 0.25rem;
}
tbody tr:hover { background: var(--row-hover); }
tbody tr.transferred { background: #f3f6fb; }
.table-wrap {
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: auto;
max-height: 70vh;
}
.badge {
display: inline-block;
padding: 0.125rem 0.5rem;
border-radius: var(--radius);
font-size: 0.75rem;
background: #f3f4f6;
color: #374151;
border: 1px solid var(--border);
}
.badge.accent { background: rgba(43, 108, 176, 0.1); color: var(--accent); border-color: rgba(43, 108, 176, 0.3); }
.badge.warn { background: #fff7ed; color: #9a3412; border-color: #fed7aa; }
.muted { color: var(--muted); font-size: 0.85rem; }
.modal-backdrop {
position: fixed; inset: 0;
background: rgba(0,0,0,0.4);
display: flex; align-items: center; justify-content: center;
z-index: 50;
padding: 1rem;
}
.modal {
background: #fff;
border-radius: var(--radius);
padding: 1.25rem;
max-width: 600px;
width: 100%;
max-height: 90vh;
overflow: auto;
}
.modal h3 { margin-top: 0; }
.toolbar { display: flex; gap: 0.5rem; margin-bottom: 0.75rem; flex-wrap: wrap; }
.toolbar .spacer { flex: 1; }
.rule-suggestion {
background: #f9fafb;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0.5rem 0.75rem;
font-size: 0.85rem;
}
.rule-card {
border: 1px solid var(--border);
border-left: 3px solid var(--accent);
border-radius: var(--radius);
padding: 0.625rem 0.875rem;
background: #fff;
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.rule-head {
display: flex;
align-items: baseline;
gap: 0.5rem;
flex-wrap: wrap;
}
.rule-num {
color: var(--accent);
font-weight: 700;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.85rem;
background: rgba(43, 108, 176, 0.08);
padding: 0.1rem 0.4rem;
border-radius: var(--radius);
white-space: nowrap;
}
.rule-text { font-size: 0.9rem; line-height: 1.3; }
.kv {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
font-size: 0.8rem;
}
.kv .k { color: var(--muted); min-width: 9rem; }
.small { font-size: 0.8rem; }
.tier-row {
display: inline-flex;
align-items: center;
gap: 0.3rem;
flex-wrap: wrap;
}
.tier-pill {
background: #fff;
border: 1px solid var(--border);
color: var(--fg);
padding: 0.1rem 0.45rem;
border-radius: var(--radius);
font-size: 0.75rem;
font-weight: 500;
}
.tier-pill:last-child {
border-color: var(--accent);
color: var(--accent);
background: rgba(43, 108, 176, 0.08);
}
.tier-arrow { color: var(--muted); font-size: 0.75rem; }
.rules-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 0.625rem;
}
.prior-box {
margin-top: 0.5rem;
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0.5rem 0.75rem;
background: #fafbfc;
}
.prior-title {
font-size: 0.8rem;
font-weight: 600;
color: var(--accent);
margin-bottom: 0.4rem;
}
.mini-table { font-size: 12px; }
.mini-table th, .mini-table td {
padding: 0.3rem 0.4rem;
border-bottom: 1px solid var(--border);
background: transparent;
}
.mini-table th { background: transparent; position: static; }
.search-box {
position: relative;
width: 100%;
}
.search-box .results {
position: absolute;
top: 100%; left: 0; right: 0;
border: 1px solid var(--border);
background: #fff;
border-radius: var(--radius);
max-height: 240px;
overflow: auto;
z-index: 5;
margin-top: 0.25rem;
}
.search-box .results .item {
padding: 0.5rem 0.6rem;
cursor: pointer;
border-bottom: 1px solid var(--border);
}
.search-box .results .item:last-child { border-bottom: none; }
.search-box .results .item:hover { background: #f3f4f6; }
.search-box .results .item .num { color: var(--accent); font-weight: 600; margin-right: 0.5rem; }
.switch {
position: relative;
display: inline-block;
width: 36px;
height: 20px;
}
.switch input { opacity: 0; width: 0; height: 0; }
.switch .slider {
position: absolute;
cursor: pointer;
inset: 0;
background-color: #d1d5db;
transition: 0.15s;
border-radius: var(--radius);
}
.switch .slider:before {
position: absolute;
content: "";
height: 14px;
width: 14px;
left: 3px;
top: 3px;
background-color: white;
transition: 0.15s;
border-radius: var(--radius);
}
.switch input:checked + .slider { background-color: var(--accent); }
.switch input:checked + .slider:before { transform: translateX(16px); }
.tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
border-bottom: 1px solid var(--border);
}
.tabs button {
background: transparent;
border: none;
border-bottom: 2px solid transparent;
border-radius: 0;
padding: 0.5rem 0.75rem;
color: var(--muted);
}
.tabs button.active {
color: var(--accent);
border-bottom-color: var(--accent);
}
.action-btn {
padding: 0.25rem 0.5rem;
font-size: 0.8rem;
background: transparent;
border: 1px solid transparent;
}
.action-btn:hover { border-color: var(--border); }
.connection-status {
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
background: #d1d5db;
flex: none;
}
.connection-status.online { background: #10b981; }
.connection-status.offline { background: #ef4444; }
.ws-indicator {
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-size: 0.85rem;
line-height: 1;
}
/* Optically drop the dot ~1px: an all-caps label (Online/Offline) has
unused descender space, so its glyphs sit above the flex centre line. */
.ws-indicator .connection-status { position: relative; top: 1px; }
textarea { resize: vertical; min-height: 60px; }
@media (max-width: 700px) {
.topbar { padding: 0.5rem 0.75rem; }
.container { padding: 0.75rem; }
}
.loading-wrap {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.muted.small,
.small { font-size: 0.75rem; }
.apply-row { gap: 1rem; }
h4 { margin: 0.75rem 0 0.25rem 0; font-size: 0.95rem; }
input[disabled],
button[disabled] { opacity: 0.55; cursor: not-allowed; }
/* Rules table: keep long rule texts readable, but cap column widths so a
single long row can't blow up the layout. */
.rules-table { table-layout: fixed; width: 100%; }
.rules-table th, .rules-table td { white-space: normal; vertical-align: top; }
.rules-table th.col-num,
.rules-table td.col-num { width: 8rem; }
.rules-table th.col-suggested,
.rules-table td.col-suggested { width: 14rem; }
.rules-table th.col-escalation,
.rules-table td.col-escalation { width: 16rem; }
.rules-table td .rule-num {
display: inline-block;
color: var(--accent);
font-weight: 700;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.8rem;
background: rgba(43, 108, 176, 0.08);
padding: 0.1rem 0.4rem;
border-radius: var(--radius);
white-space: nowrap;
}
.wrap-cell {
word-break: break-word;
overflow-wrap: anywhere;
white-space: pre-wrap;
line-height: 1.35;
}
@media (max-width: 700px) {
.rules-table th.col-num,
.rules-table td.col-num { width: 5rem; }
.rules-table th.col-suggested,
.rules-table td.col-suggested { width: 9rem; }
.rules-table th.col-escalation,
.rules-table td.col-escalation { width: 10rem; }
}
/* System Admin area: sidebar + content, own layout separate from the
normal topbar-only pages. */
.admin-shell {
display: flex;
align-items: flex-start;
}
.admin-sidebar {
width: 220px;
flex-shrink: 0;
border-right: 1px solid var(--border);
padding: 1rem 0.5rem;
min-height: calc(100vh - 57px);
}
.admin-sidebar .category { margin-bottom: 0.25rem; }
.admin-sidebar .category-header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
background: transparent;
border: none;
border-radius: var(--radius);
padding: 0.5rem 0.6rem;
font-weight: 600;
cursor: pointer;
}
.admin-sidebar .category-header:hover { background: var(--row-hover); }
.admin-sidebar .category-header .chevron {
color: var(--muted);
font-size: 0.7rem;
transition: transform 0.15s ease;
}
.admin-sidebar .category.collapsed .chevron { transform: rotate(-90deg); }
.admin-sidebar .category.collapsed .sub-list { display: none; }
.admin-sidebar .sub-list { list-style: none; margin: 0.15rem 0 0.5rem 0; padding: 0; }
.admin-sidebar .sub-item {
display: block;
padding: 0.4rem 0.6rem 0.4rem 1.25rem;
border-radius: var(--radius);
font-size: 0.9rem;
cursor: pointer;
}
.admin-sidebar .sub-item:hover { background: var(--row-hover); }
.admin-sidebar .sub-item.active {
background: rgba(43, 108, 176, 0.1);
color: var(--accent);
font-weight: 600;
}
.admin-content { flex: 1; min-width: 0; padding: 1.25rem; }
@media (max-width: 700px) {
.admin-sidebar { width: 160px; padding: 0.75rem 0.4rem; }
.admin-content { padding: 0.75rem; }
}
/* Small fixed-position dropdown menu, e.g. the "..." menu on a card. */
.menu {
position: fixed;
background: #fff;
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
z-index: 100;
min-width: 150px;
padding: 0.25rem;
}
.menu button {
display: block;
width: 100%;
text-align: left;
border: none;
border-radius: var(--radius);
padding: 0.5rem 0.6rem;
background: transparent;
}
.menu button:hover { background: var(--row-hover); }
.menu button.danger { color: var(--danger); }
.card.clickable {
cursor: pointer;
transition: box-shadow 0.1s ease, border-color 0.1s ease;
}
.card.clickable:hover {
border-color: var(--accent);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}