/* HACKTRAP dashboard styles.
   Extracted from index.html's inline <style> so a real CSP (script-src/style-src 'self')
   can be enforced — with 'unsafe-inline' an injected onerror handler still runs, which
   matters because this page renders attacker-supplied strings.
   JetBrains Mono is no longer fetched from Google Fonts; the fallback stack is used. */

* { margin: 0; padding: 0; box-sizing: border-box; }

:root {
    --bg: oklch(4% 0.01 220);
    --panel: oklch(8% 0.01 220);
    --panel-border: oklch(22% 0.03 220 / 0.5);
    --cyan: oklch(72% 0.15 180);
    --cyan-dim: oklch(50% 0.12 180);
    --cyan-glow: oklch(72% 0.15 180 / 0.3);
    --red: oklch(65% 0.25 25);
    --red-dim: oklch(45% 0.18 25);
    --red-glow: oklch(65% 0.25 25 / 0.3);
    --amber: oklch(75% 0.18 85);
    --amber-dim: oklch(55% 0.14 85);
    --amber-glow: oklch(75% 0.18 85 / 0.3);
    --green: oklch(70% 0.18 145);
    --green-dim: oklch(50% 0.14 145);
    --blue: oklch(65% 0.18 250);
    --text: oklch(90% 0 0);
    --text-dim: oklch(60% 0 0);
    --text-muted: oklch(40% 0 0);
    --mono: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;
}

html { overscroll-behavior: none; }

body {
    font-family: var(--mono);
    background: var(--bg);
    color: var(--text);
    min-height: 100vh;
    overflow-x: hidden;
    position: relative;
}

/* CRT scan lines */
body::before {
    content: '';
    position: fixed;
    inset: 0;
    background: repeating-linear-gradient(
        0deg,
        oklch(0% 0 0 / 0.03) 0px,
        oklch(0% 0 0 / 0.03) 1px,
        transparent 1px,
        transparent 3px
    );
    pointer-events: none;
    z-index: 9999;
}

/* Noise texture */
body::after {
    content: '';
    position: fixed;
    inset: 0;
    background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
    opacity: 0.015;
    pointer-events: none;
    z-index: 9998;
}

/* Exploiter flash overlay */
.dashboard {
    max-width: 1440px;
    margin: 0 auto;
    padding: 16px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* Header */
.header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 16px 20px;
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: 8px;
}

.header-left h1 {
    font-size: 1.6rem;
    font-weight: 700;
    letter-spacing: 4px;
    color: var(--cyan);
    text-shadow: 0 0 20px var(--cyan-glow), 0 0 40px oklch(72% 0.15 180 / 0.15);
    display: inline;
}

.header-left h1::after {
    content: '_';
    animation: blink 1s step-end infinite;
    color: var(--cyan);
}

@keyframes blink {
    50% { opacity: 0; }
}

.header-subtitle {
    font-size: 0.7rem;
    font-weight: 300;
    letter-spacing: 3px;
    text-transform: uppercase;
    color: var(--text-dim);
    margin-top: 4px;
}

.header-right {
    display: flex;
    align-items: center;
    gap: 16px;
    font-size: 0.75rem;
    color: var(--text-dim);
}

.status-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--green);
    box-shadow: 0 0 8px var(--green);
    animation: pulse-dot 2s ease-in-out infinite;
}
.status-dot.disconnected {
    background: var(--red);
    box-shadow: 0 0 8px var(--red);
    animation: none;
}

@keyframes pulse-dot {
    0%, 100% { opacity: 1; transform: scale(1); }
    50% { opacity: 0.5; transform: scale(0.8); }
}

.status-label { font-weight: 600; }
.status-label.connected { color: var(--green); }
.status-label.disconnected { color: var(--red); }

#utc-clock { font-variant-numeric: tabular-nums; }

