Opengrid / static /style.css
K446's picture
Polish for hackathon submission: training evidence, two pipelines, UI, docs
e81353d
/* ============================================================================
OpenGrid KPTCL-SLDC Control Room — Design System
Inspired by ERCOT control room aesthetics, adapted for Karnataka grid
============================================================================ */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap');
/* ---------- CSS Custom Properties ---------- */
:root {
/* Background layers */
--bg-primary: #121212;
--bg-secondary: #121212;
--bg-tertiary: #121212;
--bg-glass: rgba(35, 35, 35, 0.85);
--bg-card: rgba(24, 24, 24, 0.7);
/* Operational states */
--status-normal: #00e5a0;
--status-warning: #ffd700;
--status-critical:#ff3d3d;
--status-offline: #4a5568;
--status-overload:#ff6b35;
/* Voltage colors */
--voltage-400kv: #e94560;
--voltage-220kv: #f5a623;
--voltage-110kv: #7ed321;
--voltage-66kv: #cbd5e1;
/* Agent identity colors */
--agent-0: #e2e8f0;
--agent-1: #ff69b4;
--agent-2: #ff6347;
/* Text */
--text-primary: #e8eaf6;
--text-secondary: #90a4ae;
--text-accent: #00e5a0;
--text-danger: #ff5252;
--text-muted: #546e7a;
/* Chart */
--chart-demand: #e2e8f0;
--chart-supply: #00e5a0;
--chart-reward: #ffd700;
/* Spacing */
--gap-sm: 8px;
--gap-md: 12px;
--gap-lg: 16px;
--gap-xl: 20px;
/* Radius */
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
}
/* ---------- Reset & Base ---------- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
height: 100%;
background: var(--bg-primary);
color: var(--text-primary);
font-family: 'Bespoke Stencil', 'Inter', 'Segoe UI', sans-serif;
font-size: 13px;
line-height: 1.5;
overflow: hidden;
-webkit-font-smoothing: antialiased;
}
/* Subtle scanline overlay */
body::before {
content: '';
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
pointer-events: none;
z-index: 9999;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0,0,0,0.03) 2px,
rgba(0,0,0,0.03) 4px
);
}
/* ---------- Layout ---------- */
.control-room {
display: grid;
grid-template-rows: 48px 44px 1fr 160px;
grid-template-columns: 240px 1fr 280px;
grid-template-areas:
"header header header"
"toolbar toolbar toolbar"
"left center right"
"bottom bottom bottom";
height: 100vh;
gap: 1px;
background: rgba(255,255,255,0.04);
}
/* ---------- Toolbar ---------- */
.toolbar {
grid-area: toolbar;
background: #121212;
display: flex;
align-items: center;
padding: 0 var(--gap-md);
gap: var(--gap-md);
border-bottom: 1px solid rgba(0,229,160,0.1);
z-index: 10;
overflow-x: auto;
}
.toolbar-section {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.toolbar-label {
font-size: 9px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1.2px;
color: var(--text-muted);
white-space: nowrap;
}
.toolbar-divider {
width: 1px;
height: 24px;
background: rgba(255,255,255,0.08);
flex-shrink: 0;
}
.toolbar-score {
font-family: 'JetBrains Mono', monospace;
font-size: 18px;
font-weight: 700;
color: var(--chart-reward);
}
.toolbar-value {
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
/* Task buttons (toolbar version) */
.task-selector {
display: flex;
gap: 4px;
}
.task-btn {
display: flex;
flex-direction: column;
align-items: center;
padding: 4px 10px;
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.08);
border-radius: var(--radius-sm);
color: var(--text-secondary);
font-size: 11px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
text-transform: uppercase;
letter-spacing: 0.5px;
line-height: 1.2;
}
.task-btn .task-name { font-size: 11px; }
.task-btn .task-info {
font-size: 8px;
font-weight: 400;
color: var(--text-muted);
letter-spacing: 0;
text-transform: none;
white-space: nowrap;
}
.task-btn:hover { border-color: rgba(0,229,160,0.3); color: var(--text-primary); }
.task-btn.active {
background: rgba(0,229,160,0.12);
border-color: var(--status-normal);
color: var(--status-normal);
}
.task-btn.active .task-info { color: rgba(0,229,160,0.6); }
/* Karnataka scenario gold accent */
.task-btn.ka:hover { border-color: rgba(255,235,59,0.4); color: #ffeb3b; }
.task-btn.ka.active {
background: rgba(255,235,59,0.1);
border-color: #ffeb3b;
color: #ffeb3b;
}
.task-btn.ka.active .task-info { color: rgba(255,235,59,0.5); }
/* Task group container */
.task-group {
display: flex;
gap: 4px;
}
/* Controls in toolbar */
.controls-row {
display: flex;
gap: 4px;
}
.ctrl-btn {
padding: 5px 12px;
background: rgba(255,255,255,0.04);
border: 1px solid rgba(255,255,255,0.1);
border-radius: var(--radius-sm);
color: var(--text-primary);
font-family: 'Inter', sans-serif;
font-size: 11px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
text-align: center;
white-space: nowrap;
}
.ctrl-btn:hover {
background: rgba(0,229,160,0.1);
border-color: rgba(0,229,160,0.3);
}
.ctrl-btn.accent {
background: rgba(0,229,160,0.12);
border-color: rgba(0,229,160,0.3);
color: var(--status-normal);
}
.ctrl-btn.active {
background: rgba(0,229,160,0.2);
border-color: var(--status-normal);
color: var(--status-normal);
box-shadow: 0 0 8px rgba(0,229,160,0.15);
}
.ctrl-btn.danger { border-color: rgba(255,61,61,0.3); }
.ctrl-btn.danger:hover {
background: rgba(255,61,61,0.1);
border-color: rgba(255,61,61,0.5);
color: var(--status-critical);
}
/* ---------- Map Legend ---------- */
.map-legend {
position: absolute;
bottom: 12px;
left: 12px;
background: rgba(18, 18, 18, 0.92);
border: 1px solid rgba(255,255,255,0.1);
border-radius: var(--radius-md);
padding: 8px 12px;
z-index: 1000;
backdrop-filter: blur(8px);
font-size: 10px;
}
.legend-title {
font-size: 9px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-muted);
margin-bottom: 4px;
}
.legend-item, .legend-line {
display: flex;
align-items: center;
gap: 6px;
padding: 1px 0;
color: var(--text-secondary);
}
.legend-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.legend-line-sample {
width: 20px;
height: 3px;
border-radius: 2px;
flex-shrink: 0;
}
.legend-line-sample.normal { background: #e91e63; }
.legend-line-sample.warn { background: #ff9100; }
.legend-line-sample.crit { background: #ff1744; box-shadow: 0 0 6px rgba(255,23,68,0.5); }
/* ---------- Header ---------- */
.header {
grid-area: header;
background: #121212;
display: flex;
align-items: center;
padding: 0 var(--gap-lg);
gap: var(--gap-lg);
border-bottom: 1px solid rgba(0,229,160,0.15);
z-index: 10;
}
.header-brand {
display: flex;
align-items: center;
gap: var(--gap-sm);
flex-shrink: 0;
}
.header-brand .logo {
width: 28px;
height: 28px;
background: linear-gradient(135deg, #00e5a0, #000000);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 14px;
color: #000000;
}
.header-brand h1 {
font-size: 14px;
font-weight: 600;
letter-spacing: 0.5px;
}
.header-brand .sub {
font-size: 10px;
color: var(--text-secondary);
letter-spacing: 1px;
text-transform: uppercase;
}
.header-stats {
display: flex;
gap: var(--gap-lg);
margin-left: auto;
align-items: center;
}
.header-stat {
display: flex;
flex-direction: column;
align-items: center;
padding: 4px 12px;
border-radius: var(--radius-sm);
background: rgba(255,255,255,0.04);
}
.header-stat .label {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 1px;
color: var(--text-secondary);
}
.header-stat .value {
font-family: 'JetBrains Mono', monospace;
font-size: 14px;
font-weight: 600;
}
.header-stat .value.normal { color: var(--status-normal); }
.header-stat .value.warning { color: var(--status-warning); }
.header-stat .value.critical { color: var(--status-critical); }
.sim-badge {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 10px;
border-radius: 20px;
background: rgba(0,229,160,0.1);
border: 1px solid rgba(0,229,160,0.25);
font-size: 10px;
font-weight: 600;
color: var(--status-normal);
text-transform: uppercase;
letter-spacing: 1px;
}
.sim-badge .dot {
width: 6px; height: 6px;
background: var(--status-normal);
border-radius: 50%;
animation: pulse-dot 2s infinite;
}
@keyframes pulse-dot {
0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(0,229,160,0.4); }
50% { opacity: 0.7; box-shadow: 0 0 0 4px rgba(0,229,160,0); }
}
/* ---------- Left Panel ---------- */
.left-panel {
grid-area: left;
background: var(--bg-secondary);
padding: var(--gap-md);
overflow-y: auto;
display: flex;
flex-direction: column;
gap: var(--gap-md);
border-right: 1px solid rgba(255,255,255,0.05);
}
/* ---------- Cards (shared) ---------- */
.card {
background: var(--bg-card);
border: 1px solid rgba(255,255,255,0.06);
border-radius: var(--radius-md);
padding: var(--gap-md);
backdrop-filter: blur(8px);
}
.card-title {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1.5px;
color: var(--text-secondary);
margin-bottom: var(--gap-sm);
padding-bottom: 6px;
border-bottom: 1px solid rgba(255,255,255,0.06);
}
/* ---------- Alarm Log ---------- */
.alarm-log {
flex: 1;
max-height: 90px;
overflow-y: auto;
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
line-height: 1.4;
display: flex;
flex-direction: column;
gap: 4px;
}
.alarm-entry {
padding: 4px 6px;
background: rgba(255,255,255,0.03);
border-left: 2px solid transparent;
border-radius: 2px;
}
.alarm-time { color: var(--text-muted); margin-right: 6px; }
.alarm-entry.warn { border-left-color: var(--status-warning); background: rgba(255,152,0,0.05); color: #ffb74d; }
.alarm-entry.crit { border-left-color: var(--status-critical); background: rgba(244,67,54,0.05); color: #ef5350; }
.alarm-entry.info { border-left-color: var(--status-normal); }
/* ---------- Frequency Display ---------- */
.freq-card .card-title {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.freq-nominal-tag {
font-family: 'JetBrains Mono', monospace;
font-size: 9px;
font-weight: 500;
color: var(--text-muted);
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.06);
padding: 2px 7px;
border-radius: 999px;
letter-spacing: 0.4px;
text-transform: none;
}
.freq-display {
text-align: center;
padding: 4px 0 0;
position: relative;
}
.freq-arc-container {
position: relative;
width: 100%;
max-width: 240px;
margin: 0 auto;
aspect-ratio: 240 / 140;
}
.freq-arc-container .freq-svg {
width: 100%;
height: 100%;
overflow: visible;
display: block;
}
/* Big numeric readout sitting under the arc */
.freq-readout {
display: flex;
align-items: baseline;
justify-content: center;
gap: 4px;
margin-top: -28px;
margin-bottom: 6px;
position: relative;
z-index: 2;
}
.freq-value-big {
font-family: 'Bespoke Stencil', sans-serif;
font-size: 34px;
font-weight: 400;
letter-spacing: -1.0px;
line-height: 1;
transition: color 0.25s;
font-variant-numeric: tabular-nums;
}
.freq-unit {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
font-weight: 600;
color: var(--text-muted);
letter-spacing: 1.2px;
text-transform: uppercase;
transform: translateY(-2px);
}
.freq-value-big.normal { color: #4a7c59; }
.freq-value-big.warning { color: #c4a45e; }
.freq-value-big.critical {
color: #7c203a;
animation: freq-blink 0.9s ease-in-out infinite;
}
@keyframes freq-blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
/* Delta + condition row */
.freq-delta-row {
display: flex;
align-items: stretch;
justify-content: center;
gap: 6px;
margin-top: 8px;
flex-wrap: wrap;
}
.freq-delta-chip {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 0;
border-radius: 0;
font-family: 'Bespoke Stencil', sans-serif;
font-size: 11px;
font-weight: 400;
letter-spacing: 0.5px;
border: none;
transition: all 0.25s;
font-variant-numeric: tabular-nums;
}
.freq-delta-arrow {
font-size: 8px;
line-height: 1;
}
.freq-delta-chip.normal { color: #4a7c59; }
.freq-delta-chip.warning { color: #c4a45e; }
.freq-delta-chip.critical { color: #7c203a; }
/* Grid condition badge */
.grid-condition {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 0;
border-radius: 0;
font-family: 'Bespoke Stencil', sans-serif;
font-size: 10px;
font-weight: 400;
text-transform: uppercase;
letter-spacing: 1.5px;
border: none;
transition: all 0.25s;
position: relative;
margin-left: 10px;
}
.grid-condition .cond-dot {
width: 4px;
height: 4px;
border-radius: 50%;
background: currentColor;
flex-shrink: 0;
}
.grid-condition.normal { color: #4a7c59; }
.grid-condition.conservative { color: #c4a45e; }
.grid-condition.alert { color: #c4a45e; }
.grid-condition.emergency {
color: #7c203a;
animation: cond-pulse 1.2s ease-in-out infinite;
}
animation: cond-pulse 1.2s ease-in-out infinite;
}
.grid-condition.emergency .cond-dot {
animation: dot-pulse 0.8s ease-in-out infinite;
}
@keyframes cond-pulse {
0%, 100% {
box-shadow: 0 0 0 0 rgba(255, 61, 61, 0.35),
inset 0 0 0 0 rgba(255, 61, 61, 0);
}
50% {
box-shadow: 0 0 0 5px rgba(255, 61, 61, 0),
inset 0 0 8px 0 rgba(255, 61, 61, 0.15);
}
}
@keyframes dot-pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.4); opacity: 0.7; }
}
/* ---------- System Summary ---------- */
.stat-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 0;
font-size: 12px;
}
.stat-row .label { color: var(--text-secondary); }
.stat-row .value {
font-family: 'JetBrains Mono', monospace;
font-weight: 500;
}
.stat-row.highlight .value {
color: var(--status-normal);
font-weight: 600;
}
/* Progress bars */
.progress-bar {
height: 4px;
background: rgba(255,255,255,0.06);
border-radius: 2px;
overflow: hidden;
margin-top: 4px;
}
.progress-bar-fill {
height: 100%;
border-radius: 2px;
transition: width 0.5s;
}
/* ---------- Center Panel (Grid Map) ---------- */
.center-panel {
grid-area: center;
background: var(--bg-tertiary);
position: relative;
overflow: hidden;
}
.grid-map {
width: 100%;
height: 100%;
}
.grid-map svg {
width: 100%;
height: 100%;
}
/* SVG map styles */
.zone-polygon {
opacity: 0.06;
transition: opacity 0.4s;
cursor: pointer;
filter: blur(0.5px);
}
.zone-polygon:hover { opacity: 0.18; }
.substation-node { cursor: pointer; }
.substation-node:hover .node-outer { stroke-width: 2.5; filter: url(#glow); }
.substation-node:hover .node-label { opacity: 1; }
.node-label {
font-family: 'Inter', sans-serif;
font-size: 8px;
fill: var(--text-secondary);
text-anchor: middle;
pointer-events: none;
opacity: 0.7;
transition: opacity 0.2s;
}
.node-mw {
font-family: 'JetBrains Mono', monospace;
font-size: 9px;
fill: var(--text-primary);
text-anchor: middle;
pointer-events: none;
font-weight: 500;
}
.line-flow {
fill: none;
stroke-linecap: round;
}
/* Animated flow on lines */
@keyframes dash-flow {
to { stroke-dashoffset: -24; }
}
.line-animated {
animation: dash-flow 1.2s linear infinite;
}
.line-animated.reverse {
animation-direction: reverse;
}
.flow-label {
font-family: 'JetBrains Mono', monospace;
font-size: 8px;
fill: rgba(232,234,246,0.6);
text-anchor: middle;
pointer-events: none;
}
.zone-badge { font-family: 'Inter', sans-serif; pointer-events: none; }
.zone-badge-bg {
rx: 8;
fill: rgba(18, 18, 18, 0.88);
stroke-width: 1;
backdrop-filter: blur(6px);
}
.zone-badge-name { font-size: 10px; font-weight: 600; text-anchor: middle; }
.zone-badge-status { font-size: 8px; text-anchor: middle; fill: var(--text-secondary); }
.zone-badge-reward { font-size: 9px; text-anchor: middle; font-weight: 600; font-family: 'JetBrains Mono', monospace; }
/* Bus tooltip */
.bus-tooltip {
position: absolute;
background: rgba(18, 18, 18, 0.95);
border: 1px solid rgba(0,229,160,0.2);
border-radius: var(--radius-sm);
padding: 8px 10px;
font-size: 11px;
pointer-events: none;
z-index: 20;
min-width: 140px;
backdrop-filter: blur(12px);
box-shadow: 0 4px 20px rgba(0,0,0,0.4);
display: none;
}
.bus-tooltip.visible { display: block; }
.bus-tooltip .tt-title {
font-weight: 600;
margin-bottom: 4px;
padding-bottom: 4px;
border-bottom: 1px solid rgba(255,255,255,0.08);
}
.bus-tooltip .tt-row {
display: flex;
justify-content: space-between;
padding: 1px 0;
}
.bus-tooltip .tt-row .tt-val {
font-family: 'JetBrains Mono', monospace;
font-weight: 500;
}
/* Map overlay controls */
.map-controls {
position: absolute;
top: var(--gap-md);
right: var(--gap-md);
display: flex;
flex-direction: column;
gap: 4px;
z-index: 5;
}
.map-btn {
width: 32px; height: 32px;
background: var(--bg-glass);
border: 1px solid rgba(255,255,255,0.1);
border-radius: var(--radius-sm);
color: var(--text-secondary);
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
backdrop-filter: blur(8px);
transition: all 0.2s;
}
.map-btn:hover {
background: rgba(0,229,160,0.15);
color: var(--status-normal);
border-color: rgba(0,229,160,0.3);
}
/* ---------- Right Panel (Agent Monitor) ---------- */
.right-panel {
grid-area: right;
background: var(--bg-secondary);
padding: var(--gap-md);
overflow-y: auto;
display: flex;
flex-direction: column;
gap: var(--gap-md);
border-left: 1px solid rgba(255,255,255,0.05);
}
/* Agent cards */
.agent-card {
border-radius: var(--radius-md);
padding: var(--gap-md);
background: var(--bg-card);
border: 1px solid rgba(255,255,255,0.06);
backdrop-filter: blur(8px);
transition: border-color 0.3s, box-shadow 0.3s;
}
.agent-card.active {
border-color: rgba(0,229,160,0.2);
}
.agent-card.warning {
border-color: rgba(255,215,0,0.3);
box-shadow: 0 0 12px rgba(255,215,0,0.05);
}
.agent-card.critical {
border-color: rgba(255,61,61,0.3);
box-shadow: 0 0 12px rgba(255,61,61,0.08);
animation: card-pulse 1.5s infinite;
}
@keyframes card-pulse {
0%, 100% { box-shadow: 0 0 12px rgba(255,61,61,0.08); }
50% { box-shadow: 0 0 20px rgba(255,61,61,0.15); }
}
.agent-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--gap-sm);
}
.agent-name {
font-size: 12px;
font-weight: 600;
display: flex;
align-items: center;
gap: 6px;
}
.agent-dot {
width: 8px; height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.agent-status-badge {
font-size: 9px;
font-weight: 600;
padding: 2px 6px;
border-radius: 10px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.agent-status-badge.active {
background: rgba(0,229,160,0.15);
color: var(--status-normal);
}
.agent-status-badge.corrected {
background: rgba(255,215,0,0.15);
color: var(--status-warning);
}
.agent-metrics {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
margin-top: var(--gap-sm);
}
.agent-metric {
padding: 6px 8px;
background: rgba(255,255,255,0.02);
border-radius: var(--radius-sm);
}
.agent-metric .label {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-muted);
}
.agent-metric .value {
font-family: 'JetBrains Mono', monospace;
font-size: 14px;
font-weight: 600;
margin-top: 2px;
}
/* Safety shield */
.safety-shield {
margin-top: var(--gap-sm);
padding: 6px 8px;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
gap: 6px;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.safety-shield.safe {
background: rgba(0,229,160,0.08);
border: 1px solid rgba(0,229,160,0.15);
color: var(--status-normal);
}
.safety-shield.corrected {
background: rgba(255,215,0,0.08);
border: 1px solid rgba(255,215,0,0.2);
color: var(--status-warning);
}
.safety-shield.violated {
background: rgba(255,61,61,0.08);
border: 1px solid rgba(255,61,61,0.2);
color: var(--status-critical);
}
/* Sparkline */
.sparkline-container {
margin-top: var(--gap-sm);
height: 30px;
background: rgba(255,255,255,0.02);
border-radius: var(--radius-sm);
padding: 4px;
}
.sparkline-container svg {
width: 100%;
height: 100%;
}
/* ---------- Bottom Panel ---------- */
.bottom-panel {
grid-area: bottom;
background: var(--bg-secondary);
display: grid;
grid-template-columns: 2fr 1fr 1fr;
gap: 1px;
border-top: 1px solid rgba(255,255,255,0.05);
}
.bottom-card {
background: var(--bg-card);
padding: var(--gap-md);
display: flex;
flex-direction: column;
}
.chart-area {
flex: 1;
position: relative;
min-height: 0;
}
.chart-area canvas, .chart-area svg {
width: 100%;
height: 100%;
}
/* Reward chart */
.reward-history {
flex: 1;
}
/* Leaderboard */
.leaderboard {
list-style: none;
}
.leaderboard li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 0;
font-size: 11px;
border-bottom: 1px solid rgba(255,255,255,0.03);
}
.leaderboard li:last-child { border-bottom: none; }
.leaderboard .agent-label {
display: flex;
align-items: center;
gap: 6px;
}
.leaderboard .score {
font-family: 'JetBrains Mono', monospace;
font-weight: 600;
font-size: 12px;
}
/* Coordination score */
.coord-score {
text-align: center;
padding: var(--gap-sm);
}
.coord-score .big-value {
font-family: 'JetBrains Mono', monospace;
font-size: 28px;
font-weight: 700;
}
/* Alert banner */
.alert-banner {
position: fixed;
top: 52px;
left: 0; right: 0;
z-index: 100;
padding: 8px var(--gap-lg);
display: flex;
align-items: center;
gap: var(--gap-sm);
font-size: 12px;
font-weight: 500;
transform: translateY(-20px);
opacity: 0;
pointer-events: none;
transition: all 0.3s;
}
.alert-banner.visible {
transform: translateY(0);
opacity: 1;
pointer-events: auto;
}
.alert-banner.critical {
background: rgba(255,61,61,0.15);
border-bottom: 1px solid rgba(255,61,61,0.3);
color: var(--status-critical);
}
.alert-banner.warning {
background: rgba(255,215,0,0.1);
border-bottom: 1px solid rgba(255,215,0,0.2);
color: var(--status-warning);
}
.alert-banner .dismiss {
margin-left: auto;
background: none;
border: 1px solid currentColor;
border-radius: var(--radius-sm);
color: inherit;
padding: 2px 8px;
font-size: 10px;
cursor: pointer;
opacity: 0.7;
}
.alert-banner .dismiss:hover { opacity: 1; }
/* Scrollbar */
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.2); }
/* Loading state */
.loading-overlay {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: var(--bg-primary);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
transition: opacity 0.5s;
}
.loading-overlay.hidden {
opacity: 0;
pointer-events: none;
}
.loading-spinner {
width: 40px; height: 40px;
border: 3px solid rgba(0,229,160,0.15);
border-top-color: var(--status-normal);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.loading-text {
margin-top: var(--gap-md);
color: var(--text-secondary);
font-size: 12px;
letter-spacing: 2px;
text-transform: uppercase;
}
/* ── Leaflet Overrides ── */
.grid-map .leaflet-container {
background: var(--bg-primary) !important;
}
.leaflet-tooltip-dark {
background: rgba(10, 14, 26, 0.92) !important;
border: 1px solid rgba(0, 229, 160, 0.3) !important;
color: #e0e0e0 !important;
font-family: 'JetBrains Mono', monospace !important;
font-size: 11px !important;
border-radius: 6px !important;
padding: 6px 10px !important;
box-shadow: 0 4px 20px rgba(0,0,0,0.6) !important;
}
.leaflet-tooltip-dark::before {
border-top-color: rgba(10, 14, 26, 0.92) !important;
}
.bus-label-icon, .bus-mw-icon, .zone-badge-leaflet, .line-flow-label {
background: none !important;
border: none !important;
text-align: center;
overflow: visible !important;
}
/* MW injection pill above each significant bus node */
.bus-mw-pill {
display: inline-flex;
align-items: baseline;
gap: 1px;
padding: 2px 6px;
border-radius: 999px;
font-family: 'JetBrains Mono', monospace;
font-size: 9px;
font-weight: 700;
line-height: 1;
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
border: 1px solid transparent;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.bus-mw-pill small {
font-size: 7px;
font-weight: 500;
opacity: 0.7;
margin-left: 1px;
}
.bus-mw-pill.pos {
background: rgba(0, 229, 160, 0.18);
color: #00e5a0;
border-color: rgba(0, 229, 160, 0.35);
}
.bus-mw-pill.neg {
background: rgba(233, 69, 96, 0.18);
color: #ff8a9e;
border-color: rgba(233, 69, 96, 0.35);
}
.bus-mw-pill.zero {
background: rgba(255, 255, 255, 0.08);
color: #cbd5e1;
border-color: rgba(255, 255, 255, 0.15);
}
/* Transmission line flow pill */
.line-flow-pill {
display: inline-flex;
align-items: baseline;
gap: 1px;
padding: 2px 5px;
border-radius: 4px;
font-family: 'JetBrains Mono', monospace;
font-size: 9px;
font-weight: 700;
line-height: 1;
color: var(--flow-color, #fff);
background: rgba(10, 10, 10, 0.78);
border: 1px solid var(--flow-color, rgba(255,255,255,0.2));
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.line-flow-pill small {
font-size: 7px;
font-weight: 500;
opacity: 0.7;
margin-left: 1px;
}
/* Region badge floating above each zone cluster */
.zone-pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 9px 4px 4px;
background: rgba(15, 15, 18, 0.92);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 999px;
font-family: 'Inter', sans-serif;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.04);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
white-space: nowrap;
pointer-events: none;
transition: transform 0.2s;
}
.zone-pill-bar {
width: 4px;
height: 14px;
border-radius: 2px;
background: var(--zc, #00e5a0);
box-shadow: 0 0 6px var(--zc, #00e5a0);
flex-shrink: 0;
}
.zone-pill-name {
color: #e8eaf6;
font-size: 10px;
font-weight: 600;
letter-spacing: 0.2px;
}
.zone-pill-pts {
font-family: 'JetBrains Mono', monospace;
font-size: 9px;
font-weight: 700;
padding: 1px 6px;
border-radius: 999px;
font-variant-numeric: tabular-nums;
}
.zone-pill-pts.pos {
background: rgba(0, 229, 160, 0.18);
color: #00e5a0;
}
.zone-pill-pts.neg {
background: rgba(255, 61, 61, 0.18);
color: #ff8a8a;
}
.zone-pill-pts.neutral {
background: rgba(255, 255, 255, 0.08);
color: #b0bec5;
}
/* Dark zoom controls */
.leaflet-control-zoom a {
background: rgba(15, 22, 40, 0.9) !important;
color: var(--status-normal) !important;
border-color: rgba(0, 229, 160, 0.2) !important;
font-family: 'JetBrains Mono', monospace !important;
}
.leaflet-control-zoom a:hover {
background: rgba(0, 229, 160, 0.15) !important;
}
.leaflet-control-attribution {
background: var(--bg-glass) !important;
color: #555 !important;
font-size: 9px !important;
}
.leaflet-control-attribution a {
color: #666 !important;
}
.line-flow-label {
background: none !important;
border: none !important;
text-align: center;
}
/* Dark background for procedural grids (no map tiles) */
.leaflet-container {
background: #121212 !important;
}
/* ---------- Bottom Panel ---------- */
.bottom-panel {
grid-area: bottom;
background: var(--bg-secondary);
display: flex;
gap: var(--gap-md);
padding: var(--gap-md);
border-top: 1px solid rgba(255,255,255,0.05);
z-index: 10;
}
.bottom-card {
flex: 1;
background: linear-gradient(180deg, rgba(28,28,28,0.7) 0%, rgba(20,20,20,0.7) 100%);
border: 1px solid rgba(255,255,255,0.06);
border-radius: var(--radius-md);
padding: var(--gap-sm) var(--gap-md) 4px;
display: flex;
flex-direction: column;
transition: border-color 0.2s, box-shadow 0.2s;
min-width: 0;
}
.bottom-card:hover {
border-color: rgba(255,255,255,0.1);
box-shadow: 0 4px 16px rgba(0,0,0,0.25);
}
.bottom-card .card-title {
margin-bottom: 4px;
}
.chart-area {
flex: 1;
min-height: 0;
position: relative;
overflow: hidden;
}
.chart-area svg,
.chart-area .chart-svg {
width: 100%;
height: 100%;
display: block;
overflow: visible;
}
.chart-area svg text {
font-feature-settings: "tnum" 1;
}