/* Glitch effect */
@keyframes glitch {
    0%, 95%, 100% { clip-path: inset(0 0 0 0); transform: translate(0); }
    96% { clip-path: inset(20% 0 40% 0); transform: translate(-3px, 1px); }
    97% { clip-path: inset(60% 0 10% 0); transform: translate(3px, -1px); }
    98% { clip-path: inset(30% 0 50% 0); transform: translate(-2px, 2px); }
    99% { clip-path: inset(10% 0 70% 0); transform: translate(2px, 0); }
}

.header-left h1 {
    animation: glitch 20s linear infinite;
}

/* Stats Bar */
.stats-bar {
    display: flex;
    gap: 12px;
}

.stat-card {
    flex: 1;
    padding: 14px 16px;
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: 8px;
    text-align: center;
    position: relative;
    overflow: hidden;
}

.stat-card::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 2px;
}

.stat-card.tracked::before { background: var(--cyan); }
.stat-card.banned::before { background: var(--red); }
.stat-card.engaged::before { background: var(--amber); }
.stat-card.exploiters::before { background: var(--red); }
.stat-card.total-hits::before { background: var(--blue); }

.stat-value {
    font-size: 1.8rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    transition: transform 0.2s, text-shadow 0.2s;
}
.stat-card.tracked .stat-value { color: var(--cyan); }
.stat-card.banned .stat-value { color: var(--red); }
.stat-card.engaged .stat-value { color: var(--amber); }
.stat-card.exploiters .stat-value { color: var(--red); }
.stat-card.total-hits .stat-value { color: var(--blue); }

.stat-value.pulse {
    transform: scale(1.08);
    text-shadow: 0 0 20px currentColor;
}

.stat-label {
    font-size: 0.65rem;
    font-weight: 500;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: var(--text-dim);
    margin-top: 4px;
}

/* Main content area */
.main-row {
    display: grid;
    grid-template-columns: 3fr 2fr;
    gap: 12px;
    min-height: 420px;
}

.panel {
    background: var(--panel);
    border: 1px solid var(--panel-border);
    border-radius: 8px;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.panel-header {
    padding: 10px 16px;
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 2px;
    text-transform: uppercase;
    color: var(--text-dim);
    border-bottom: 1px solid var(--panel-border);
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.panel-header .count {
    color: var(--cyan-dim);
    font-variant-numeric: tabular-nums;
}

.panel-body {
    flex: 1;
    overflow: hidden;
    position: relative;
}

/* Live Feed */
.feed-scroll {
    position: absolute;
    inset: 0;
    overflow-y: auto;
    padding: 4px 0;
    scrollbar-width: thin;
    scrollbar-color: oklch(30% 0 0) transparent;
}

.feed-entry {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 12px;
    font-size: 0.68rem;
    border-left: 3px solid transparent;
    animation: slideIn 0.3s ease-out;
    white-space: nowrap;
    overflow: hidden;
}

.feed-entry.scanner { border-left-color: var(--green-dim); }
.feed-entry.prober { border-left-color: var(--amber-dim); }
.feed-entry.exploiter { border-left-color: var(--red); }

.feed-entry.alert-ban {
    background: oklch(20% 0.08 25 / 0.3);
    border-left-color: var(--red);
    color: var(--red);
    font-weight: 600;
    justify-content: center;
}

.feed-entry.alert-engage {
    background: oklch(20% 0.06 85 / 0.3);
    border-left-color: var(--amber);
    color: var(--amber);
    font-weight: 600;
    justify-content: center;
}

.feed-entry.alert-ratelimit {
    background: oklch(18% 0.06 300 / 0.3);
    border-left-color: oklch(65% 0.20 300);
    color: oklch(70% 0.18 300);
    font-weight: 600;
    justify-content: center;
}

@keyframes slideIn {
    from { opacity: 0; transform: translateY(-12px); }
    to { opacity: 1; transform: translateY(0); }
}

.feed-entry:nth-child(even) { background: oklch(6% 0.005 220 / 0.5); }

.feed-time { color: var(--text-muted); font-variant-numeric: tabular-nums; min-width: 60px; }
.feed-proto { font-weight: 600; font-size: 0.6rem; padding: 1px 5px; border-radius: 3px; min-width: 40px; text-align: center; }
.feed-proto.http { background: oklch(30% 0.08 250); color: var(--blue); }
.feed-proto.ssh { background: oklch(30% 0.08 145); color: var(--green); }
.feed-proto.mysql { background: oklch(30% 0.08 300); color: oklch(70% 0.18 300); }
.feed-proto.postgresql { background: oklch(30% 0.08 200); color: oklch(72% 0.14 200); }
.feed-proto.redis { background: oklch(30% 0.08 25); color: var(--red); }
.feed-proto.telnet { background: oklch(30% 0.08 85); color: var(--amber); }

.feed-level { font-weight: 700; font-size: 0.58rem; padding: 1px 4px; border-radius: 3px; min-width: 16px; text-align: center; }
.feed-level.s { background: oklch(25% 0.06 145); color: var(--green); }
.feed-level.p { background: oklch(25% 0.06 85); color: var(--amber); }
.feed-level.e { background: oklch(25% 0.08 25); color: var(--red); }

.feed-hash { color: var(--cyan-dim); min-width: 78px; }
.feed-method { color: var(--text-dim); min-width: 32px; }
.feed-path { color: var(--text); flex: 1; overflow: hidden; text-overflow: ellipsis; }
.feed-cat { font-size: 0.55rem; padding: 1px 4px; border-radius: 3px; color: oklch(80% 0.12 280); background: oklch(25% 0.06 280); white-space: nowrap; }
.feed-country { color: var(--text-dim); min-width: 24px; text-align: right; }

/* Attack Map */
.map-container {
    position: relative;
    width: 100%;
    height: 100%;
    overflow: hidden;
}

.map-container svg {
    width: 100%;
    height: 100%;
}

.map-land {
    fill: oklch(14% 0.02 220);
    stroke: oklch(22% 0.03 220);
    stroke-width: 0.3;
}

.map-border {
    fill: none;
    stroke: oklch(18% 0.02 220);
    stroke-width: 0.15;
}

/* The map-ping keyframes and .map-hit ring are gone: they announced arrivals in real time and
   would have fired once per frozen hit on load. */

.map-dot {
    opacity: 0.7;
}

.map-stats {
    position: absolute;
    bottom: 8px;
    left: 8px;
    font-size: 0.6rem;
    color: var(--text-muted);
}

/* Timeline */
.timeline-panel {
    height: 200px;
}

.timeline-panel .panel-body {
    padding: 8px 12px;
}

#timeline-chart {
    width: 100%;
    height: 100%;
}

/* Two-column row */
.split-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

/* Top Paths */
.paths-list {
    padding: 8px 12px;
    overflow-y: auto;
    max-height: 300px;
}

.path-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 3px 0;
    font-size: 0.68rem;
}

.path-bar-bg {
    flex: 1;
    height: 18px;
    background: oklch(10% 0.01 220);
    border-radius: 3px;
    overflow: hidden;
    position: relative;
}

.path-bar-fill {
    height: 100%;
    background: linear-gradient(90deg, var(--cyan-dim), var(--cyan));
    border-radius: 3px;
    transition: width 0.5s ease;
}

.path-bar-label {
    position: absolute;
    left: 6px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 0.6rem;
    color: var(--text);
    white-space: nowrap;
    text-shadow: 0 0 4px oklch(0% 0 0);
}

.path-count {
    min-width: 36px;
    text-align: right;
    color: var(--text-dim);
    font-variant-numeric: tabular-nums;
}

/* Attackers Table */
.attackers-scroll {
    overflow-y: auto;
    max-height: 300px;
}

.attackers-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.65rem;
}

.attackers-table th {
    padding: 6px 8px;
    text-align: left;
    color: var(--text-dim);
    font-weight: 600;
    letter-spacing: 1px;
    text-transform: uppercase;
    font-size: 0.58rem;
    border-bottom: 1px solid var(--panel-border);
    position: sticky;
    top: 0;
    background: var(--panel);
    cursor: pointer;
}

.attackers-table th:hover { color: var(--cyan); }

.attackers-table td {
    padding: 5px 8px;
    border-bottom: 1px solid oklch(12% 0.01 220 / 0.5);
    font-variant-numeric: tabular-nums;
}

.attackers-table tr:hover td {
    background: oklch(12% 0.02 220 / 0.5);
}

.threat-badge {
    display: inline-block;
    padding: 1px 5px;
    border-radius: 3px;
    font-size: 0.58rem;
    font-weight: 600;
}
.threat-badge.scanner { background: oklch(25% 0.06 145); color: var(--green); }
.threat-badge.prober { background: oklch(25% 0.06 85); color: var(--amber); }
.threat-badge.exploiter { background: oklch(25% 0.08 25); color: var(--red); }

.proto-tag {
    display: inline-block;
    padding: 0 3px;
    border-radius: 2px;
    font-size: 0.55rem;
    margin-right: 2px;
}

/* Donut Charts */
.donuts-row {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 12px;
}

.donut-container {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 24px;
    padding: 16px;
}

.donut-svg {
    width: 140px;
    height: 140px;
}

.donut-ring {
    fill: none;
    stroke-width: 16;
    transition: stroke-dasharray 0.5s ease;
}

.donut-center-text {
    font-family: var(--mono);
    font-size: 16px;
    font-weight: 700;
    fill: var(--text);
    text-anchor: middle;
    dominant-baseline: central;
}

.donut-center-label {
    font-family: var(--mono);
    font-size: 7px;
    fill: var(--text-dim);
    text-anchor: middle;
    letter-spacing: 1px;
    text-transform: uppercase;
}

.donut-legend {
    display: flex;
    flex-direction: column;
    gap: 8px;
    font-size: 0.68rem;
}

.legend-item {
    display: flex;
    align-items: center;
    gap: 8px;
}

.legend-swatch {
    width: 10px;
    height: 10px;
    border-radius: 2px;
    flex-shrink: 0;
}

.legend-label { color: var(--text-dim); }
.legend-value { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; margin-left: auto; }

/* Footer */
.footer-link {
    text-align: center;
    padding: 12px;
    font-size: 0.7rem;
}
.footer-link a {
    color: var(--cyan-dim);
    text-decoration: none;
}
.footer-link a:hover { color: var(--cyan); }

/* Responsive */
@media (max-width: 1024px) {
    .main-row { grid-template-columns: 1fr; }
    .main-row .panel:first-child { min-height: 350px; }
    .main-row .panel:last-child { min-height: 300px; }
    .split-row { grid-template-columns: 1fr; }
    .donuts-row { grid-template-columns: 1fr; }
}

@media (max-width: 768px) {
    .stats-bar { flex-wrap: wrap; }
    .stat-card { flex: 1 1 calc(50% - 6px); min-width: 120px; }
    .header { flex-direction: column; gap: 8px; text-align: center; }
    .header-left h1 { font-size: 1.2rem; }
    .attackers-scroll { overflow-x: auto; }
    .attackers-table { min-width: 600px; }
    .dashboard { padding: 8px; }
}

/* uPlot overrides */
.uplot { background: transparent !important; }
.u-legend { font-family: var(--mono) !important; font-size: 0.65rem !important; color: var(--text-dim) !important; }
.u-legend .u-value { font-weight: 600 !important; }

/* ==========================================================================
   RETIRED MODE
   ==========================================================================
   The dashboard is frozen on its final data. Desaturation is applied as a
   `filter` on .dashboard rather than by overriding the :root custom properties,
   because most colours are NOT in :root — the uPlot series and axes, the donut
   ring strokes, the 25-entry attack-category palette and the protocol tag
   backgrounds are all oklch literals set from JS via setAttribute or inline
   style, and none of that is reachable by a property override. An ancestor
   filter covers the whole subtree including the uPlot <canvas> bitmap.

   The class goes on .dashboard, NOT <body>: `filter` establishes a containing
   block for position:fixed descendants, and the scanline/noise overlays are
   body::before / body::after. Those are switched off separately below. */

.dashboard.retired {
    filter: grayscale(0.85) contrast(0.95);
}

/* 0.85 rather than 1 keeps a trace of the CRT identity instead of reading as a
   broken page. */

/* The scanline and film-grain overlays are body pseudo-elements, so they sit outside the
   filtered .dashboard subtree and have to be switched off directly. */
body.retired::before,
body.retired::after {
    display: none;
}

/* One rule retires every animation and transition on the page: blink, pulse-dot,
   glitch, slideIn, map-ping, the map-dot and path-bar-fill transitions, the
   donut-ring sweep and the exploiter flash. */
.retired,
.retired *,
.retired *::before,
.retired *::after {
    animation: none !important;
    transition: none !important;
}

.status-dot.retired-dot {
    background: var(--text-dim);
    box-shadow: none;
}

.status-label.retired-label {
    color: var(--text-dim);
    letter-spacing: 1px;
}

.header-window {
    font-size: 0.7rem;
    color: var(--text-dim);
    letter-spacing: 0.5px;
}

/* --- RETIRED stamp ---------------------------------------------------------- */

.retired-stamp {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 16px;
    margin-bottom: 16px;
    padding: 14px 20px;
    border: 1px solid oklch(45% 0.02 220 / 0.6);
    border-radius: 4px;
    background: repeating-linear-gradient(
        135deg,
        oklch(10% 0.005 220) 0px,
        oklch(10% 0.005 220) 12px,
        oklch(13% 0.008 220) 12px,
        oklch(13% 0.008 220) 24px
    );
}

.retired-stamp-word {
    font-size: clamp(1.1rem, 4vw, 2rem);
    font-weight: 700;
    letter-spacing: clamp(4px, 2vw, 14px);
    color: oklch(72% 0.02 220);
    text-transform: uppercase;
    white-space: nowrap;
}

.retired-stamp-note {
    font-size: 0.72rem;
    line-height: 1.5;
    color: var(--text-dim);
    max-width: 52ch;
}

/* Keeps the trailing ISO date off its own line. */
#stamp-window {
    white-space: nowrap;
}

@media (max-width: 620px) {
    .retired-stamp {
        flex-direction: column;
        text-align: center;
    }

    #stamp-window {
        white-space: normal;
    }
}

.retired-cta {
    display: block;
    margin: 0 0 16px;
    padding: 14px 20px;
    text-align: center;
    text-decoration: none;
    border: 1px solid var(--cyan-dim);
    border-radius: 4px;
    background: oklch(12% 0.02 220);
    color: var(--cyan);
    font-weight: 600;
    font-size: 0.85rem;
    letter-spacing: 1px;
}

.retired-cta:hover {
    background: oklch(16% 0.03 220);
}

.retired-cta small {
    display: block;
    margin-top: 6px;
    font-weight: 400;
    letter-spacing: 0;
    color: var(--text-dim);
}

/* --- Panel note (used to label frozen panels honestly) ---------------------- */

.panel-note {
    padding: 6px 12px;
    font-size: 0.65rem;
    color: var(--text-dim);
    border-bottom: 1px solid var(--panel-border);
    background: oklch(6% 0.01 220);
}

/* --- Classes replacing inline style attributes ------------------------------
   A style="..." attribute in markup is blocked by `style-src 'self'`, so every
   constant colour moved here. Only genuinely per-item values (path bar widths,
   donut swatch colours) are still assigned in JS, through the CSSOM. */

.proto-tag {
    background: oklch(25% 0.06 220);
}

.proto-tag.cat-tag {
    background: oklch(25% 0.05 280);
    color: oklch(75% 0.12 280);
}

.attackers-table td.cell-actor {
    color: var(--cyan-dim);
}

.attackers-table td.cell-dim {
    color: var(--text-dim);
}

/* Eight-digit event totals overflow the ring at the inherited size. */
.donut-center-text {
    font-size: 13px;
}
