<!DOCTYPE html>
<html lang="<%= lang %>">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
    <title><%= t('analytics.pageTitle') %> | <%= botName %></title>
    <meta name="description" content="<%= t('analytics.pageSubtitle') %>">
    <link rel="icon" type="image/png" href="/images/transparent_toothlessssss.png">

    <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
    <link rel="preload" href="/Fonts/symbols_skyquake.ttf" as="font" type="font/ttf" crossorigin="anonymous">
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="/css/output.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" media="print" onload="this.media='all'">

    <style>
        @font-face {
            font-family: 'skyquakesymbols';
            src: url('/Fonts/symbols_skyquake.ttf') format('truetype');
            font-display: swap;
        }
        body { background: #1b1b1b; min-height: 100vh; }
        .text-accent { color: #F5F5DC; }
        .text-muted { color: #90EE90; }

        .leaderboard-container {
            max-width: 1600px;
            margin: 0 auto;
            padding: 6rem 1rem 2rem;
            min-height: calc(100vh - 200px);
        }
        .leaderboard-header { text-align: center; margin-bottom: 2rem; }
        .leaderboard-title {
            font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5rem;
            background: linear-gradient(135deg, #F5F5DC 0%, #E8E8D0 100%);
            -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
        }
        .leaderboard-subtitle { font-size: 1.1rem; color: rgba(255, 255, 255, 0.8); margin-bottom: 1rem; }
        .disclaimer { font-size: 0.9rem; opacity: 0.8; font-style: italic; color: rgba(255, 255, 255, 0.7); }

        .mode-pills {
            display: flex; justify-content: center; gap: 0.75rem; flex-wrap: wrap;
            margin-bottom: 1.5rem;
        }
        .mode-pill {
            padding: 0.6rem 1.4rem;
            background: rgba(30, 30, 30, 0.8);
            border: 1px solid rgba(144, 238, 144, 0.2);
            border-radius: 999px;
            color: #ffffff; font-weight: 500; cursor: pointer;
            transition: all 0.2s ease;
        }
        .mode-pill:hover { background: rgba(144, 238, 144, 0.1); color: #90EE90; border-color: #90EE90; }
        .mode-pill.active {
            background: linear-gradient(135deg, rgba(144, 238, 144, 0.15), rgba(144, 238, 144, 0.05));
            color: #90EE90; border-color: #90EE90;
            text-shadow: 0 0 12px rgba(144, 238, 144, 0.5);
        }

        .search-wrapper {
            max-width: 800px; margin: 0 auto 2.5rem; position: relative; z-index: 100;
        }
        .search-bar {
            background: rgba(28, 28, 28, 0.6);
            border: 1px solid rgba(144, 238, 144, 0.08);
            border-radius: 16px;
            backdrop-filter: blur(20px);
            transition: border-color 0.3s ease;
            padding: 0.75rem 1.25rem;
            display: flex; align-items: center; gap: 0.75rem;
            position: relative;
        }
        .search-bar:focus-within { border-color: rgba(144, 238, 144, 0.2); }
        .search-bar input {
            flex: 1; width: 100%; background: transparent; border: none; outline: none;
            color: #fff; font-size: 14px; font-weight: 500; padding: 0.5rem 0;
            font-family: 'Inter', system-ui, -apple-system, sans-serif;
        }
        /* Squadron tags + vehicle names contain skyquake glyphs (▄ ◢ ◊ ␗ etc.)
           that need the symbol font to render. Player nicks don't, so leave
           the player input on Inter. */
        #squadronInput, #vehicleInput {
            font-family: 'skyquakesymbols', 'Inter', system-ui, -apple-system, sans-serif;
        }
        .search-label {
            font-size: 10px; font-weight: 700; letter-spacing: 0.2em; text-transform: uppercase;
            color: rgba(144, 238, 144, 0.35); margin-bottom: 0.25rem;
        }
        .search-drop {
            position: absolute;
            left: 0; right: 0; top: calc(100% + 8px);
            border-radius: 12px; max-height: 280px; overflow-y: auto; z-index: 999;
            background: rgba(20, 20, 20, 0.98);
            border: 1px solid rgba(144, 238, 144, 0.1);
            backdrop-filter: blur(24px);
            box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
        }
        .search-drop::-webkit-scrollbar { width: 4px; }
        .search-drop::-webkit-scrollbar-thumb { background: rgba(144, 238, 144, 0.15); border-radius: 4px; }
        .search-hit {
            padding: 10px 16px; cursor: pointer; transition: background 0.15s;
            border-bottom: 1px solid rgba(255, 255, 255, 0.03);
        }
        .search-hit:last-child { border-bottom: none; }
        .search-hit:hover { background: rgba(144, 238, 144, 0.05); }
        .sq-tag { font-family: 'skyquakesymbols', 'Inter', sans-serif; color: #A8E6CF; }
        /* Apply the skyquake symbol font everywhere a WT-translated vehicle
           name is shown, so country-leak / event indicator glyphs (▄ ◢ ◊ ␗
           etc.) render as proper icons instead of fallback Unicode boxes.
           Browsers vary on how strictly they let custom fonts apply inside
           <select>/<option> — applying it on both is the most we can do. */
        .vehicle-name,
        .comp-lineup-items {
            font-family: 'skyquakesymbols', 'Inter', sans-serif;
        }

        .leaderboard-nav {
            background: rgba(30, 30, 30, 0.6);
            border-radius: 1rem;
            padding: 1rem;
            backdrop-filter: blur(10px);
            border: 1px solid rgba(144, 238, 144, 0.1);
            margin-bottom: 2rem;
            display: flex; justify-content: center; gap: 0.75rem; flex-wrap: wrap;
        }
        .leaderboard-tab {
            padding: 0.65rem 1.25rem;
            background: rgba(30, 30, 30, 0.8);
            border: 1px solid rgba(144, 238, 144, 0.2);
            border-radius: 0.5rem;
            color: #ffffff; cursor: pointer; font-weight: 500;
            transition: all 0.3s ease;
        }
        .leaderboard-tab:hover { background: rgba(144, 238, 144, 0.1); color: #90EE90; border-color: #90EE90; }
        .leaderboard-tab.active {
            background: linear-gradient(135deg, rgba(144, 238, 144, 0.15), rgba(144, 238, 144, 0.05));
            color: #90EE90; border-color: #90EE90;
            text-shadow: 0 0 12px rgba(144, 238, 144, 0.5);
        }

        .leaderboard-content {
            background: rgba(30, 30, 30, 0.6);
            border-radius: 1rem;
            padding: 2rem;
            backdrop-filter: blur(10px);
            border: 1px solid rgba(144, 238, 144, 0.1);
            min-height: 300px;
        }

        .loading-state, .empty-state, .error-state {
            text-align: center; padding: 4rem 2rem; color: #90EE90;
        }
        .loading-spinner {
            border: 3px solid rgba(144, 238, 144, 0.3); border-top: 3px solid #90EE90;
            border-radius: 50%; width: 40px; height: 40px;
            animation: spin 1s linear infinite; margin: 0 auto 1rem;
        }
        @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
        .error-state { color: #ff6b6b; }

        .analytics-table {
            width: 100%; border-collapse: collapse;
            background: rgba(30, 30, 30, 0.8);
            border-radius: 0.75rem; overflow: hidden;
            border: 1px solid rgba(144, 238, 144, 0.1);
        }
        .analytics-table th {
            background: linear-gradient(135deg, rgba(144, 238, 144, 0.15), rgba(144, 238, 144, 0.05));
            color: #F5F5DC; padding: 0.9rem 1rem; text-align: center;
            font-weight: 600; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.5px;
            border-bottom: 1px solid rgba(144, 238, 144, 0.2);
            text-shadow: 0 0 10px rgba(144, 238, 144, 0.3);
        }
        .analytics-table th:first-child, .analytics-table td:first-child { text-align: left; }
        .analytics-table td {
            padding: 0.75rem 1rem; border-bottom: 1px solid rgba(144, 238, 144, 0.08);
            color: #fff; text-align: center; font-size: 0.9rem;
        }
        .analytics-table tr:last-child td { border-bottom: none; }
        .analytics-table tr:hover td { background: rgba(144, 238, 144, 0.04); }
        .analytics-table th.sortable-th { cursor: pointer; user-select: none; }
        .analytics-table th.sortable-th:hover { background: linear-gradient(135deg, rgba(144, 238, 144, 0.25), rgba(144, 238, 144, 0.1)); }
        .analytics-table th .sort-ind { margin-left: 4px; font-size: 0.7rem; opacity: 0.4; }
        .analytics-table th[data-sort-dir="asc"] .sort-ind,
        .analytics-table th[data-sort-dir="desc"] .sort-ind { opacity: 0.95; color: #90EE90; }

        .bar-track {
            width: 100%; max-width: 160px; height: 8px;
            background: rgba(144, 238, 144, 0.08); border-radius: 999px;
            overflow: hidden; margin: 0 auto;
        }
        .bar-fill {
            height: 100%; background: linear-gradient(90deg, #90EE90, #A8E6CF);
            border-radius: 999px;
        }

        .section-divider {
            text-align: left; padding: 0.8rem 1rem 0.4rem;
            color: #90EE90; font-weight: 600; text-transform: uppercase;
            font-size: 0.75rem; letter-spacing: 0.15em;
        }

        .matchup-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
        .matchup-col h3 {
            text-align: center; margin-bottom: 1rem;
            color: #F5F5DC; font-size: 1.05rem; font-weight: 600;
        }

        .maps-layout {
            display: grid;
            grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
            gap: 1.5rem;
            align-items: start;
        }
        .maps-table-wrap {
            max-height: 640px;
            overflow-y: auto;
            border-radius: 0.75rem;
            border: 1px solid rgba(144, 238, 144, 0.1);
        }
        .maps-table-wrap::-webkit-scrollbar { width: 6px; }
        .maps-table-wrap::-webkit-scrollbar-thumb { background: rgba(144, 238, 144, 0.2); border-radius: 4px; }
        .maps-table-wrap .analytics-table { border: none; border-radius: 0; }
        .maps-table-wrap .analytics-table thead th {
            position: sticky; top: 0; z-index: 2;
            background: #2a352a;
            box-shadow: inset 0 -1px 0 rgba(144, 238, 144, 0.25);
        }
        .maps-table .col-map { width: 32%; }
        .maps-table .col-num { width: 9%; }
        .maps-table .col-bar { width: 32%; }
        .maps-table .bar-track {
            max-width: none; width: 100%; height: 12px;
        }
        .squadmates-table .sqmate-link {
            color: #A8E6CF;
            text-decoration: none;
        }
        .squadmates-table .sqmate-link:hover {
            text-decoration: underline;
        }
        .squadmates-table .sqmate-meta {
            display: block;
            margin-top: 0.15rem;
            font-size: 0.72rem;
            color: rgba(168, 230, 207, 0.65);
            letter-spacing: 0.03em;
        }

        .radar-card {
            background: linear-gradient(135deg, rgba(40, 50, 40, 0.55), rgba(20, 25, 20, 0.55));
            border: 1px solid rgba(144, 238, 144, 0.15);
            border-radius: 1rem;
            padding: 1.25rem 1.25rem 0.75rem;
            backdrop-filter: blur(14px);
            -webkit-backdrop-filter: blur(14px);
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.04);
            position: sticky; top: 1rem;
        }
        .radar-card-header {
            display: flex; justify-content: space-between; align-items: baseline;
            margin-bottom: 0.5rem;
        }
        .radar-card-title {
            font-size: 0.95rem; font-weight: 600; color: #F5F5DC;
            text-transform: uppercase; letter-spacing: 0.1em;
        }
        .radar-card-meta { font-size: 0.7rem; color: rgba(168, 230, 207, 0.7); letter-spacing: 0.05em; }
        .radar-canvas-wrap { position: relative; width: 100%; aspect-ratio: 1 / 1; max-width: 620px; margin: 0 auto; }
        .radar-legend {
            display: flex; justify-content: center; gap: 1.25rem; flex-wrap: wrap;
            font-size: 0.75rem; color: rgba(255, 255, 255, 0.75); margin-top: 0.5rem;
        }
        .radar-legend .swatch {
            display: inline-block; width: 10px; height: 10px; border-radius: 2px;
            margin-right: 0.4rem; vertical-align: middle;
        }
        .radar-footnote {
            margin-top: 0.6rem; font-size: 0.7rem; color: rgba(255,255,255,0.45);
            text-align: center; font-style: italic;
        }

        .entity-header {
            display: flex; flex-wrap: wrap; align-items: center; gap: 1.25rem;
            padding: 1rem 1.25rem;
            background: rgba(30, 30, 30, 0.6);
            border: 1px solid rgba(144, 238, 144, 0.12);
            border-radius: 0.75rem;
            margin-bottom: 1.25rem;
            backdrop-filter: blur(10px);
        }
        .entity-name {
            display: inline-flex; align-items: center; gap: 0.55rem;
            font-size: 1.15rem; font-weight: 600; color: #F5F5DC;
            text-decoration: none; padding-right: 1rem;
            border-right: 1px solid rgba(144, 238, 144, 0.18);
        }
        .entity-name:hover { color: #90EE90; }
        .entity-name i { color: rgba(144, 238, 144, 0.7); font-size: 0.95rem; }

        /* Filter bar nests inline inside the entity header now. */
        .af-time-filter-bar {
            display: flex; flex-wrap: wrap; align-items: end; gap: 1rem;
            padding: 0; background: transparent; border: none;
            border-radius: 0; margin-bottom: 0; backdrop-filter: none;
            flex: 1 1 auto;
        }
        .af-time-filter-bar .filter-group {
            display: flex; flex-direction: column; gap: 0.4rem;
        }
        .af-time-filter-bar label {
            color: rgba(255, 255, 255, 0.9); font-weight: 500;
            font-size: 0.85rem; white-space: nowrap;
        }
        .af-time-filter-bar select,
        .af-time-filter-bar input[type="date"] {
            background: rgba(30, 30, 30, 0.9);
            border: 1px solid rgba(144, 238, 144, 0.25);
            border-radius: 0.5rem;
            padding: 0.45rem 0.7rem;
            color: #fff; font-size: 0.9rem; min-width: 140px;
            font-family: inherit;
            transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
        }
        .af-time-filter-bar select:focus,
        .af-time-filter-bar input[type="date"]:focus {
            outline: none; border-color: #90EE90;
            box-shadow: 0 0 15px rgba(144, 238, 144, 0.3);
        }
        .af-time-filter-bar select option { background: rgba(30,30,30,0.95); color: #fff; }
        .af-time-filter-bar input[type="date"]::-webkit-calendar-picker-indicator {
            filter: invert(1) opacity(0.6); cursor: pointer;
        }
        .af-time-filter-bar .custom-range { flex-direction: row; align-items: end; gap: 0.5rem; }
        .af-time-filter-bar .filter-reset-btn {
            background: linear-gradient(135deg, rgba(239,68,68,0.2), rgba(220,38,38,0.2));
            border: 1px solid rgba(239,68,68,0.4);
            color: #ef4444; padding: 0.5rem 1rem;
            border-radius: 0.5rem; cursor: pointer;
            font-weight: 600; font-size: 0.85rem;
            transition: all 0.25s ease;
        }
        .af-time-filter-bar .filter-reset-btn:hover {
            background: linear-gradient(135deg, rgba(239,68,68,0.35), rgba(220,38,38,0.35));
            border-color: #ef4444;
        }

        .consistency-layout {
            display: grid;
            grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
            gap: 1.5rem;
            align-items: start;
        }
        .consistency-table-wrap {
            max-height: 640px; overflow-y: auto;
            border-radius: 0.75rem;
            border: 1px solid rgba(144, 238, 144, 0.1);
        }
        .consistency-table-wrap::-webkit-scrollbar { width: 6px; }
        .consistency-table-wrap::-webkit-scrollbar-thumb { background: rgba(144, 238, 144, 0.2); border-radius: 4px; }
        .consistency-table-wrap .analytics-table { border: none; border-radius: 0; }
        .consistency-table-wrap .analytics-table thead th {
            position: sticky; top: 0; z-index: 2;
            background: #2a352a;
            box-shadow: inset 0 -1px 0 rgba(144, 238, 144, 0.25);
        }
        .consistency-row { cursor: pointer; transition: background 0.15s; }
        .consistency-row.active td { background: rgba(144, 238, 144, 0.12) !important; }
        .scatter-card {
            background: linear-gradient(135deg, rgba(40, 50, 40, 0.55), rgba(20, 25, 20, 0.55));
            border: 1px solid rgba(144, 238, 144, 0.15);
            border-radius: 1rem;
            padding: 1.25rem 1.25rem 0.75rem;
            backdrop-filter: blur(14px);
            -webkit-backdrop-filter: blur(14px);
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.04);
            position: sticky; top: 1rem;
        }
        .scatter-card-header {
            display: flex; justify-content: space-between; align-items: baseline;
            margin-bottom: 0.5rem;
        }
        .scatter-card-title {
            font-size: 0.95rem; font-weight: 600; color: #F5F5DC;
            text-transform: uppercase; letter-spacing: 0.1em;
        }
        .scatter-reset {
            background: rgba(144,238,144,0.15); border: 1px solid rgba(144,238,144,0.3);
            color: #90EE90; border-radius: 6px; padding: 0.25rem 0.6rem;
            font-size: 0.7rem; cursor: pointer;
        }
        .scatter-reset:hover { background: rgba(144,238,144,0.25); }
        .scatter-canvas-wrap { position: relative; width: 100%; aspect-ratio: 1 / 1; max-width: 620px; margin: 0 auto; }

        .comps-layout {
            display: flex; flex-direction: column; gap: 1.5rem;
        }
        .comps-section { display: flex; flex-direction: column; gap: 0.6rem; }
        .comps-section-header {
            display: flex; justify-content: space-between; align-items: baseline;
            padding: 0 0.25rem;
        }
        .comps-section-title {
            font-size: 0.95rem; font-weight: 600; color: #F5F5DC;
            text-transform: uppercase; letter-spacing: 0.1em;
        }
        .comps-section-meta {
            font-size: 0.7rem; color: rgba(168, 230, 207, 0.7); letter-spacing: 0.05em;
        }
        .comps-table-wrap {
            max-height: 540px; overflow-y: auto;
            border-radius: 0.75rem;
            border: 1px solid rgba(144, 238, 144, 0.1);
        }
        .comps-table-wrap::-webkit-scrollbar { width: 6px; }
        .comps-table-wrap::-webkit-scrollbar-thumb { background: rgba(144, 238, 144, 0.2); border-radius: 4px; }
        .comps-table-wrap .analytics-table { border: none; border-radius: 0; }
        .comps-table-wrap .analytics-table thead th {
            position: sticky; top: 0; z-index: 2;
            background: #2a352a;
            box-shadow: inset 0 -1px 0 rgba(144, 238, 144, 0.25);
        }
        .comp-type-tag {
            display: inline-block; margin-left: 0.4rem;
            font-size: 0.7rem; font-weight: 700; letter-spacing: 0.05em;
            padding: 0.05rem 0.35rem;
            border: 1px solid currentColor; border-radius: 4px;
            opacity: 0.85;
        }
        .comp-type-badges {
            display: inline-flex; gap: 0.35rem; flex-wrap: wrap;
            align-items: center; justify-content: flex-start;
        }
        .comp-type-badge {
            display: inline-flex; align-items: baseline; gap: 0.2rem;
            padding: 0.18rem 0.5rem;
            background: rgba(0, 0, 0, 0.25);
            border: 1px solid; border-radius: 6px;
            font-size: 0.85rem; font-weight: 700;
            font-variant-numeric: tabular-nums;
            white-space: nowrap;
        }
        .comp-type-badge-letter {
            font-size: 0.68rem; font-weight: 600; letter-spacing: 0.05em;
            opacity: 0.85;
        }
        .comp-lineup {
            display: flex; flex-direction: column; gap: 0.2rem;
            text-align: left;
        }
        .comp-lineup-row {
            display: flex; gap: 0.6rem; align-items: baseline;
            font-size: 0.85rem; line-height: 1.35;
        }
        .comp-lineup-label {
            flex: 0 0 140px;
            font-size: 0.7rem; font-weight: 700; letter-spacing: 0.05em;
            text-transform: uppercase; opacity: 0.95;
            white-space: nowrap;
        }
        .comp-lineup-items {
            flex: 1 1 auto;
            min-width: 0;
            color: rgba(245, 245, 220, 0.92);
        }
        .comps-comp-table td:nth-child(2),
        .comps-comp-table td:nth-child(3) { text-align: left; }
        .comps-comp-table th:nth-child(2),
        .comps-comp-table th:nth-child(3) { text-align: left; padding-left: 1rem; }

        .comp-search {
            display: flex; flex-direction: column; gap: 0.6rem;
            padding: 0.85rem 1rem;
            background: rgba(20, 25, 20, 0.55);
            border: 1px solid rgba(144, 238, 144, 0.15);
            border-radius: 0.75rem;
        }
        .comp-search-row {
            display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center;
        }
        .comp-search-label {
            flex: 0 0 auto;
            font-size: 0.75rem; color: rgba(168, 230, 207, 0.85);
            text-transform: uppercase; letter-spacing: 0.05em;
            min-width: 110px;
        }
        .comp-search-select {
            flex: 1 1 220px;
            background: rgba(15, 20, 15, 0.95);
            border: 1px solid rgba(144, 238, 144, 0.3);
            border-radius: 0.45rem;
            padding: 0.4rem 0.6rem;
            color: #F5F5DC; font-size: 0.85rem;
        }
        .comp-search-select option { background: rgba(15, 20, 15, 0.98); color: #F5F5DC; }
        .comp-search-btn {
            padding: 0.35rem 0.85rem;
            border-radius: 0.45rem; border: 1px solid rgba(144, 238, 144, 0.3);
            background: rgba(144, 238, 144, 0.1); color: #F5F5DC;
            cursor: pointer; font-size: 0.8rem;
        }
        .comp-search-btn:hover { background: rgba(144, 238, 144, 0.2); }
        .comp-search-btn-primary {
            background: rgba(144, 238, 144, 0.25);
            border-color: #90EE90; color: #1b1b1b; font-weight: 600;
        }
        .comp-search-btn-primary:hover { background: #90EE90; }
        .comp-search-btn-ghost { background: transparent; border-color: rgba(144, 238, 144, 0.2); }
        .comp-custom-types {
            display: flex; flex-wrap: wrap; gap: 0.5rem;
        }
        .comp-custom-type {
            display: inline-flex; align-items: center; gap: 0.5rem;
            padding: 0.4rem 0.65rem;
            background: rgba(0, 0, 0, 0.3);
            border: 1px solid rgba(144, 238, 144, 0.15);
            border-radius: 0.5rem;
            cursor: help;  /* hints that the title tooltip exists */
            transition: border-color 0.15s ease, background 0.15s ease;
        }
        .comp-custom-type:hover {
            border-color: rgba(144, 238, 144, 0.45);
            background: rgba(144, 238, 144, 0.05);
        }
        /* Bump the type letter inside the customizer specifically — the small
           badges in the Comp column stay at their original size. */
        .comp-custom-type .comp-type-badge {
            padding: 0.3rem 0.65rem;
            font-size: 1.05rem;
            border-radius: 7px;
        }
        .comp-custom-type .comp-type-badge-letter {
            font-size: 0.95rem;
            opacity: 0.95;
        }
        .comp-custom-type input[type="number"] {
            width: 64px;
            background: rgba(15, 20, 15, 0.95);
            border: 1px solid rgba(144, 238, 144, 0.25);
            border-radius: 0.4rem;
            padding: 0.35rem 0.5rem;
            color: #F5F5DC; font-size: 1rem; text-align: center;
            font-variant-numeric: tabular-nums;
            font-weight: 600;
            -moz-appearance: textfield;
        }
        .comp-custom-type input[type="number"]:focus {
            outline: none;
            border-color: #90EE90;
            box-shadow: 0 0 12px rgba(144, 238, 144, 0.18);
        }
        .comp-custom-type input[type="number"]::-webkit-outer-spin-button,
        .comp-custom-type input[type="number"]::-webkit-inner-spin-button {
            -webkit-appearance: none; margin: 0;
        }
        .comp-search-row-block { align-items: flex-start; }
        .comp-search-hint {
            font-size: 0.7rem; color: rgba(245, 245, 220, 0.55);
            font-style: italic;
        }
        .comp-refine-container {
            display: flex; flex-direction: column; gap: 0.45rem;
            flex: 1 1 100%;
        }
        .comp-refine-hint {
            font-size: 0.78rem;
            color: rgba(245, 245, 220, 0.55);
            font-style: italic;
        }
        .comp-refine-row {
            display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap;
        }
        .comp-refine-label {
            flex: 0 0 130px;
            font-size: 0.72rem; font-weight: 700; letter-spacing: 0.05em;
            text-transform: uppercase; opacity: 0.95;
        }
        .comp-refine-slots {
            display: flex; flex-wrap: wrap; gap: 0.35rem;
            flex: 1 1 auto;
        }
        /* Custom dropdown — native <select> is replaced with a div+button so
           the skyquake symbol font actually applies to options across browsers
           (Firefox in particular delegates option rendering to the OS). */
        .comp-refine-select {
            position: relative;
            flex: 1 1 160px;
            min-width: 140px;
        }
        .comp-refine-select-btn {
            width: 100%;
            display: flex; align-items: center; justify-content: space-between;
            gap: 0.4rem;
            background: rgba(15, 20, 15, 0.95);
            border: 1px solid rgba(144, 238, 144, 0.25);
            border-radius: 0.4rem;
            padding: 0.3rem 0.6rem;
            color: #F5F5DC; font-size: 0.82rem;
            font-family: 'skyquakesymbols', 'Inter', sans-serif;
            cursor: pointer; text-align: left;
        }
        .comp-refine-select-btn:hover { border-color: rgba(144, 238, 144, 0.45); }
        .comp-refine-select.is-open .comp-refine-select-btn {
            border-color: #90EE90;
            box-shadow: 0 0 12px rgba(144, 238, 144, 0.18);
        }
        .comp-refine-select-btn-label {
            flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
        }
        .comp-refine-select-btn-label.is-placeholder { color: rgba(245, 245, 220, 0.5); }
        .comp-refine-select-btn i {
            font-size: 0.65rem; opacity: 0.55;
            transition: transform 0.15s ease;
        }
        .comp-refine-select.is-open .comp-refine-select-btn i { transform: rotate(180deg); opacity: 0.95; }
        .comp-refine-select-drop {
            position: absolute;
            left: 0; right: 0; top: calc(100% + 4px);
            border-radius: 0.5rem; max-height: 280px; overflow-y: auto;
            z-index: 60;
            background: rgba(20, 20, 20, 0.98);
            border: 1px solid rgba(144, 238, 144, 0.15);
            backdrop-filter: blur(24px);
            box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
            display: none;
        }
        .comp-refine-select.is-open .comp-refine-select-drop { display: block; }
        .comp-refine-select-drop::-webkit-scrollbar { width: 4px; }
        .comp-refine-select-drop::-webkit-scrollbar-thumb { background: rgba(144, 238, 144, 0.15); border-radius: 4px; }
        .comp-refine-select-opt {
            padding: 0.4rem 0.65rem;
            color: #F5F5DC; font-size: 0.82rem;
            font-family: 'skyquakesymbols', 'Inter', sans-serif;
            cursor: pointer;
            border-bottom: 1px solid rgba(255, 255, 255, 0.03);
            white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
        }
        .comp-refine-select-opt:last-child { border-bottom: none; }
        .comp-refine-select-opt:hover { background: rgba(144, 238, 144, 0.08); }
        .comp-refine-select-opt.is-selected {
            background: rgba(144, 238, 144, 0.12);
            color: #90EE90;
        }
        .comp-refine-select-opt.is-placeholder { color: rgba(245, 245, 220, 0.55); font-style: italic; }
        .comp-search-meta {
            font-size: 0.75rem; color: rgba(168, 230, 207, 0.7);
            text-align: right;
        }

        .time-table .time-cell { width: 50%; }
        .time-table .time-cell .time-label {
            display: inline-block; min-width: 95px; color: #fff;
        }
        .time-table .bar-track.time-bar {
            display: inline-block; vertical-align: middle;
            width: calc(100% - 110px); max-width: none; height: 10px;
            margin-left: 0.5rem;
        }

        .micro-bar {
            display: inline-block; vertical-align: middle;
            width: 70px; height: 8px;
            background: rgba(144, 238, 144, 0.08); border-radius: 999px; overflow: hidden;
            margin-left: 0.4rem;
        }
        .micro-bar > span {
            display: block; height: 100%;
            background: linear-gradient(90deg, #90EE90, #A8E6CF); border-radius: 999px;
        }
        .micro-bar.kd > span { background: linear-gradient(90deg, #F5C16C, #F5E6A0); }

        .player-stat-strip {
            display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
            gap: 0.75rem; margin-bottom: 1rem;
        }
        .player-stat-strip > div {
            background: rgba(30,30,30,0.7);
            border: 1px solid rgba(144, 238, 144, 0.12);
            border-radius: 0.6rem;
            padding: 0.65rem 0.85rem;
            display: flex; flex-direction: column; gap: 0.25rem;
        }
        .player-stat-strip .ps-label {
            font-size: 0.7rem; color: rgba(144,238,144,0.7);
            text-transform: uppercase; letter-spacing: 0.08em;
        }
        .player-stat-strip .ps-val { font-size: 1.1rem; font-weight: 600; color: #F5F5DC; }

        .timeline-card {
            background: linear-gradient(135deg, rgba(40, 50, 40, 0.55), rgba(20, 25, 20, 0.55));
            border: 1px solid rgba(144, 238, 144, 0.15);
            border-radius: 1rem;
            padding: 1.25rem;
            backdrop-filter: blur(14px);
            -webkit-backdrop-filter: blur(14px);
        }
        .timeline-card-header {
            display: flex; justify-content: space-between; align-items: baseline;
            margin-bottom: 0.5rem;
        }
        .timeline-card-title {
            font-size: 0.95rem; font-weight: 600; color: #F5F5DC;
            text-transform: uppercase; letter-spacing: 0.1em;
        }
        .timeline-card-meta { font-size: 0.7rem; color: rgba(168, 230, 207, 0.7); letter-spacing: 0.05em; }
        .timeline-canvas-wrap { position: relative; width: 100%; height: 320px; }
        .calendar-wrap {
            overflow-x: auto; padding: 0.25rem 0;
        }
        .calendar-wrap::-webkit-scrollbar { height: 6px; }
        .calendar-wrap::-webkit-scrollbar-thumb { background: rgba(144,238,144,0.2); border-radius: 4px; }

        @media (max-width: 1024px) {
            .maps-layout, .consistency-layout { grid-template-columns: 1fr; }
            .radar-card, .scatter-card { position: static; }
        }
        @media (max-width: 768px) {
            .matchup-grid { grid-template-columns: 1fr; }
            .leaderboard-title { font-size: 2rem; }
        }
    </style>
</head>

<body class="text-white antialiased">
    <%- include('partials/nav', { activePage: 'analytics' }) %>

    <div class="leaderboard-container">
        <div class="leaderboard-header">
            <h1 class="leaderboard-title"><%= t('analytics.pageTitle') %></h1>
            <p class="leaderboard-subtitle"><%= t('analytics.pageSubtitle') %></p>
            <div class="disclaimer">
                <i class="fas fa-info-circle" style="margin-right: 0.5rem;"></i>
                <%= t('common.recordingSince') %>
            </div>
        </div>

        <div class="mode-pills">
            <button class="mode-pill active" data-mode="squadron">
                <i class="fas fa-users"></i> <%= t('analytics.modeSquadron') %>
            </button>
            <button class="mode-pill" data-mode="player">
                <i class="fas fa-user"></i> <%= t('analytics.modePlayer') %>
            </button>
            <button class="mode-pill" data-mode="vehicle">
                <i class="fas fa-fighter-jet"></i> <%= t('analytics.modeVehicle') %>
            </button>
        </div>

        <div class="search-wrapper">
            <div id="squadronSearchBlock">
                <div class="search-bar">
                    <div style="flex:1; position:relative;">
                        <div class="search-label"><%= t('analytics.modeSquadron') %></div>
                        <input type="text" id="squadronInput" placeholder="<%= t('home.typeSquadronName') %>" autocomplete="off">
                        <div id="squadronDropdown" class="search-drop hidden"></div>
                    </div>
                    <button id="squadronClear" class="hidden" style="background:none; border:none; color:rgba(255,255,255,0.3); cursor:pointer; padding:4px 8px;">
                        <i class="fas fa-times"></i>
                    </button>
                </div>
            </div>

            <div id="playerSearchBlock" class="hidden">
                <div class="search-bar">
                    <div style="flex:1; position:relative;">
                        <div class="search-label"><%= t('analytics.modePlayer') %></div>
                        <input type="text" id="playerInput" placeholder="<%= t('home.typePlayerName') %>" autocomplete="off">
                        <div id="playerDropdown" class="search-drop hidden"></div>
                    </div>
                </div>
            </div>

            <div id="vehicleSearchBlock" class="hidden">
                <div class="search-bar">
                    <div style="flex:1; position:relative;">
                        <div class="search-label"><%= t('analytics.modeVehicle') %></div>
                        <input type="text" id="vehicleInput" placeholder="<%= t('leaderboard.searchVehiclePlaceholder') %>" autocomplete="off">
                        <div id="vehicleDropdown" class="search-drop hidden"></div>
                    </div>
                </div>
            </div>
        </div>

        <div id="analyticsResultsRoot">
            <div class="leaderboard-content">
                <div class="empty-state" id="emptyHint">
                    <i class="fas fa-arrow-up" style="font-size:1.5rem; margin-bottom:0.75rem; display:block;"></i>
                    <span id="emptyHintText"><%= t('analytics.pickSquadron') %></span>
                </div>
            </div>
        </div>
    </div>

    <%- include('partials/footer') %>

    <script>
        window.__lang = '<%= lang %>';
        window.__i18n = <%- localeJson %>;
        window.__t = function(key, params) {
            var parts = key.split('.'), obj = window.__i18n;
            for (var i = 0; i < parts.length; i++) { obj = obj && obj[parts[i]]; }
            var val = obj !== undefined ? obj : key;
            if (params && typeof val === 'string') {
                for (var p in params) {
                    if (Object.prototype.hasOwnProperty.call(params, p)) {
                        val = val.replace(new RegExp('\\{' + p + '\\}', 'g'), params[p]);
                    }
                }
            }
            return val;
        };
    </script>
    <script src="/js/main.js?v=3"></script>
    <script src="/js/api-client.js"></script>
    <script src="/js/seasons-filter.js"></script>
    <script src="/js/vehicle-i18n.js"></script>
    <script src="/js/chart.umd.min.js"></script>
    <script>
    (function() {
        const escapeHtml = (t) => { const d = document.createElement('div'); d.textContent = t == null ? '' : String(t); return d.innerHTML; };
        const T = (k, params) => window.__t(k, params);

        // ─────────── Mode switching ───────────
        const pills = document.querySelectorAll('.mode-pill');
        const blocks = {
            squadron: document.getElementById('squadronSearchBlock'),
            player: document.getElementById('playerSearchBlock'),
            vehicle: document.getElementById('vehicleSearchBlock'),
        };
        const hintText = document.getElementById('emptyHintText');
        const hintMsgs = {
            squadron: 'analytics.pickSquadron',
            player: 'analytics.pickPlayer',
            vehicle: 'analytics.pickVehicle',
        };
        let currentMode = 'squadron';

        function setMode(mode) {
            currentMode = mode;
            pills.forEach(p => p.classList.toggle('active', p.dataset.mode === mode));
            Object.entries(blocks).forEach(([key, el]) => el.classList.toggle('hidden', key !== mode));
            // Reset results panel when switching modes
            resetResults();
            hintText.textContent = T(hintMsgs[mode]);
        }
        pills.forEach(p => p.addEventListener('click', () => setMode(p.dataset.mode)));

        // ─────────── Data loading (shared squadrons + players) ───────────
        let playersData = [];
        let allSquadrons = [];

        async function loadData() {
            try {
                // Load player leaderboard + squadron leaderboard in parallel.
                // The player count we used to derive from playersData was
                // "every player who's ever logged a match wearing this tag",
                // which can exceed WT's 128-member cap because it counts
                // historical members too. /api/leaderboard/squadrons exposes
                // the real current-member count from the squadron_members
                // cache — use that as the authoritative number, and only
                // fall back to the historical derivation for squadrons not
                // present in that cache.
                const [playerResp, squadronResp] = await Promise.all([
                    window.apiClient.getPlayerLeaderboard(),
                    window.apiClient.getSquadronLeaderboard().catch(err => {
                        console.warn('squadron leaderboard load failed, falling back to player-derived counts', err);
                        return null;
                    }),
                ]);
                playersData = (playerResp.players || []).map(p => ({
                    ...p,
                    total_kills: p.total_kills || ((p.ground_kills || 0) + (p.air_kills || 0)),
                })).sort((a, b) => b.total_kills - a.total_kills);

                // Authoritative current-member counts keyed by clan_id and short_name.
                const memberByClanId = new Map();
                const memberByShort = new Map();
                if (squadronResp && Array.isArray(squadronResp.squadrons)) {
                    for (const s of squadronResp.squadrons) {
                        const n = s.player_count || 0;
                        if (s.clan_id != null) memberByClanId.set(s.clan_id, n);
                        if (s.short_name) memberByShort.set(s.short_name.toLowerCase(), n);
                    }
                }

                const groups = {};
                playersData.forEach(p => {
                    if (!p.squadron_name) return;
                    const key = p.squadron_clan_id != null
                        ? `id:${p.squadron_clan_id}`
                        : (p.squadron_short_name ? `s:${p.squadron_short_name.toLowerCase()}` : `n:${p.squadron_name.toLowerCase()}`);
                    if (!groups[key]) {
                        groups[key] = {
                            name: p.squadron_name,
                            short_name: p.squadron_short_name || p.squadron_name,
                            clan_id: p.squadron_clan_id != null ? p.squadron_clan_id : null,
                            count: 0,            // authoritative current member count (set below)
                            historical: 0,      // distinct players ever seen with this tag
                        };
                    }
                    if (p.squadron_short_name && groups[key].short_name === groups[key].name) {
                        groups[key].name = p.squadron_name;
                        groups[key].short_name = p.squadron_short_name;
                    }
                    if (p.squadron_clan_id != null && groups[key].clan_id == null) {
                        groups[key].clan_id = p.squadron_clan_id;
                    }
                    groups[key].historical += 1;
                });

                for (const g of Object.values(groups)) {
                    let canonical = null;
                    if (g.clan_id != null && memberByClanId.has(g.clan_id)) {
                        canonical = memberByClanId.get(g.clan_id);
                    } else if (g.short_name && memberByShort.has(g.short_name.toLowerCase())) {
                        canonical = memberByShort.get(g.short_name.toLowerCase());
                    }
                    g.count = canonical != null ? canonical : g.historical;
                }

                allSquadrons = Object.values(groups).sort((a, b) => b.count - a.count);
            } catch (e) { console.error('Error loading data:', e); }
        }
        loadData();

        // ─────────── Squadron search ───────────
        const sqInput = document.getElementById('squadronInput');
        const sqDrop = document.getElementById('squadronDropdown');
        const sqClear = document.getElementById('squadronClear');
        let sqTimeout, selectedSquadronShort = null;

        sqInput.addEventListener('input', () => {
            clearTimeout(sqTimeout);
            if (selectedSquadronShort) {
                selectedSquadronShort = null;
                sqClear.classList.add('hidden');
            }
            const val = sqInput.value.trim().toLowerCase();
            if (val.length < 1) { sqDrop.classList.add('hidden'); return; }
            sqTimeout = setTimeout(() => {
                const hits = allSquadrons.filter(s => s.name.toLowerCase().includes(val)).slice(0, 12);
                if (!hits.length) {
                    sqDrop.innerHTML = '<div class="p-3 text-center text-xs" style="color:rgba(144,238,144,0.25)">' + T('home.noSquadronsFound') + '</div>';
                } else {
                    sqDrop.innerHTML = hits.map(s => `
                        <div class="search-hit" data-name="${escapeHtml(s.name)}" data-short="${escapeHtml(s.short_name)}">
                            <span class="sq-tag text-sm">${escapeHtml(s.name)}</span>
                            <span class="text-[11px] ml-2" style="color:rgba(144,238,144,0.25)">${s.count} ${T('common.playersCount')}</span>
                        </div>`).join('');
                    sqDrop.querySelectorAll('.search-hit').forEach(el =>
                        el.addEventListener('click', () => pickSquadron(el.dataset.name, el.dataset.short))
                    );
                }
                sqDrop.classList.remove('hidden');
            }, 100);
        });
        sqInput.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') {
                e.preventDefault();
                const val = sqInput.value.trim().toLowerCase();
                const match = allSquadrons.find(s => s.name.toLowerCase() === val)
                    || allSquadrons.find(s => s.name.toLowerCase().includes(val));
                if (match) pickSquadron(match.name, match.short_name);
            }
            if (e.key === 'Escape') { sqDrop.classList.add('hidden'); sqInput.blur(); }
        });
        sqClear.addEventListener('click', () => {
            selectedSquadronShort = null;
            sqInput.value = '';
            sqClear.classList.add('hidden');
            resetResults();
        });
        function pickSquadron(name, shortName) {
            selectedSquadronShort = shortName || name;
            sqInput.value = shortName || name;
            sqClear.classList.remove('hidden');
            sqDrop.classList.add('hidden');
            renderSquadronPanel(selectedSquadronShort);
        }

        // ─────────── Player search (loads inline analytics) ───────────
        const plInput = document.getElementById('playerInput');
        const plDrop = document.getElementById('playerDropdown');
        let plTimeout;
        let activePlayerUid = null;
        let activePlayerNick = null;
        plInput.addEventListener('input', () => {
            clearTimeout(plTimeout);
            const val = plInput.value.trim().toLowerCase();
            if (val.length < 2) { plDrop.classList.add('hidden'); return; }
            plTimeout = setTimeout(() => {
                const hits = playersData.filter(p => p.nick.toLowerCase().includes(val)).slice(0, 20);
                if (!hits.length) {
                    plDrop.innerHTML = '<div class="p-3 text-center text-xs" style="color:rgba(144,238,144,0.25)">' + T('home.noPlayersFound') + '</div>';
                } else {
                    plDrop.innerHTML = hits.map(p => {
                        const tag = p.squadron_name ? `<span class="sq-tag text-[11px] mr-1.5" style="opacity:0.5">${escapeHtml(p.squadron_name)}</span>` : '';
                        return `<div class="search-hit" data-uid="${escapeHtml(p.uid)}" data-nick="${escapeHtml(p.nick)}">${tag}<span class="text-sm" style="color:#A8E6CF">${escapeHtml(p.nick)}</span></div>`;
                    }).join('');
                    plDrop.querySelectorAll('.search-hit').forEach(el => {
                        el.addEventListener('click', () => pickPlayer(el.dataset.uid, el.dataset.nick));
                    });
                }
                plDrop.classList.remove('hidden');
            }, 100);
        });
        plInput.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') { plDrop.classList.add('hidden'); plInput.blur(); }
        });
        function pickPlayer(uid, nick) {
            activePlayerUid = uid;
            activePlayerNick = nick;
            plInput.value = nick;
            plDrop.classList.add('hidden');
            renderPlayerPanel(uid, nick);
        }

        // ─────────── Vehicle search (loads inline analytics) ───────────
        // Driven by the translation map: /api/analytics/vehicle-list gives us
        // the set of (lowercased) vehicle_internal ids that have data + their
        // spawn counts; the per-language display name comes from the i18n map
        // (/api/i18n/vehicles) which already bakes in country-leak / event
        // glyphs (▄ ◢ ◊ ␗ etc.). So search filters on the localized name and
        // routing is by internal id, with no DB display string in the loop.
        const vhInput = document.getElementById('vehicleInput');
        const vhDrop = document.getElementById('vehicleDropdown');
        let vehicleList = []; // [{vehicle_internal, total}, ...] sorted by total desc
        let vhTimeout;
        let activeVehicle = null;          // display name (for the input + header)
        let activeVehicleInternal = null;  // canonical lowercased internal id (API key)

        async function ensureVehicleList() {
            if (vehicleList.length) return;
            try {
                const data = await window.apiClient.request('/api/analytics/vehicle-list');
                vehicleList = (data && data.vehicles) || [];
            } catch (e) { console.error('vehicle-list fetch failed', e); }
        }
        // Resolve the localized display name for an internal id. The translation
        // map (/api/i18n/vehicles) is the single source of truth — it bakes in
        // country/event glyphs per language (BOT/utils.py:1344). Returns the
        // internal id itself if the map hasn't loaded or doesn't know it.
        function vehicleDisplay(internal) {
            if (!internal) return '';
            if (window.vehicleI18n && window.vehicleI18n.translate) {
                return window.vehicleI18n.translate(internal, internal);
            }
            return internal;
        }
        vhInput.addEventListener('focus', () => {
            ensureVehicleList();
            if (window.vehicleI18n && window.vehicleI18n.ensureLoaded) window.vehicleI18n.ensureLoaded();
        });
        vhInput.addEventListener('input', () => {
            clearTimeout(vhTimeout);
            const val = vhInput.value.trim().toLowerCase();
            if (val.length < 1) { vhDrop.classList.add('hidden'); return; }
            vhTimeout = setTimeout(async () => {
                await ensureVehicleList();
                if (window.vehicleI18n && window.vehicleI18n.ensureLoaded) await window.vehicleI18n.ensureLoaded();
                // vehicleList is already total-desc; filter preserves order.
                const hits = [];
                for (const v of vehicleList) {
                    const display = vehicleDisplay(v.vehicle_internal);
                    if (!display.toLowerCase().includes(val)) continue;
                    hits.push({ vehicle: display, vehicle_internal: v.vehicle_internal, total: v.total });
                    if (hits.length >= 25) break;
                }
                if (!hits.length) {
                    vhDrop.innerHTML = '<div class="p-3 text-center text-xs" style="color:rgba(144,238,144,0.25)">No vehicles found</div>';
                } else {
                    vhDrop.innerHTML = hits.map(v => `
                        <div class="search-hit" data-pick-internal="${escapeHtml(v.vehicle_internal)}" data-pick-display="${escapeHtml(v.vehicle)}">
                            <span class="vehicle-name text-sm" style="color:#A8E6CF">${escapeHtml(v.vehicle)}</span>
                            <span class="text-[11px]" style="color:rgba(255,255,255,0.4); margin-left:0.5rem;">${v.total} uses</span>
                        </div>`).join('');
                    vhDrop.querySelectorAll('.search-hit').forEach(el => {
                        el.addEventListener('click', () => pickVehicle(el.dataset.pickDisplay, el.dataset.pickInternal));
                    });
                }
                vhDrop.classList.remove('hidden');
            }, 100);
        });
        vhInput.addEventListener('keydown', (e) => {
            if (e.key === 'Escape') { vhDrop.classList.add('hidden'); vhInput.blur(); }
            else if (e.key === 'Enter') {
                e.preventDefault();
                // Accept Enter only on an exact display match. If multiple
                // internals translate to the same display (country variants
                // sharing a name in the cache, rare), pick the most-used one
                // (first in the total-desc vehicleList).
                const val = vhInput.value.trim().toLowerCase();
                if (!val) return;
                for (const v of vehicleList) {
                    const display = vehicleDisplay(v.vehicle_internal);
                    if (display.toLowerCase() === val) { pickVehicle(display, v.vehicle_internal); break; }
                }
            }
        });
        function pickVehicle(name, internal) {
            activeVehicle = name;
            activeVehicleInternal = internal || null;
            vhInput.value = name;
            vhDrop.classList.add('hidden');
            renderVehiclePanel(name);
        }

        // ─────────── Results panel (squadron analytics) ───────────
        const resultsRoot = document.getElementById('analyticsResultsRoot');
        const cache = new Map(); // squadron|view|filterKey -> data
        let activeView = 'maps';
        let activeSquadron = null;
        let dateFilter = { type: 'all', startTs: 0, endTs: 0 };
        const TIMESLOT_BOUNDS = {
            all: { start: [0, 0, 0, 0], end: [23, 59, 59, 999] },
            na: { start: [1, 0, 0, 0], end: [7, 59, 59, 999] },
            eu: { start: [14, 0, 0, 0], end: [22, 59, 59, 999] },
        };

        function parseUtcDateInput(value) {
            const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value || '');
            if (!match) return null;
            return new Date(Date.UTC(
                Number(match[1]),
                Number(match[2]) - 1,
                Number(match[3]),
                0, 0, 0, 0
            ));
        }

        function buildTimeslotBoundary(dateValue, slotKey, edge) {
            const date = parseUtcDateInput(dateValue);
            if (!date) return null;
            const slot = TIMESLOT_BOUNDS[slotKey] || TIMESLOT_BOUNDS.all;
            const parts = edge === 'start' ? slot.start : slot.end;
            date.setUTCHours(parts[0], parts[1], parts[2], parts[3]);
            return date;
        }

        function getCustomRangeDayPair() {
            const startInput = document.getElementById('af-start-date')?.value || '';
            const endInput = document.getElementById('af-end-date')?.value || '';
            if (!startInput && !endInput) return null;
            return {
                startDay: startInput || endInput,
                endDay: endInput || startInput,
            };
        }

        function filterKey() {
            return `${dateFilter.type}:${dateFilter.startTs}:${dateFilter.endTs}`;
        }
        function filterQueryString() {
            const p = new URLSearchParams();
            if (dateFilter.startTs) p.set('start_date', String(dateFilter.startTs));
            if (dateFilter.endTs)   p.set('end_date',   String(dateFilter.endTs));
            // When the filter is narrower than a whole season (i.e. week or custom
            // date range), relax per-view min_games cutoffs so low-sample views
            // still show data instead of coming back empty.
            if (dateFilter.type === 'week' || dateFilter.type === 'date') {
                p.set('min_games', '1');
            }
            const s = p.toString();
            return s ? `?${s}` : '';
        }

        const VIEWS = [
            { key: 'maps',        label: 'analytics.tabMaps',        icon: 'map' },
            { key: 'comps',       label: 'analytics.tabComps',       icon: 'layer-group' },
            { key: 'consistency', label: 'analytics.tabConsistency', icon: 'chart-line' },
            { key: 'time',        label: 'analytics.tabTime',        icon: 'clock' },
            { key: 'matchup',     label: 'analytics.tabMatchups',    icon: 'users-between-lines' },
        ];

        const PLAYER_VIEWS = [
            { key: 'maps',     label: 'analytics.tabMaps',     icon: 'map' },
            { key: 'squadmates', label: 'analytics.tabSquadmates', icon: 'users' },
            { key: 'time',     label: 'analytics.tabTime',     icon: 'clock' },
            { key: 'timeline', label: 'analytics.tabTimeline', icon: 'chart-line' },
            { key: 'matchup',  label: 'analytics.tabMatchups', icon: 'users-between-lines' },
        ];

        let panelMode = null; // 'squadron' | 'player' | null

        function entityHeaderHtml(kind, name, href) {
            // Vehicle has no profile page yet — render as plain text, no icon.
            const iconHtml = kind === 'player'   ? '<i class="fas fa-user"></i>'
                           : kind === 'squadron' ? '<i class="fas fa-shield-halved"></i>'
                           : '';
            // Squadron and vehicle names need the skyquake font to render
            // country/event glyphs (◢ ▄ ◊ ␗ etc.). Player nicks are plain Inter.
            // For vehicles the caller already passes the localized display
            // (from the translation map), so we don't add data-vehicle-internal
            // here — that would just trigger redundant retranslation.
            const nameStyle = (kind === 'vehicle' || kind === 'squadron')
                ? ' style="font-family:\'skyquakesymbols\',\'Inter\',sans-serif;"' : '';
            const inner = `${iconHtml}<span${nameStyle}>${escapeHtml(name)}</span>`;
            const node = href
                ? `<a class="entity-name" href="${href}">${inner}</a>`
                : `<span class="entity-name">${inner}</span>`;
            return `
                <div class="entity-header">
                    ${node}
                    ${filterBarHtml()}
                </div>`;
        }

        function filterBarHtml() {
            return `
                <div class="af-time-filter-bar" id="afFilterBar">
                    <div class="filter-group">
                        <label for="af-filter-category"><i class="fas fa-filter" style="margin-right:0.3rem;"></i>${escapeHtml(T('player.filterBy'))}</label>
                        <select id="af-filter-category">
                            <option value="all">${escapeHtml(T('player.allTime'))}</option>
                            <option value="date">${escapeHtml(T('player.dateRange'))}</option>
                            <option value="season">${escapeHtml(T('player.season'))}</option>
                            <option value="week">${escapeHtml(T('player.week'))}</option>
                        </select>
                    </div>
                    <div class="filter-group" id="af-date-type-filter" style="display:none;">
                        <label for="af-date-filter-type"><i class="fas fa-calendar-alt" style="margin-right:0.3rem;"></i>${escapeHtml(T('player.dateType'))}</label>
                        <select id="af-date-filter-type">
                            <option value="last7">${escapeHtml(T('player.last7Days'))}</option>
                            <option value="last30">${escapeHtml(T('player.last30Days'))}</option>
                            <option value="last90">${escapeHtml(T('player.last90Days'))}</option>
                            <option value="custom">${escapeHtml(T('player.customRange'))}</option>
                        </select>
                    </div>
                    <div class="filter-group custom-range" id="af-custom-range-filters" style="display:none;">
                        <div class="filter-group">
                            <label for="af-start-date">${escapeHtml(T('player.from'))}</label>
                            <input type="date" id="af-start-date">
                        </div>
                        <div class="filter-group">
                            <label for="af-end-date">${escapeHtml(T('player.to'))}</label>
                            <input type="date" id="af-end-date">
                        </div>
                        <div class="filter-group" id="af-single-day-timeslot-filter" style="display:none;">
                            <label for="af-single-day-timeslot">${escapeHtml(T('player.timeslot'))}</label>
                            <select id="af-single-day-timeslot">
                                <option value="all">${escapeHtml(T('player.fullDay'))}</option>
                                <option value="na">${escapeHtml(T('analytics.naTimeslot'))}</option>
                                <option value="eu">${escapeHtml(T('analytics.euTimeslot'))}</option>
                            </select>
                        </div>
                    </div>
                    <div class="filter-group" id="af-season-select-filter" style="display:none;">
                        <label for="af-season">${escapeHtml(T('player.selectSeason'))}</label>
                        <select id="af-season"><option value="">${escapeHtml(T('player.pleaseSelect'))}</option></select>
                    </div>
                    <div class="filter-group" id="af-week-season-filter" style="display:none;">
                        <label for="af-week-season-select">${escapeHtml(T('player.selectSeason'))}</label>
                        <select id="af-week-season-select"><option value="">${escapeHtml(T('player.pleaseSelect'))}</option></select>
                    </div>
                    <div class="filter-group" id="af-week-select-filter" style="display:none;">
                        <label for="af-week">${escapeHtml(T('player.selectWeek'))}</label>
                        <select id="af-week"><option value="">${escapeHtml(T('player.pleaseSelect'))}</option></select>
                    </div>
                    <div class="filter-group" id="af-reset-btn-group" style="display:none;">
                        <label>&nbsp;</label>
                        <button class="filter-reset-btn" id="af-reset-btn" type="button">
                            <i class="fas fa-times-circle"></i> ${escapeHtml(T('player.resetFilters'))}
                        </button>
                    </div>
                </div>`;
        }

        function reloadActiveView() {
            cache.clear();
            if (panelMode === 'squadron' && activeSquadron && activeView) loadView(activeView);
            else if (panelMode === 'player' && activePlayerUid && activeView) loadPlayerView(activeView);
            else if (panelMode === 'vehicle' && activeVehicle && activeView) loadVehicleView(activeView);
        }

        function resetResults() {
            activeSquadron = null;
            activePlayerUid = null;
            activePlayerNick = null;
            activeVehicle = null;
            activeVehicleInternal = null;
            panelMode = null;
            cache.clear();
            resultsRoot.innerHTML = `
                <div class="leaderboard-content">
                    <div class="empty-state" id="emptyHint">
                        <i class="fas fa-arrow-up" style="font-size:1.5rem; margin-bottom:0.75rem; display:block;"></i>
                        <span id="emptyHintText">${escapeHtml(T(hintMsgs[currentMode]))}</span>
                    </div>
                </div>`;
        }

        function renderSquadronPanel(sqShort) {
            panelMode = 'squadron';
            activeSquadron = sqShort;
            activePlayerUid = null;
            activeView = 'maps';
            dateFilter = { type: 'all', startTs: 0, endTs: 0 };
            resultsRoot.innerHTML = `
                ${entityHeaderHtml('squadron', sqShort, '/squadrons/' + encodeURIComponent(sqShort))}
                <div class="leaderboard-nav" id="analyticsTabs">
                    ${VIEWS.map(v => `
                        <button class="leaderboard-tab ${v.key === 'maps' ? 'active' : ''}" data-view="${v.key}">
                            <i class="fas fa-${v.icon}"></i> ${escapeHtml(T(v.label))}
                        </button>`).join('')}
                </div>
                <div class="leaderboard-content" id="analyticsViewSlot"></div>`;
            document.querySelectorAll('#analyticsTabs .leaderboard-tab').forEach(btn => {
                btn.addEventListener('click', () => switchView(btn.dataset.view));
            });
            wireDateFilter();
            switchView('maps');
        }

        function renderPlayerPanel(uid, nick) {
            panelMode = 'player';
            activeSquadron = null;
            activePlayerUid = uid;
            activePlayerNick = nick;
            activeView = 'maps';
            dateFilter = { type: 'all', startTs: 0, endTs: 0 };
            resultsRoot.innerHTML = `
                ${entityHeaderHtml('player', nick, '/players/' + encodeURIComponent(uid))}
                <div class="leaderboard-nav" id="analyticsTabs">
                    ${PLAYER_VIEWS.map(v => `
                        <button class="leaderboard-tab ${v.key === 'maps' ? 'active' : ''}" data-view="${v.key}">
                            <i class="fas fa-${v.icon}"></i> ${escapeHtml(T(v.label))}
                        </button>`).join('')}
                </div>
                <div class="leaderboard-content" id="analyticsViewSlot"></div>`;
            document.querySelectorAll('#analyticsTabs .leaderboard-tab').forEach(btn => {
                btn.addEventListener('click', () => playerSwitchView(btn.dataset.view));
            });
            wireDateFilter();
            playerSwitchView('maps');
        }

        // Lookup helper: short_name (e.g. "DSPL") → full glyph-wrapped tag
        // ("⫨DSPL⌐" rendered as a clan emblem in skyquakesymbols).
        // Falls back to the short name if we don't have a mapping yet.
        function squadronTagDisplay(shortName) {
            if (!shortName) return '';
            const match = allSquadrons.find(s => s.short_name === shortName);
            return match && match.name ? match.name : shortName;
        }

        // Makes any <table> sortable by header click. Respects per-<th>
        // data-sortable="false" (skips) and data-sort-type="text"/"num" (forces).
        // Rows carrying .section-divider-row are held in place; useful for time
        // table where NA/EU dividers shouldn't move.
        function makeTableSortable(table) {
            if (!table) return;
            const thead = table.querySelector('thead tr');
            const tbody = table.querySelector('tbody');
            if (!thead || !tbody) return;
            const ths = Array.from(thead.children);
            ths.forEach((th, idx) => {
                if (th.dataset.sortable === 'false') return;
                th.classList.add('sortable-th');
                if (!th.querySelector('.sort-ind')) {
                    th.insertAdjacentHTML('beforeend', '<span class="sort-ind"></span>');
                }
                th.addEventListener('click', () => {
                    const current = th.dataset.sortDir || '';
                    ths.forEach(h => {
                        h.removeAttribute('data-sort-dir');
                        const s = h.querySelector('.sort-ind'); if (s) s.textContent = '';
                    });
                    const next = current === 'desc' ? 'asc' : 'desc';
                    th.dataset.sortDir = next;
                    const ind = th.querySelector('.sort-ind'); if (ind) ind.textContent = next === 'asc' ? '▲' : '▼';

                    const allRows = Array.from(tbody.querySelectorAll('tr'));
                    const sortable = allRows.filter(r => !r.classList.contains('section-divider-row'));
                    const getVal = (tr) => {
                        const td = tr.children[idx]; if (!td) return '';
                        if (td.dataset.sortValue !== undefined) return td.dataset.sortValue;
                        return td.innerText.trim();
                    };
                    const forced = th.dataset.sortType;
                    const sample = sortable.length ? getVal(sortable[0]) : '';
                    const numeric = forced === 'num' || (forced !== 'text' && /^-?\d/.test(String(sample).replace(/[%,\s]/g, '')));
                    sortable.sort((a, b) => {
                        let av = getVal(a), bv = getVal(b);
                        if (numeric) {
                            av = parseFloat(String(av).replace(/[%,\s]/g, '')); if (Number.isNaN(av)) av = 0;
                            bv = parseFloat(String(bv).replace(/[%,\s]/g, '')); if (Number.isNaN(bv)) bv = 0;
                            return next === 'asc' ? av - bv : bv - av;
                        }
                        return next === 'asc' ? String(av).localeCompare(String(bv)) : String(bv).localeCompare(String(av));
                    });
                    // Rebuild: preserve dividers where they are by anchoring sorted
                    // rows between them (simplest: if no dividers, just append).
                    if (allRows.some(r => r.classList.contains('section-divider-row'))) {
                        // Keep dividers; within each segment, replace with sorted subset filtered to original segment rows.
                        const segments = [];
                        let current = [];
                        allRows.forEach(r => {
                            if (r.classList.contains('section-divider-row')) {
                                segments.push({ divider: r, rows: current });
                                current = [];
                            } else current.push(r);
                        });
                        segments.push({ divider: null, rows: current });
                        tbody.innerHTML = '';
                        for (const seg of segments) {
                            if (seg.divider) tbody.appendChild(seg.divider);
                            const segSet = new Set(seg.rows);
                            sortable.filter(r => segSet.has(r)).forEach(r => tbody.appendChild(r));
                        }
                    } else {
                        sortable.forEach(r => tbody.appendChild(r));
                    }
                });
            });
        }

        // Returns a function that maps a value to a 0–100 bar width based on
        // the min and max of the input set, so tightly-bunched values (e.g. all
        // K/D in 8.0–10.0) still produce visibly distinct bar lengths.
        function relativeBarScaler(values) {
            const lo = Math.min(...values);
            const hi = Math.max(...values);
            const span = hi - lo;
            const FLOOR = 8;
            return (v) => {
                if (span <= 0.001) return 50;
                const pct = ((v - lo) / span) * (100 - FLOOR) + FLOOR;
                return Math.max(0, Math.min(100, pct));
            };
        }

        const VEHICLE_VIEWS = [
            { key: 'players',   label: 'analytics.tabTopPlayers',   icon: 'user' },
            { key: 'squadrons', label: 'analytics.tabTopSquadrons', icon: 'shield-halved' },
            { key: 'maps',      label: 'analytics.tabMaps',         icon: 'map' },
        ];

        function renderVehiclePanel(name) {
            panelMode = 'vehicle';
            activeSquadron = null;
            activePlayerUid = null;
            activeView = 'players';
            dateFilter = { type: 'all', startTs: 0, endTs: 0 };
            // pickVehicle is the only legitimate caller; activeVehicleInternal
            // is already set by the time we get here.
            resultsRoot.innerHTML = `
                ${entityHeaderHtml('vehicle', name, null)}
                <div id="vehicleStatStrip" class="player-stat-strip" style="margin-bottom:1rem;"></div>
                <div class="leaderboard-nav" id="analyticsTabs">
                    ${VEHICLE_VIEWS.map(v => `
                        <button class="leaderboard-tab ${v.key === 'players' ? 'active' : ''}" data-view="${v.key}">
                            <i class="fas fa-${v.icon}"></i> ${escapeHtml(T(v.label))}
                        </button>`).join('')}
                </div>
                <div class="leaderboard-content" id="analyticsViewSlot"></div>`;
            document.querySelectorAll('#analyticsTabs .leaderboard-tab').forEach(btn => {
                btn.addEventListener('click', () => vehicleSwitchView(btn.dataset.view));
            });
            wireDateFilter();
            // vehicleSwitchView → loadVehicleView fires loadVehicleStats() itself,
            // so don't double-fire the /vehicle/stats query.
            vehicleSwitchView('players');
        }

        function vehicleSwitchView(view) {
            activeView = view;
            document.querySelectorAll('#analyticsTabs .leaderboard-tab').forEach(btn => {
                btn.classList.toggle('active', btn.dataset.view === view);
            });
            loadVehicleView(view);
        }

        async function loadVehicleStats() {
            const strip = document.getElementById('vehicleStatStrip');
            if (!strip || !activeVehicleInternal) return;
            const cacheKey = `vehicle:${activeVehicleInternal}|stats|${filterKey()}`;
            try {
                let s = cache.get(cacheKey);
                if (!s) {
                    s = await window.apiClient.request(`/api/analytics/vehicle/stats/${encodeURIComponent(activeVehicleInternal)}${filterQueryString()}`);
                    cache.set(cacheKey, s);
                }
                strip.innerHTML = `
                    <div><span class="ps-label">Spawns</span><span class="ps-val">${s.spawns}</span></div>
                    <div><span class="ps-label">Players</span><span class="ps-val">${s.unique_players}</span></div>
                    <div><span class="ps-label">Sessions</span><span class="ps-val">${s.sessions}</span></div>
                    <div><span class="ps-label">K/D</span><span class="ps-val">${s.kd.toFixed(2)}</span></div>
                    <div><span class="ps-label">K/Spawn</span><span class="ps-val">${s.ks.toFixed(2)}</span></div>
                    <div><span class="ps-label">Win Rate</span><span class="ps-val">${s.win_rate}%</span></div>`;
            } catch (e) {
                console.error('vehicle stats failed', e);
                strip.innerHTML = '';
            }
        }

        async function loadVehicleView(view) {
            const slot = document.getElementById('analyticsViewSlot');
            if (!activeVehicleInternal || !slot) return;
            const cacheKey = `vehicle:${activeVehicleInternal}|${view}|${filterKey()}`;
            // Stat strip refreshes alongside whichever tab the user lands on.
            loadVehicleStats();

            if (cache.has(cacheKey)) {
                renderVehicleView(view, cache.get(cacheKey), slot);
                return;
            }
            slot.innerHTML = `<div class="loading-state"><div class="loading-spinner"></div>${escapeHtml(T('analytics.loading'))}</div>`;
            try {
                const data = await window.apiClient.request(`/api/analytics/vehicle/${view}/${encodeURIComponent(activeVehicleInternal)}${filterQueryString()}`);
                cache.set(cacheKey, data);
                if (activeView === view) renderVehicleView(view, data, slot);
            } catch (e) {
                console.error(e);
                slot.innerHTML = `<div class="error-state"><i class="fas fa-exclamation-triangle"></i> ${escapeHtml(T('analytics.loadError'))}</div>`;
            }
        }

        function renderVehicleView(view, data, slot) {
            if (!Array.isArray(data) || !data.length) {
                slot.innerHTML = `<div class="empty-state"><i class="fas fa-inbox" style="font-size:1.5rem; display:block; margin-bottom:0.5rem;"></i>${escapeHtml(T('analytics.noData'))}</div>`;
                return;
            }
            switch (view) {
                case 'players':   return renderVehiclePlayers(slot, data);
                case 'squadrons': return renderVehicleSquadrons(slot, data);
                case 'maps':      return renderMaps(slot, data);
            }
        }

        function renderVehiclePlayers(slot, data) {
            const kdScale = relativeBarScaler(data.map(r => r.kd));
            const rows = data.map((r, i) => `
                <tr>
                    <td>${i + 1}</td>
                    <td data-sort-value="${escapeHtml((r.nick || '').toLowerCase())}">
                        ${r.squadron ? `<a href="/squadrons/${encodeURIComponent(r.squadron)}" class="sq-tag" style="text-decoration:none; opacity:0.85; margin-right:0.45rem;">${escapeHtml(squadronTagDisplay(r.squadron))}</a>` : ''}
                        <a href="/players/${encodeURIComponent(r.uid)}" style="color:#A8E6CF; text-decoration:none;">${escapeHtml(r.nick || '')}</a>
                    </td>
                    <td data-sort-value="${r.games}">${r.games}</td>
                    <td data-sort-value="${r.kd}" style="white-space:nowrap;">${r.kd.toFixed(2)}<span class="micro-bar kd"><span style="width:${kdScale(r.kd)}%"></span></span></td>
                    <td data-sort-value="${r.win_rate}" style="white-space:nowrap;">${r.win_rate}%<span class="micro-bar"><span style="width:${Math.min(100, r.win_rate)}%"></span></span></td>
                </tr>`).join('');
            slot.innerHTML = `
                <div class="consistency-table-wrap">
                    <table class="analytics-table">
                        <thead><tr>
                            <th data-sortable="false">#</th>
                            <th data-sort-type="text">${escapeHtml(T('analytics.colPlayer'))}</th>
                            <th data-sort-type="num">${escapeHtml(T('analytics.colGames'))}</th>
                            <th data-sort-type="num">K/D</th>
                            <th data-sort-type="num">${escapeHtml(T('analytics.colWinRate'))}</th>
                        </tr></thead>
                        <tbody>${rows}</tbody>
                    </table>
                </div>`;
            makeTableSortable(slot.querySelector('table'));
        }

        function renderVehicleSquadrons(slot, data) {
            const kdScale = relativeBarScaler(data.map(r => r.kd));
            const rows = data.map((r, i) => `
                <tr>
                    <td>${i + 1}</td>
                    <td data-sort-value="${escapeHtml((r.squadron || '').toLowerCase())}"><a href="/squadrons/${encodeURIComponent(r.squadron)}" class="sq-tag" style="text-decoration:none;">${escapeHtml(squadronTagDisplay(r.squadron))}</a></td>
                    <td data-sort-value="${r.games}">${r.games}</td>
                    <td data-sort-value="${r.kd}" style="white-space:nowrap;">${r.kd.toFixed(2)}<span class="micro-bar kd"><span style="width:${kdScale(r.kd)}%"></span></span></td>
                    <td data-sort-value="${r.win_rate}" style="white-space:nowrap;">${r.win_rate}%<span class="micro-bar"><span style="width:${Math.min(100, r.win_rate)}%"></span></span></td>
                </tr>`).join('');
            slot.innerHTML = `
                <div class="consistency-table-wrap">
                    <table class="analytics-table">
                        <thead><tr>
                            <th data-sortable="false">#</th>
                            <th data-sort-type="text">${escapeHtml(T('analytics.colSquadron'))}</th>
                            <th data-sort-type="num">${escapeHtml(T('analytics.colGames'))}</th>
                            <th data-sort-type="num">K/D</th>
                            <th data-sort-type="num">${escapeHtml(T('analytics.colWinRate'))}</th>
                        </tr></thead>
                        <tbody>${rows}</tbody>
                    </table>
                </div>`;
            makeTableSortable(slot.querySelector('table'));
        }

        function playerSwitchView(view) {
            activeView = view;
            document.querySelectorAll('#analyticsTabs .leaderboard-tab').forEach(btn => {
                btn.classList.toggle('active', btn.dataset.view === view);
            });
            loadPlayerView(view);
        }

        async function loadPlayerView(view) {
            const slot = document.getElementById('analyticsViewSlot');
            if (!activePlayerUid || !slot) return;
            const cacheKey = `player:${activePlayerUid}|${view}|${filterKey()}`;

            const renderShell = (inner) => { slot.innerHTML = inner; };

            if (cache.has(cacheKey)) {
                const data = cache.get(cacheKey);
                renderShell('<div id="playerViewBody"></div>');
                renderPlayerView(view, data, document.getElementById('playerViewBody'));
                return;
            }

            renderShell(`<div class="loading-state"><div class="loading-spinner"></div>${escapeHtml(T('analytics.loading'))}</div>`);
            try {
                const data = await window.apiClient.request(`/api/analytics/player/${view}/${encodeURIComponent(activePlayerUid)}${filterQueryString()}`);
                cache.set(cacheKey, data);
                if (activeView === view) {
                    renderShell('<div id="playerViewBody"></div>');
                    renderPlayerView(view, data, document.getElementById('playerViewBody'));
                }
            } catch (e) {
                console.error(e);
                renderShell(`<div class="error-state"><i class="fas fa-exclamation-triangle"></i> ${escapeHtml(T('analytics.loadError'))}</div>`);
            }
        }

        function renderPlayerView(view, data, slot) {
            // Player time endpoint returns { hourly, daily }; everything else
            // matches the squadron-view shape.
            const empty = view === 'time'
                ? (!data || !data.hourly || Object.keys(data.hourly).length === 0)
                : Array.isArray(data) ? data.length === 0
                : (view === 'matchup' ? (!data.won_against || (!data.won_against.length && !data.lost_against.length))
                : false);
            if (empty) {
                slot.innerHTML = `<div class="empty-state"><i class="fas fa-inbox" style="font-size:1.5rem; display:block; margin-bottom:0.5rem;"></i>${escapeHtml(T('analytics.noData'))}</div>`;
                return;
            }
            switch (view) {
                case 'maps':     return renderMaps(slot, data);
                case 'squadmates': return renderPlayerSquadmates(slot, data);
                case 'time':     return renderTime(slot, data);
                case 'matchup':  return renderMatchup(slot, data);
                case 'timeline': return renderPlayerTimeline(slot, data);
            }
        }

        let playerTimelineLineChart = null;
        let playerVehicleBarChart = null;
        let playerSquadmatesRadarChart = null;

        function renderPlayerSquadmates(slot, data) {
            if (playerSquadmatesRadarChart) {
                try { playerSquadmatesRadarChart.destroy(); } catch (e) {}
                playerSquadmatesRadarChart = null;
            }

            const RADAR_MIN_SHARED = (dateFilter.type === 'week' || dateFilter.type === 'date') ? 1 : 2;
            const RADAR_MAX_AXES = 20;
            const eligible = data.filter(r => r.shared >= RADAR_MIN_SHARED);
            const radarData = eligible
                .slice()
                .sort((a, b) => b.shared - a.shared)
                .slice(0, RADAR_MAX_AXES)
                .sort((a, b) => (a.nick || a.uid || '').localeCompare(b.nick || b.uid || ''));
            const sharedScale = relativeBarScaler(data.map(r => r.shared || 0));
            const maxShared = radarData.reduce((m, r) => Math.max(m, r.shared || 0), 0) || 1;
            const truncate = (s, n) => s.length > n ? s.slice(0, n - 1).trimEnd() + '…' : s;
            const rows = data.map((r, i) => `
                <tr>
                    <td class="col-map" data-sort-value="${escapeHtml((r.nick || r.uid || '').toLowerCase())}">
                        <a href="/players/${encodeURIComponent(r.uid)}" class="sqmate-link">${escapeHtml(r.nick || r.uid || '')}</a>
                        <span class="sqmate-meta">${escapeHtml(T('analytics.colUid'))}: ${escapeHtml(r.uid)}</span>
                    </td>
                    <td class="col-num" data-sort-value="${r.shared}">${r.shared}</td>
                    <td class="col-num" data-sort-value="${r.wins}" style="color:#90EE90">${r.wins}</td>
                    <td class="col-num" data-sort-value="${r.losses}" style="color:#ff9b9b">${r.losses}</td>
                    <td class="col-num" data-sort-value="${r.win_rate}">${r.win_rate}%</td>
                    <td class="col-bar"><div class="bar-track"><div class="bar-fill" style="width:${sharedScale(r.shared)}%"></div></div></td>
                </tr>`).join('');
            const excludedCount = data.length - radarData.length;

            slot.innerHTML = `
                <div class="maps-layout">
                    <div class="maps-table-wrap">
                        <table class="analytics-table maps-table squadmates-table">
                            <thead><tr>
                                <th class="col-map" data-sort-type="text">${escapeHtml(T('analytics.colPlayer'))}</th>
                                <th class="col-num" data-sort-type="num">${escapeHtml(T('analytics.colShared'))}</th>
                                <th class="col-num" data-sort-type="num">${escapeHtml(T('analytics.colWins'))}</th>
                                <th class="col-num" data-sort-type="num">${escapeHtml(T('analytics.colLosses'))}</th>
                                <th class="col-num" data-sort-type="num">${escapeHtml(T('analytics.colWinRate'))}</th>
                                <th class="col-bar" data-sortable="false">${escapeHtml(T('analytics.colBar'))}</th>
                            </tr></thead>
                            <tbody>${rows}</tbody>
                        </table>
                    </div>
                    <div class="radar-card">
                        <div class="radar-card-header">
                            <span class="radar-card-title">${escapeHtml(T('analytics.tabSquadmates'))}</span>
                            <span class="radar-card-meta">${escapeHtml(T('analytics.radarMetaSquadmates', { shown: radarData.length, total: data.length, min: RADAR_MIN_SHARED }))}</span>
                        </div>
                        ${radarData.length < 3 ? `
                            <div class="radar-canvas-wrap" style="display:flex; align-items:center; justify-content:center; text-align:center; color:rgba(245,245,220,0.55); padding:1.5rem;">
                                ${escapeHtml(T('analytics.radarTooFewSquadmates', { min: RADAR_MIN_SHARED }))}
                            </div>` : `
                            <div class="radar-canvas-wrap">
                                <canvas id="playerSquadmatesRadarCanvas" style="cursor:pointer;"></canvas>
                            </div>
                            <div class="radar-legend">
                                <span><span class="swatch" style="background:#90EE90"></span>${escapeHtml(T('analytics.colShared'))}</span>
                                <span><span class="swatch" style="background:#F5C16C"></span>${escapeHtml(T('analytics.colWinRate'))}</span>
                            </div>`}
                        ${excludedCount > 0 ? `<div class="radar-footnote">${escapeHtml(T('analytics.radarFootnoteSquadmates', { count: excludedCount }))}</div>` : ''}
                    </div>
                </div>`;

            makeTableSortable(slot.querySelector('.squadmates-table'));

            if (radarData.length < 3 || typeof Chart === 'undefined') return;

            const labels = radarData.map(r => truncate(r.nick || r.uid || '', 16));
            const sharedSeries = radarData.map(r => Math.round((r.shared / maxShared) * 100));
            const wrSeries = radarData.map(r => r.win_rate);

            const ctx = document.getElementById('playerSquadmatesRadarCanvas').getContext('2d');
            playerSquadmatesRadarChart = new Chart(ctx, {
                type: 'radar',
                data: {
                    labels,
                    datasets: [
                        {
                            label: T('analytics.colShared'),
                            data: sharedSeries,
                            backgroundColor: 'rgba(144, 238, 144, 0.22)',
                            borderColor: 'rgba(144, 238, 144, 0.95)',
                            borderWidth: 2,
                            pointBackgroundColor: '#90EE90',
                            pointBorderColor: '#1b1b1b',
                            pointRadius: 3,
                            pointHoverRadius: 5,
                        },
                        {
                            label: T('analytics.colWinRate'),
                            data: wrSeries,
                            backgroundColor: 'rgba(245, 193, 108, 0.10)',
                            borderColor: 'rgba(245, 193, 108, 0.85)',
                            borderWidth: 1.5,
                            borderDash: [4, 4],
                            pointBackgroundColor: '#F5C16C',
                            pointBorderColor: '#1b1b1b',
                            pointRadius: 2,
                            pointHoverRadius: 4,
                        },
                    ],
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    animation: { duration: 600 },
                    onClick: (event, activeElements, chart) => {
                        const item = (activeElements && activeElements[0]) || (chart && typeof chart.getElementsAtEventForMode === 'function'
                            ? chart.getElementsAtEventForMode(event, 'nearest', { intersect: true }, true)[0]
                            : null);
                        if (!item) return;
                        const target = radarData[item.index];
                        if (target && target.uid) {
                            window.location.href = `/players/${encodeURIComponent(target.uid)}`;
                        }
                    },
                    plugins: {
                        legend: { display: false },
                        tooltip: {
                            backgroundColor: 'rgba(15, 20, 15, 0.95)',
                            borderColor: 'rgba(144, 238, 144, 0.3)',
                            borderWidth: 1,
                            titleColor: '#F5F5DC',
                            bodyColor: '#fff',
                            padding: 10,
                            callbacks: {
                                title: (items) => {
                                    const r = radarData[items[0]?.dataIndex ?? 0];
                                    return r ? `${r.nick || r.uid} · UID ${r.uid}` : '';
                                },
                                label: (item) => {
                                    const r = radarData[item.dataIndex];
                                    if (item.datasetIndex === 0) {
                                        return `${T('analytics.colShared')}: ${r.shared}`;
                                    }
                                    return `${T('analytics.colWinRate')}: ${r.win_rate}%  (${r.wins}W / ${r.losses}L)`;
                                },
                            },
                        },
                    },
                    scales: {
                        r: {
                            min: 0,
                            max: 100,
                            ticks: {
                                stepSize: 25,
                                color: 'rgba(255,255,255,0.4)',
                                backdropColor: 'transparent',
                                font: { size: 9 },
                                callback: (v) => v + '%',
                            },
                            angleLines: { color: 'rgba(255,255,255,0.1)' },
                            grid: { color: 'rgba(255,255,255,0.08)' },
                            pointLabels: {
                                color: '#F5F5DC',
                                font: { size: 10, weight: '600' },
                                callback: (label) => label,
                            },
                        },
                    },
                },
            });
        }

        function renderPlayerTimeline(slot, rawRows) {
            if (playerTimelineLineChart) { try { playerTimelineLineChart.destroy(); } catch (e) {} playerTimelineLineChart = null; }
            if (playerVehicleBarChart)   { try { playerVehicleBarChart.destroy();   } catch (e) {} playerVehicleBarChart   = null; }

            // ── Aggregate per session for K/D + W/L lines ────────────────
            const sessionMap = new Map();
            for (const r of rawRows) {
                let s = sessionMap.get(r.session_id);
                if (!s) {
                    s = { session_id: r.session_id, t: r.endtime_unix * 1000, kills: 0, deaths: 0, won: r.won };
                    sessionMap.set(r.session_id, s);
                }
                s.kills  += r.kills  || 0;
                s.deaths += r.deaths || 0;
                if (r.won !== null && r.won !== undefined) s.won = r.won;
                s.t = Math.max(s.t, r.endtime_unix * 1000);
            }
            const sessions = [...sessionMap.values()].sort((a, b) => a.t - b.t);

            // ── K/D & WR line chart still uses an adaptive bucket so the
            // ── line doesn't become noise on long ranges. Vehicle bars are
            // ── always daily (handled separately below).
            const tMin = sessions.length ? sessions[0].t : Date.now();
            const tMax = sessions.length ? sessions[sessions.length - 1].t : Date.now();
            const spanMs = Math.max(1, tMax - tMin);
            const day = 86400000;
            let bucketMs;
            if (spanMs <= 14 * day)        bucketMs = day;        // daily
            else if (spanMs <= 90 * day)   bucketMs = 7 * day;    // weekly
            else if (spanMs <= 540 * day)  bucketMs = 14 * day;   // bi-weekly
            else                           bucketMs = 30 * day;   // monthly
            const bucketStart = (t) => Math.floor((t - tMin) / bucketMs) * bucketMs + tMin;
            const dayStart = (t) => {
                const d = new Date(t);
                return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
            };

            // ── Per-bucket aggregates: K/D, WR, K/S (kills per spawn) ────
            // Each row in rawRows == one vehicle == one spawn.
            const bucketAgg = new Map();
            for (const s of sessions) {
                const b = bucketStart(s.t);
                let agg = bucketAgg.get(b);
                if (!agg) { agg = { t: b, k: 0, d: 0, w: 0, l: 0, spawns: 0 }; bucketAgg.set(b, agg); }
                agg.k += s.kills; agg.d += s.deaths;
                if (s.won === 1) agg.w++; else if (s.won === 0) agg.l++;
            }
            for (const r of rawRows) {
                const b = bucketStart(r.endtime_unix * 1000);
                const agg = bucketAgg.get(b);
                if (agg) agg.spawns += 1;
            }
            const bucketRows = [...bucketAgg.values()].sort((a, b) => a.t - b.t);
            const kdSeries = bucketRows.map(b => ({ x: b.t, y: b.d > 0 ? Math.round((b.k / b.d) * 100) / 100 : b.k }));
            const ksSeries = bucketRows.map(b => ({ x: b.t, y: b.spawns > 0 ? Math.round((b.k / b.spawns) * 100) / 100 : 0 }));
            const wrSeries = bucketRows.map(b => {
                const tot = b.w + b.l;
                return { x: b.t, y: tot > 0 ? Math.round((b.w / tot) * 1000) / 10 : null };
            });

            // ── Daily vehicle stack: top 10 vehicles PER DAY ─────────────
            // First aggregate raw counts per (day, vehicle), then for each day
            // keep only that day's top 10. The dataset list is the union of
            // any vehicle that made any day's top-10 cut.
            const PER_DAY_LIMIT = 10;
            const dayRaw = new Map(); // day -> { vehName: count }
            for (const r of rawRows) {
                if (!r.vehicle) continue;
                const b = dayStart(r.endtime_unix * 1000);
                let m = dayRaw.get(b);
                if (!m) { m = {}; dayRaw.set(b, m); }
                m[r.vehicle] = (m[r.vehicle] || 0) + 1;
            }
            const bucketVeh = new Map(); // day -> { vehName: count }   (capped)
            const keptVehicles = new Set();
            for (const [day, counts] of dayRaw) {
                const ranked = Object.entries(counts).sort((a, b) => b[1] - a[1]).slice(0, PER_DAY_LIMIT);
                const kept = {};
                for (const [veh, c] of ranked) { kept[veh] = c; keptVehicles.add(veh); }
                bucketVeh.set(day, kept);
            }
            const vehBuckets = [...bucketVeh.keys()].sort((a, b) => a - b);

            // Order legend by overall usage across the kept set.
            const keptTotals = new Map();
            for (const counts of bucketVeh.values()) {
                for (const [v, c] of Object.entries(counts)) {
                    keptTotals.set(v, (keptTotals.get(v) || 0) + c);
                }
            }
            const vehLabels = [...keptTotals.entries()].sort((a, b) => b[1] - a[1]).map(e => e[0]);

            // Pre-compute hue per vehicle. Colors are resolved at draw time
            // via scriptable callbacks below — toggling highlight only flips
            // a single state variable instead of rewriting N×M dataset arrays.
            const vehHue = new Map(vehLabels.map((v, i) => [v, Math.round((i * 360) / Math.max(1, vehLabels.length))]));
            const vehDatasets = vehLabels.map((veh) => ({
                label: veh,
                data: vehBuckets.map(b => (bucketVeh.get(b)[veh] || 0)),
                borderWidth: 0,
                stack: 'veh',
            }));

            // ── Stat strip totals ────────────────────────────────────────
            const wins = sessions.filter(s => s.won === 1).length;
            const losses = sessions.filter(s => s.won === 0).length;
            const wr = (wins + losses) > 0 ? Math.round(wins / (wins + losses) * 1000) / 10 : 0;
            const totalK = sessions.reduce((s, x) => s + x.kills, 0);
            const totalD = sessions.reduce((s, x) => s + x.deaths, 0);
            const overallKd = totalD > 0 ? Math.round((totalK / totalD) * 100) / 100 : totalK;
            const bucketLabel = bucketMs === day ? 'daily' : bucketMs === 7 * day ? 'weekly' : bucketMs === 14 * day ? 'bi-weekly' : 'monthly';

            slot.innerHTML = `
                <div class="player-stat-strip">
                    <div><span class="ps-label">Sessions</span><span class="ps-val">${sessions.length}</span></div>
                    <div><span class="ps-label">Wins</span><span class="ps-val" style="color:#90EE90">${wins}</span></div>
                    <div><span class="ps-label">Losses</span><span class="ps-val" style="color:#ff9b9b">${losses}</span></div>
                    <div><span class="ps-label">Win Rate</span><span class="ps-val">${wr}%</span></div>
                    <div><span class="ps-label">Total K</span><span class="ps-val">${totalK}</span></div>
                    <div><span class="ps-label">Total D</span><span class="ps-val">${totalD}</span></div>
                    <div><span class="ps-label">Overall K/D</span><span class="ps-val">${overallKd.toFixed(2)}</span></div>
                </div>
                <div class="timeline-card">
                    <div class="timeline-card-header">
                        <span class="timeline-card-title">K/D · K/S · Win Rate</span>
                        <span class="timeline-card-meta">${bucketLabel} buckets</span>
                    </div>
                    <div class="timeline-canvas-wrap"><canvas id="playerKdWrCanvas"></canvas></div>
                </div>
                <div class="timeline-card" style="margin-top:1rem;">
                    <div class="timeline-card-header">
                        <span class="timeline-card-title">Vehicle performance</span>
                        <span class="timeline-card-meta">x = matches · y = K/D · size = WR</span>
                    </div>
                    <div class="timeline-canvas-wrap"><canvas id="playerVehicleBubbleCanvas"></canvas></div>
                </div>
                <div class="timeline-card" style="margin-top:1rem;">
                    <div class="timeline-card-header">
                        <span class="timeline-card-title">Vehicle usage</span>
                        <span class="timeline-card-meta">daily · top 10 per day · ${vehLabels.length} unique · click legend to highlight</span>
                    </div>
                    <div class="timeline-canvas-wrap"><canvas id="playerVehicleCanvas"></canvas></div>
                </div>
                `;

            if (typeof Chart === 'undefined' || !sessions.length) return;

            // ── Dual-axis line chart ─────────────────────────────────────
            const lineCtx = document.getElementById('playerKdWrCanvas').getContext('2d');
            playerTimelineLineChart = new Chart(lineCtx, {
                type: 'line',
                data: {
                    datasets: [
                        {
                            label: 'K/D',
                            data: kdSeries,
                            borderColor: 'rgba(144, 238, 144, 0.95)',
                            backgroundColor: 'rgba(144, 238, 144, 0.12)',
                            borderWidth: 2,
                            pointRadius: 2.5,
                            pointHoverRadius: 5,
                            tension: 0.3,
                            yAxisID: 'yKd',
                            fill: false,
                        },
                        {
                            label: 'K/S',
                            data: ksSeries,
                            borderColor: 'rgba(168, 200, 230, 0.95)',
                            backgroundColor: 'rgba(168, 200, 230, 0.0)',
                            borderWidth: 2,
                            borderDash: [2, 3],
                            pointRadius: 2,
                            pointHoverRadius: 4,
                            tension: 0.3,
                            yAxisID: 'yKd',
                            fill: false,
                        },
                        {
                            label: 'Win Rate %',
                            data: wrSeries,
                            borderColor: 'rgba(245, 193, 108, 0.95)',
                            backgroundColor: 'rgba(245, 193, 108, 0.0)',
                            borderWidth: 2,
                            borderDash: [5, 4],
                            pointRadius: 2.5,
                            pointHoverRadius: 5,
                            tension: 0.3,
                            yAxisID: 'yWr',
                            spanGaps: true,
                            fill: false,
                        },
                    ],
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    animation: false,
                    // Only fire when the cursor is actually on a data point.
                    interaction: { mode: 'nearest', intersect: true, axis: 'xy' },
                    hover: { mode: 'nearest', intersect: true },
                    plugins: {
                        legend: { display: true, labels: { color: 'rgba(245,245,220,0.85)', font: { size: 11 } } },
                        tooltip: {
                            animation: false,
                            mode: 'nearest',
                            intersect: true,
                            backgroundColor: 'rgba(15,20,15,0.95)', borderColor: 'rgba(144,238,144,0.3)',
                            borderWidth: 1, titleColor: '#F5F5DC', bodyColor: '#fff', padding: 10,
                            callbacks: {
                                title: (items) => new Date(items[0].parsed.x).toISOString().slice(0, 10),
                                label: (item) => {
                                    const v = item.parsed.y;
                                    if (v === null) return null;
                                    if (item.dataset.label === 'Win Rate %') return `WR: ${v}%`;
                                    return `${item.dataset.label}: ${v.toFixed(2)}`;
                                },
                            },
                        },
                    },
                    scales: {
                        x: {
                            type: 'linear',
                            ticks: { color: 'rgba(255,255,255,0.5)', callback: (v) => new Date(v).toISOString().slice(0, 10) },
                            grid: { color: 'rgba(144,238,144,0.06)' },
                        },
                        yKd: {
                            type: 'linear',
                            position: 'left',
                            // Lock the K/D axis bounds so toggling the dataset
                            // doesn't rescale. Span K/D and K/S together.
                            min: 0,
                            max: Math.max(1, Math.ceil(Math.max(0, ...kdSeries.map(p => p.y), ...ksSeries.map(p => p.y)) * 1.15 * 10) / 10),
                            title: { display: true, text: 'K/D · K/S', color: 'rgba(144,238,144,0.85)' },
                            ticks: { color: 'rgba(144,238,144,0.7)' },
                            grid: { color: 'rgba(144,238,144,0.08)' },
                        },
                        yWr: {
                            type: 'linear',
                            position: 'right',
                            min: 0, max: 100,
                            title: { display: true, text: 'WR %', color: 'rgba(245,193,108,0.85)' },
                            ticks: { color: 'rgba(245,193,108,0.7)', stepSize: 25, callback: (v) => v + '%' },
                            grid: { drawOnChartArea: false },
                        },
                    },
                },
            });

            // ── Stacked daily vehicle bar ────────────────────────────────
            if (vehBuckets.length) {
                // Single shared state — scriptable color callbacks read it on
                // each draw, so toggling is one var write + one redraw.
                const highlightState = { veh: null };
                const bgFor = (ctx) => {
                    const ds = ctx.dataset; if (!ds) return 'hsla(0,0%,50%,0)';
                    const hue = vehHue.get(ds.label) ?? 0;
                    if (highlightState.veh === null) return `hsla(${hue},65%,60%,0.78)`;
                    if (ds.label === highlightState.veh) return `hsla(${hue},65%,60%,0.92)`;
                    return `hsla(${hue},65%,60%,0.06)`;
                };
                const bdFor = (ctx) => {
                    const ds = ctx.dataset; if (!ds) return 'transparent';
                    if (highlightState.veh && ds.label === highlightState.veh) return '#ffffff';
                    return 'transparent';
                };
                const bwFor = (ctx) => {
                    const ds = ctx.dataset; if (!ds) return 0;
                    return (highlightState.veh && ds.label === highlightState.veh) ? 1.5 : 0;
                };

                // Apply scriptable callbacks once on the dataset config.
                vehDatasets.forEach(ds => {
                    ds.backgroundColor = bgFor;
                    ds.borderColor     = bdFor;
                    ds.borderWidth     = bwFor;
                });

                const applyHighlight = (chart) => {
                    // Scriptable callbacks resolve from highlightState on draw,
                    // so just trigger a paint — no per-dataset mutation needed.
                    chart.update('none');
                };

                const barCtx = document.getElementById('playerVehicleCanvas').getContext('2d');
                playerVehicleBarChart = new Chart(barCtx, {
                    type: 'bar',
                    data: {
                        labels: vehBuckets.map(b => new Date(b).toISOString().slice(0, 10)),
                        datasets: vehDatasets,
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        animation: false,
                        // Cursor must intersect a real bar segment, but tooltip
                        // shows the full stack for that day (index mode).
                        interaction: { mode: 'index', intersect: true, axis: 'x' },
                        hover: { mode: 'index', intersect: true, axis: 'x' },
                        // Pack bars edge-to-edge so we can show as many days as fit.
                        categoryPercentage: 1.0,
                        barPercentage: 0.95,
                        plugins: {
                            legend: {
                                display: true,
                                position: 'bottom',
                                labels: { color: 'rgba(245,245,220,0.85)', font: { size: 10, family: "'skyquakesymbols', 'Inter', sans-serif" }, boxWidth: 12 },
                                onClick: (_e, item, legend) => {
                                    const chart = legend && legend.chart ? legend.chart : playerVehicleBarChart;
                                    highlightState.veh = (highlightState.veh === item.text) ? null : item.text;
                                    applyHighlight(chart);
                                },
                            },
                            tooltip: {
                                enabled: true,
                                mode: 'index',
                                intersect: true,
                                animation: false,
                                backgroundColor: 'rgba(15,20,15,0.95)', borderColor: 'rgba(144,238,144,0.3)',
                                borderWidth: 1, titleColor: '#F5F5DC', bodyColor: '#fff', padding: 10,
                                bodyFont: { family: "'skyquakesymbols', 'Inter', sans-serif" },
                                // Drop the zero-value layers so we only show
                                // vehicles actually used on this day.
                                filter: (item) => item.parsed.y > 0,
                                itemSort: (a, b) => b.parsed.y - a.parsed.y,
                                callbacks: {
                                    title: (items) => {
                                        const i = items[0]?.dataIndex;
                                        return i != null ? new Date(vehBuckets[i]).toISOString().slice(0, 10) : '';
                                    },
                                    label: (item) => `${item.dataset.label}: ${item.parsed.y}`,
                                },
                            },
                        },
                        scales: {
                            x: {
                                stacked: true,
                                ticks: {
                                    color: 'rgba(255,255,255,0.5)',
                                    autoSkip: true,
                                    maxRotation: 0,
                                    autoSkipPadding: 10,
                                },
                                grid: { display: false },
                            },
                            y: {
                                stacked: true,
                                title: { display: true, text: 'Matches', color: 'rgba(245,245,220,0.7)' },
                                ticks: { color: 'rgba(255,255,255,0.5)', precision: 0 },
                                grid: { color: 'rgba(144,238,144,0.06)' },
                            },
                        },
                    },
                });
            }

            // ── Vehicle performance bubble (X=matches, Y=KD, size=WR) ────
            const sessionWon = new Map(sessions.map(s => [s.session_id, s.won]));
            const vehStats = new Map();
            for (const r of rawRows) {
                if (!r.vehicle) continue;
                let v = vehStats.get(r.vehicle);
                if (!v) { v = { name: r.vehicle, k: 0, d: 0, sessions: new Set() }; vehStats.set(r.vehicle, v); }
                v.k += r.kills || 0;
                v.d += r.deaths || 0;
                v.sessions.add(r.session_id);
            }
            const vehBubbleRows = [];
            for (const v of vehStats.values()) {
                let w = 0, l = 0;
                for (const sid of v.sessions) {
                    const won = sessionWon.get(sid);
                    if (won === 1) w++; else if (won === 0) l++;
                }
                const games = v.sessions.size;
                if (games < 3) continue; // hide noise from one-off vehicles
                vehBubbleRows.push({
                    name: v.name,
                    games,
                    kd: v.d > 0 ? Math.round((v.k / v.d) * 100) / 100 : v.k,
                    wr: (w + l) > 0 ? Math.round((w / (w + l)) * 1000) / 10 : 0,
                });
            }

            const bubbleCanvas = document.getElementById('playerVehicleBubbleCanvas');
            if (bubbleCanvas && vehBubbleRows.length) {
                const maxGames = Math.max(...vehBubbleRows.map(v => v.games));
                const maxKd = Math.max(0, ...vehBubbleRows.map(v => v.kd));
                const points = vehBubbleRows.map(v => ({
                    x: v.games,
                    y: v.kd,
                    r: 4 + (v.games / maxGames) * 12,
                    _meta: v,
                }));
                const ctx = bubbleCanvas.getContext('2d');
                new Chart(ctx, {
                    type: 'bubble',
                    data: { datasets: [{
                        data: points,
                        backgroundColor: points.map(p => `hsla(${Math.round(p._meta.wr * 1.2)}, 65%, 55%, 0.7)`),
                        borderColor:    points.map(p => `hsla(${Math.round(p._meta.wr * 1.2)}, 65%, 70%, 1)`),
                        borderWidth: 1,
                    }] },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        animation: false,
                        layout: { padding: 18 },
                        plugins: {
                            legend: { display: false },
                            tooltip: {
                                animation: false,
                                backgroundColor: 'rgba(15,20,15,0.95)', borderColor: 'rgba(144,238,144,0.3)',
                                borderWidth: 1, titleColor: '#F5F5DC', bodyColor: '#fff', padding: 10,
                                titleFont: { family: "'skyquakesymbols', 'Inter', sans-serif" },
                                callbacks: {
                                    title: (items) => points[items[0].dataIndex]._meta.name,
                                    label: (item) => {
                                        const m = points[item.dataIndex]._meta;
                                        return [`Matches: ${m.games}`, `K/D: ${m.kd.toFixed(2)}`, `WR: ${m.wr}%`];
                                    },
                                },
                            },
                        },
                        scales: {
                            x: {
                                title: { display: true, text: 'Matches played', color: 'rgba(245,245,220,0.7)' },
                                min: 0,
                                suggestedMax: Math.ceil(maxGames * 1.1),
                                ticks: { color: 'rgba(255,255,255,0.5)', precision: 0 },
                                grid: { color: 'rgba(144,238,144,0.06)' },
                            },
                            y: {
                                title: { display: true, text: 'K/D', color: 'rgba(245,245,220,0.7)' },
                                min: 0,
                                suggestedMax: Math.max(1, Math.ceil(maxKd * 1.15 * 10) / 10),
                                ticks: { color: 'rgba(255,255,255,0.5)' },
                                grid: { color: 'rgba(144,238,144,0.08)' },
                            },
                        },
                    },
                });
            }

        }

        function renderActivityCalendar(mount, dailyArr) {
            // dailyArr: [{day: utcMs, count: int}, ...] sorted ascending
            if (!dailyArr || !dailyArr.length) { mount.innerHTML = '<div class="empty-state" style="padding:1.5rem;">No activity</div>'; return; }
            const counts = new Map();
            for (const r of dailyArr) counts.set(r.day, r.count);

            // Always render entire calendar years. We span min(firstDataYear,
            // currentYear) → max(lastDataYear, currentYear) so the grid stays
            // the same size when filters narrow the data — filters change
            // cell intensity, never crop the calendar.
            const nowYear   = new Date().getUTCFullYear();
            const dataFirst = new Date(dailyArr[0].day).getUTCFullYear();
            const dataLast  = new Date(dailyArr[dailyArr.length - 1].day).getUTCFullYear();
            const firstYear = Math.min(dataFirst, nowYear);
            const lastYear  = Math.max(dataLast,  nowYear);
            const yearStart = Date.UTC(firstYear, 0, 1);
            const yearEnd   = Date.UTC(lastYear, 11, 31);
            const startDow = new Date(yearStart).getUTCDay();
            const start = yearStart - startDow * 86400000;
            const endDow = new Date(yearEnd).getUTCDay();
            const end = yearEnd + (6 - endDow) * 86400000;
            const weeks = Math.round((end - start) / 86400000 / 7) + 1;

            // Bucket → color (5 levels)
            const max = Math.max(0, ...counts.values());
            const lvl = (n) => {
                if (!n) return 0;
                if (max <= 4) return Math.min(4, n);
                if (n >= max * 0.75) return 4;
                if (n >= max * 0.50) return 3;
                if (n >= max * 0.25) return 2;
                return 1;
            };
            const palette = ['rgba(144,238,144,0.06)', 'rgba(144,238,144,0.25)', 'rgba(144,238,144,0.5)', 'rgba(144,238,144,0.75)', 'rgba(144,238,144,1)'];

            const cell = 12, gap = 3, leftPad = 28, topPad = 18, bottomPad = 4;
            const w = leftPad + weeks * (cell + gap);
            const h = topPad + 7 * (cell + gap) + bottomPad;
            const dowLabels = ['', 'Mon', '', 'Wed', '', 'Fri', ''];
            const monthLabels = [];
            let prevMonth = -1;

            let svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" style="display:block;">`;
            for (let r = 0; r < 7; r++) {
                if (dowLabels[r]) {
                    svg += `<text x="0" y="${topPad + r * (cell + gap) + cell - 2}" fill="rgba(255,255,255,0.45)" font-size="9" font-family="Inter">${dowLabels[r]}</text>`;
                }
            }
            for (let wIdx = 0; wIdx < weeks; wIdx++) {
                for (let dRow = 0; dRow < 7; dRow++) {
                    const dayMs = start + (wIdx * 7 + dRow) * 86400000;
                    if (dayMs < yearStart || dayMs > yearEnd) continue;
                    const c = counts.get(dayMs) || 0;
                    const x = leftPad + wIdx * (cell + gap);
                    const y = topPad + dRow * (cell + gap);
                    const dateStr = new Date(dayMs).toISOString().slice(0, 10);
                    svg += `<rect x="${x}" y="${y}" width="${cell}" height="${cell}" rx="2" fill="${palette[lvl(c)]}"><title>${dateStr}: ${c} game${c === 1 ? '' : 's'}</title></rect>`;
                    const m = new Date(dayMs).getUTCMonth();
                    if (dRow === 0 && m !== prevMonth) {
                        const mLabel = new Date(dayMs).toLocaleString('en-US', { month: 'short', timeZone: 'UTC' });
                        monthLabels.push({ x, label: mLabel });
                        prevMonth = m;
                    }
                }
            }
            for (const m of monthLabels) {
                svg += `<text x="${m.x}" y="${topPad - 5}" fill="rgba(255,255,255,0.55)" font-size="9" font-family="Inter">${m.label}</text>`;
            }
            svg += `</svg>`;
            mount.innerHTML = svg;
        }

        async function ensureSeasonsLoaded() {
            if (!window.seasonsFilter) return;
            try {
                if (!window.seasonsFilter.loaded) await window.seasonsFilter.loadSeasons();
                window.seasonsFilter.populateSeasonSelect(document.getElementById('af-season'));
                window.seasonsFilter.populateSeasonSelect(document.getElementById('af-week-season-select'));
            } catch (e) { console.error('seasonsFilter load failed', e); }
        }

        function wireDateFilter() {
            const $ = (id) => document.getElementById(id);
            const cat = $('af-filter-category');
            const dateType = $('af-date-filter-type');
            const startEl = $('af-start-date');
            const endEl = $('af-end-date');
            const singleDaySlotWrap = $('af-single-day-timeslot-filter');
            const singleDaySlotEl = $('af-single-day-timeslot');
            const seasonEl = $('af-season');
            const weekSeasonEl = $('af-week-season-select');
            const weekEl = $('af-week');
            const resetGroup = $('af-reset-btn-group');
            const resetBtn = $('af-reset-btn');
            ensureSeasonsLoaded();

            const refreshSingleDaySlot = () => {
                const pair = getCustomRangeDayPair();
                const singleDay = !!pair && pair.startDay === pair.endDay;
                const show = cat.value === 'date' && dateType.value === 'custom' && singleDay;
                singleDaySlotWrap.style.display = show ? 'flex' : 'none';
                if (!show) singleDaySlotEl.value = 'all';
            };

            const showCat = (c) => {
                $('af-date-type-filter').style.display = 'none';
                $('af-custom-range-filters').style.display = 'none';
                $('af-season-select-filter').style.display = 'none';
                $('af-week-season-filter').style.display = 'none';
                $('af-week-select-filter').style.display = 'none';
                if (c === 'date') $('af-date-type-filter').style.display = 'flex';
                else if (c === 'season') $('af-season-select-filter').style.display = 'flex';
                else if (c === 'week') {
                    $('af-week-season-filter').style.display = 'flex';
                    $('af-week-select-filter').style.display = 'flex';
                }
                refreshSingleDaySlot();
            };
            const refreshReset = () => {
                resetGroup.style.display = cat.value !== 'all' ? 'flex' : 'none';
            };
            const applyAndReload = () => {
                let startTs = 0, endTs = 0;
                const c = cat.value;
                if (c === 'date') {
                    const t = dateType.value;
                    const now = Date.now();
                    const day = 86400000;
                    if (t === 'last7')  { startTs = Math.floor((now - 7  * day) / 1000); endTs = Math.floor(now / 1000); }
                    else if (t === 'last30') { startTs = Math.floor((now - 30 * day) / 1000); endTs = Math.floor(now / 1000); }
                    else if (t === 'last90') { startTs = Math.floor((now - 90 * day) / 1000); endTs = Math.floor(now / 1000); }
                    else if (t === 'custom') {
                        const pair = getCustomRangeDayPair();
                        if (!pair) return;
                        const slot = pair.startDay === pair.endDay
                            ? (singleDaySlotEl.value || 'all')
                            : 'all';
                        const s = buildTimeslotBoundary(pair.startDay, slot, 'start');
                        const e = buildTimeslotBoundary(pair.endDay, slot, 'end');
                        if (!s || !e) return;
                        startTs = Math.floor(s.getTime() / 1000);
                        endTs   = Math.floor(e.getTime() / 1000);
                    } else return;
                } else if (c === 'season') {
                    const name = seasonEl.value;
                    // Empty value (Please select / All seasons) → drop the filter.
                    if (!name) {
                        dateFilter = { type: 'all', startTs: 0, endTs: 0 };
                        refreshReset();
                        reloadActiveView();
                        return;
                    }
                    if (!window.seasonsFilter) return;
                    const range = window.seasonsFilter.getSeasonDateRange(name);
                    if (!range) return;
                    startTs = Math.floor(range.startDate.getTime() / 1000);
                    endTs   = Math.floor(range.endDate.getTime() / 1000);
                } else if (c === 'week') {
                    const name = weekSeasonEl.value, w = weekEl.value;
                    if (!name || !w || !window.seasonsFilter) return;
                    let range;
                    if (w === 'all') range = window.seasonsFilter.getSeasonDateRange(name);
                    else {
                        const wn = (w === 'final') ? null : parseInt(w);
                        range = window.seasonsFilter.getWeekDateRange(name, wn);
                    }
                    if (!range) return;
                    startTs = Math.floor(range.startDate.getTime() / 1000);
                    endTs   = Math.floor(range.endDate.getTime() / 1000);
                }
                dateFilter = { type: c, startTs, endTs };
                refreshReset();
                reloadActiveView();
            };

            cat.addEventListener('change', () => {
                showCat(cat.value);
                refreshReset();
                if (cat.value === 'all') {
                    dateFilter = { type: 'all', startTs: 0, endTs: 0 };
                    reloadActiveView();
                } else if (cat.value === 'date') {
                    // pick a sensible default once the user opens the date sub-controls
                    dateType.dispatchEvent(new Event('change'));
                }
            });
            dateType.addEventListener('change', () => {
                $('af-custom-range-filters').style.display = dateType.value === 'custom' ? 'flex' : 'none';
                refreshSingleDaySlot();
                if (dateType.value !== 'custom') applyAndReload();
            });
            startEl.addEventListener('change', () => { refreshSingleDaySlot(); applyAndReload(); });
            endEl.addEventListener('change', () => { refreshSingleDaySlot(); applyAndReload(); });
            singleDaySlotEl.addEventListener('change', applyAndReload);
            seasonEl.addEventListener('change', applyAndReload);
            weekSeasonEl.addEventListener('change', () => {
                if (weekSeasonEl.value && window.seasonsFilter) {
                    window.seasonsFilter.populateWeekSelect(weekEl, weekSeasonEl.value, true);
                } else {
                    weekEl.innerHTML = `<option value="">${escapeHtml(T('player.pleaseSelect'))}</option>`;
                }
            });
            weekEl.addEventListener('change', applyAndReload);
            resetBtn.addEventListener('click', () => {
                cat.value = 'all'; dateType.value = 'last7';
                startEl.value = ''; endEl.value = '';
                singleDaySlotEl.value = 'all';
                seasonEl.value = ''; weekSeasonEl.value = '';
                weekEl.innerHTML = `<option value="">${escapeHtml(T('player.pleaseSelect'))}</option>`;
                showCat('all'); refreshReset();
                dateFilter = { type: 'all', startTs: 0, endTs: 0 };
                reloadActiveView();
            });
        }

        function switchView(view) {
            activeView = view;
            document.querySelectorAll('#analyticsTabs .leaderboard-tab').forEach(btn => {
                btn.classList.toggle('active', btn.dataset.view === view);
            });
            loadView(view);
        }

        async function loadView(view) {
            const slot = document.getElementById('analyticsViewSlot');
            if (!activeSquadron) return;

            const cacheKey = `${activeSquadron}|${view}|${filterKey()}`;
            if (cache.has(cacheKey)) {
                renderView(view, cache.get(cacheKey));
                return;
            }

            slot.innerHTML = `<div class="loading-state"><div class="loading-spinner"></div>${escapeHtml(T('analytics.loading'))}</div>`;
            try {
                const data = await window.apiClient.request(`/api/analytics/${view}/${encodeURIComponent(activeSquadron)}${filterQueryString()}`);
                cache.set(cacheKey, data);
                if (activeView === view) renderView(view, data);
            } catch (e) {
                console.error(e);
                slot.innerHTML = `<div class="error-state"><i class="fas fa-exclamation-triangle"></i> ${escapeHtml(T('analytics.loadError'))}</div>`;
            }
        }

        function renderView(view, data) {
            const slot = document.getElementById('analyticsViewSlot');
            const isEmpty = Array.isArray(data)
                ? data.length === 0
                : (view === 'time' ? (!data || !data.hourly || Object.keys(data.hourly).length === 0)
                  : view === 'matchup' ? (!data.won_against || (!data.won_against.length && !data.lost_against.length))
                  : view === 'comps' ? (!data || !data.top_vehicles || data.top_vehicles.length === 0)
                  : false);
            if (isEmpty) {
                slot.innerHTML = `<div class="empty-state"><i class="fas fa-inbox" style="font-size:1.5rem; display:block; margin-bottom:0.5rem;"></i>${escapeHtml(T('analytics.noData'))}</div>`;
                return;
            }
            switch (view) {
                case 'maps': return renderMaps(slot, data);
                case 'consistency': return renderConsistency(slot, data);
                case 'time': return renderTime(slot, data);
                case 'matchup': return renderMatchup(slot, data);
                case 'comps': return renderComps(slot, data);
            }
        }

        let mapsRadarChart = null;

        function renderMaps(slot, data) {
            if (mapsRadarChart) { try { mapsRadarChart.destroy(); } catch (e) {} mapsRadarChart = null; }

            const rows = data.map(r => `
                <tr>
                    <td class="col-map" data-sort-value="${escapeHtml((r.map_name || '').toLowerCase())}">${escapeHtml(r.map_name)}</td>
                    <td class="col-num" data-sort-value="${r.wins}" style="color:#90EE90">${r.wins}</td>
                    <td class="col-num" data-sort-value="${r.losses}" style="color:#ff9b9b">${r.losses}</td>
                    <td class="col-num" data-sort-value="${r.win_rate}">${r.win_rate}%</td>
                    <td class="col-bar"><div class="bar-track"><div class="bar-fill" style="width:${Math.min(100, r.win_rate)}%"></div></div></td>
                    <td class="col-num" data-sort-value="${r.total}">${r.total}</td>
                </tr>`).join('');

            const RADAR_MIN_GAMES = (dateFilter.type === 'week' || dateFilter.type === 'date') ? 2 : 5;
            const RADAR_MAX_AXES = 20;
            const eligible = data.filter(r => r.total >= RADAR_MIN_GAMES);
            // Pick top N by games-played, then sort alphabetically for stable layout.
            const radarData = eligible
                .slice()
                .sort((a, b) => b.total - a.total)
                .slice(0, RADAR_MAX_AXES)
                .sort((a, b) => a.map_name.localeCompare(b.map_name));
            const excludedCount = data.length - radarData.length;
            const maxGames = radarData.reduce((m, r) => Math.max(m, r.total), 0) || 1;
            const truncate = (s, n) => s.length > n ? s.slice(0, n - 1).trimEnd() + '…' : s;

            slot.innerHTML = `
                <div class="maps-layout">
                    <div class="maps-table-wrap">
                        <table class="analytics-table maps-table">
                            <thead><tr>
                                <th class="col-map" data-sort-type="text">${escapeHtml(T('analytics.colMap'))}</th>
                                <th class="col-num" data-sort-type="num">${escapeHtml(T('analytics.colWins'))}</th>
                                <th class="col-num" data-sort-type="num">${escapeHtml(T('analytics.colLosses'))}</th>
                                <th class="col-num" data-sort-type="num">${escapeHtml(T('analytics.colWinRate'))}</th>
                                <th class="col-bar" data-sortable="false">${escapeHtml(T('analytics.colBar'))}</th>
                                <th class="col-num" data-sort-type="num">${escapeHtml(T('analytics.colGames'))}</th>
                            </tr></thead>
                            <tbody>${rows}</tbody>
                        </table>
                    </div>
                    <div class="radar-card">
                        <div class="radar-card-header">
                            <span class="radar-card-title">${escapeHtml(T('analytics.tabMaps'))}</span>
                            <span class="radar-card-meta">${escapeHtml(T('analytics.radarMetaMaps', { shown: radarData.length, total: data.length, min: RADAR_MIN_GAMES }))}</span>
                        </div>
                        ${radarData.length < 3 ? `
                            <div class="radar-canvas-wrap" style="display:flex; align-items:center; justify-content:center; text-align:center; color:rgba(245,245,220,0.55); padding:1.5rem;">
                                ${escapeHtml(T('analytics.radarTooFewMaps', { min: RADAR_MIN_GAMES }))}
                            </div>` : `
                            <div class="radar-canvas-wrap">
                                <canvas id="mapsRadarCanvas"></canvas>
                            </div>
                            <div class="radar-legend">
                                <span><span class="swatch" style="background:#90EE90"></span>${escapeHtml(T('analytics.colWinRate'))}</span>
                                <span><span class="swatch" style="background:#F5C16C"></span>${escapeHtml(T('analytics.colGames'))}</span>
                            </div>`}
                        ${excludedCount > 0 ? `<div class="radar-footnote">${escapeHtml(T('analytics.radarFootnoteMaps', { count: excludedCount }))}</div>` : ''}
                    </div>
                </div>`;

            makeTableSortable(slot.querySelector('.maps-table'));

            if (radarData.length < 3 || typeof Chart === 'undefined') return;

            const labels = radarData.map(r => truncate(r.map_name, 16));
            const wrSeries = radarData.map(r => r.win_rate);
            const gamesSeries = radarData.map(r => Math.round((r.total / maxGames) * 100));

            const ctx = document.getElementById('mapsRadarCanvas').getContext('2d');
            mapsRadarChart = new Chart(ctx, {
                type: 'radar',
                data: {
                    labels,
                    datasets: [
                        {
                            label: T('analytics.colWinRate'),
                            data: wrSeries,
                            backgroundColor: 'rgba(144, 238, 144, 0.22)',
                            borderColor: 'rgba(144, 238, 144, 0.95)',
                            borderWidth: 2,
                            pointBackgroundColor: '#90EE90',
                            pointBorderColor: '#1b1b1b',
                            pointRadius: 3,
                            pointHoverRadius: 5,
                        },
                        {
                            label: T('analytics.colGames'),
                            data: gamesSeries,
                            backgroundColor: 'rgba(245, 193, 108, 0.10)',
                            borderColor: 'rgba(245, 193, 108, 0.85)',
                            borderWidth: 1.5,
                            borderDash: [4, 4],
                            pointBackgroundColor: '#F5C16C',
                            pointBorderColor: '#1b1b1b',
                            pointRadius: 2,
                            pointHoverRadius: 4,
                        },
                    ],
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    animation: { duration: 600 },
                    plugins: {
                        legend: { display: false },
                        tooltip: {
                            backgroundColor: 'rgba(15, 20, 15, 0.95)',
                            borderColor: 'rgba(144, 238, 144, 0.3)',
                            borderWidth: 1,
                            titleColor: '#F5F5DC',
                            bodyColor: '#fff',
                            padding: 10,
                            callbacks: {
                                title: (items) => items[0]?.label ?? '',
                                label: (item) => {
                                    const r = radarData[item.dataIndex];
                                    if (item.datasetIndex === 0) {
                                        return `${T('analytics.colWinRate')}: ${r.win_rate}%  (${r.wins}W / ${r.losses}L)`;
                                    }
                                    return `${T('analytics.colGames')}: ${r.total}`;
                                },
                            },
                        },
                    },
                    scales: {
                        r: {
                            min: 0,
                            max: 100,
                            ticks: {
                                stepSize: 25,
                                color: 'rgba(255,255,255,0.4)',
                                backdropColor: 'transparent',
                                font: { size: 9 },
                                callback: (v) => v + '%',
                            },
                            grid: { color: 'rgba(144, 238, 144, 0.12)' },
                            angleLines: { color: 'rgba(144, 238, 144, 0.12)' },
                            pointLabels: {
                                color: 'rgba(245, 245, 220, 0.85)',
                                font: { size: 10, family: 'Inter, sans-serif' },
                            },
                        },
                    },
                },
            });
        }

        let consistencyScatter = null;
        let consistencySelectedUid = null;

        function renderConsistency(slot, data) {
            if (consistencyScatter) { try { consistencyScatter.destroy(); } catch (e) {} consistencyScatter = null; }
            consistencySelectedUid = null;

            const maxKd = Math.max(1, ...data.map(r => r.kd));
            const rows = data.map((r, i) => `
                <tr class="consistency-row row-link" data-uid="${escapeHtml(r.uid)}">
                    <td>${i + 1}</td>
                    <td data-sort-value="${escapeHtml((r.nick || '').toLowerCase())}">
                        <a href="/players/${escapeHtml(r.uid)}" class="row-link-overlay" onclick="event.preventDefault();" aria-label="View ${escapeHtml(r.nick)}"></a>
                        <a href="/players/${escapeHtml(r.uid)}" style="color:#A8E6CF; text-decoration:none;" onclick="event.stopPropagation();">${escapeHtml(r.nick)}</a>
                    </td>
                    <td data-sort-value="${r.games}">${r.games}</td>
                    <td data-sort-value="${r.kd}" style="white-space:nowrap;">${r.kd.toFixed(2)}<span class="micro-bar kd"><span style="width:${Math.min(100, (r.kd / maxKd) * 100)}%"></span></span></td>
                    <td data-sort-value="${r.win_rate}" style="white-space:nowrap;">${r.win_rate}%<span class="micro-bar"><span style="width:${Math.min(100, r.win_rate)}%"></span></span></td>
                </tr>`).join('');

            slot.innerHTML = `
                <div class="consistency-layout">
                    <div class="consistency-table-wrap">
                        <table class="analytics-table consistency-table">
                            <thead><tr>
                                <th data-sortable="false">#</th>
                                <th data-sort-type="text">${escapeHtml(T('analytics.colPlayer'))}</th>
                                <th data-sort-type="num">${escapeHtml(T('analytics.colGames'))}</th>
                                <th data-sort-type="num">K/D</th>
                                <th data-sort-type="num">${escapeHtml(T('analytics.colWinRate'))}</th>
                            </tr></thead>
                            <tbody>${rows}</tbody>
                        </table>
                    </div>
                    <div class="scatter-card">
                        <div class="scatter-card-header">
                            <span class="scatter-card-title">${escapeHtml(T('analytics.tabConsistency'))}</span>
                            <button class="scatter-reset" id="scatterReset" style="visibility:hidden;">Reset</button>
                        </div>
                        <div class="scatter-canvas-wrap">
                            <canvas id="consistencyScatterCanvas"></canvas>
                        </div>
                        <div class="radar-footnote">${data.length} players · K/D vs Win Rate · dot size = games</div>
                    </div>
                </div>`;

            makeTableSortable(slot.querySelector('.consistency-table'));

            const tableRows = slot.querySelectorAll('.consistency-row');
            const resetBtn = document.getElementById('scatterReset');
            tableRows.forEach(tr => {
                tr.addEventListener('click', () => {
                    const uid = tr.dataset.uid;
                    if (consistencySelectedUid === uid) {
                        consistencySelectedUid = null;
                        tableRows.forEach(r => r.classList.remove('active'));
                        resetBtn.style.visibility = 'hidden';
                    } else {
                        consistencySelectedUid = uid;
                        tableRows.forEach(r => r.classList.toggle('active', r.dataset.uid === uid));
                        resetBtn.style.visibility = 'visible';
                    }
                    applyScatterFocus();
                });
            });
            resetBtn.addEventListener('click', () => {
                consistencySelectedUid = null;
                tableRows.forEach(r => r.classList.remove('active'));
                resetBtn.style.visibility = 'hidden';
                applyScatterFocus();
            });

            if (data.length === 0 || typeof Chart === 'undefined') return;

            const maxGames = Math.max(...data.map(r => r.games));
            const points = data.map(r => ({
                x: r.kd,
                y: r.win_rate,
                r: 4 + (r.games / maxGames) * 9,
                _meta: r,
            }));

            const ctx = document.getElementById('consistencyScatterCanvas').getContext('2d');
            consistencyScatter = new Chart(ctx, {
                type: 'bubble',
                data: {
                    datasets: [{
                        label: 'Players',
                        data: points,
                        backgroundColor: points.map(() => 'rgba(144, 238, 144, 0.55)'),
                        borderColor:    points.map(() => 'rgba(144, 238, 144, 0.95)'),
                        borderWidth: 1,
                        hoverBorderWidth: 2,
                    }],
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    animation: { duration: 400 },
                    onClick: (evt, elements) => {
                        if (!elements.length) return;
                        const uid = points[elements[0].index]._meta.uid;
                        const tr = slot.querySelector(`.consistency-row[data-uid="${CSS.escape(uid)}"]`);
                        if (tr) tr.click();
                    },
                    plugins: {
                        legend: { display: false },
                        tooltip: {
                            backgroundColor: 'rgba(15, 20, 15, 0.95)',
                            borderColor: 'rgba(144, 238, 144, 0.3)',
                            borderWidth: 1,
                            titleColor: '#F5F5DC',
                            bodyColor: '#fff',
                            padding: 10,
                            callbacks: {
                                title: (items) => points[items[0].dataIndex]._meta.nick,
                                label: (item) => {
                                    const m = points[item.dataIndex]._meta;
                                    return [`Games: ${m.games}`, `K/D: ${m.kd.toFixed(2)}`, `WR: ${m.win_rate}%  (${m.wins}W / ${m.losses}L)`];
                                },
                            },
                        },
                    },
                    layout: { padding: 18 },
                    scales: {
                        x: {
                            title: { display: true, text: 'K/D', color: 'rgba(245,245,220,0.7)' },
                            min: 0,
                            suggestedMax: Math.max(1, Math.ceil(Math.max(...points.map(p => p.x)) * 1.05 * 10) / 10),
                            ticks: { color: 'rgba(255,255,255,0.5)' },
                            grid: { color: 'rgba(144, 238, 144, 0.08)' },
                        },
                        y: {
                            title: { display: true, text: 'Win Rate %', color: 'rgba(245,245,220,0.7)' },
                            min: -5, max: 105,
                            ticks: { color: 'rgba(255,255,255,0.5)', stepSize: 25, callback: v => (v >= 0 && v <= 100) ? v + '%' : '' },
                            grid: { color: 'rgba(144, 238, 144, 0.08)' },
                        },
                    },
                },
            });
        }

        function applyScatterFocus() {
            if (!consistencyScatter) return;
            const points = consistencyScatter.data.datasets[0].data;
            const bg = points.map(p => {
                if (!consistencySelectedUid) return 'rgba(144, 238, 144, 0.55)';
                return p._meta.uid === consistencySelectedUid ? 'rgba(245, 193, 108, 0.85)' : 'rgba(144, 238, 144, 0.08)';
            });
            const bd = points.map(p => {
                if (!consistencySelectedUid) return 'rgba(144, 238, 144, 0.95)';
                return p._meta.uid === consistencySelectedUid ? 'rgba(245, 193, 108, 1)' : 'rgba(144, 238, 144, 0.15)';
            });
            consistencyScatter.data.datasets[0].backgroundColor = bg;
            consistencyScatter.data.datasets[0].borderColor = bd;
            consistencyScatter.update('none');
        }

        function renderTime(slot, data) {
            // Accepts either the legacy shape (hour-keyed object) or the new
            // shape { hourly: {...}, daily: [{day, count}] }.
            const hourly = (data && data.hourly) ? data.hourly : data;
            const daily  = (data && data.daily)  ? data.daily  : null;

            // SQB only runs in NA 01-07 UTC and EU 14-22 UTC. Render those 16
            // hours in chronological order with NA first (matches BOT/botscript.py).
            const slots = [
                { region: 'na', hours: [1,2,3,4,5,6,7] },
                { region: 'eu', hours: [14,15,16,17,18,19,20,21,22] },
            ];
            const out = [];
            for (const { region, hours } of slots) {
                const label = region === 'eu' ? T('analytics.euTimeslot') : T('analytics.naTimeslot');
                out.push(`<tr class="section-divider-row"><td colspan="4" class="section-divider">${escapeHtml(label)}</td></tr>`);
                for (const h of hours) {
                    const s = (hourly && hourly[h]) || { wins: 0, losses: 0, win_rate: 0 };
                    const empty = (s.wins + s.losses) === 0;
                    const rowStyle = empty ? ' style="opacity:0.35"' : '';
                    out.push(`
                        <tr${rowStyle}>
                            <td class="time-cell">
                                <span class="time-label">${String(h).padStart(2, '0')}:00 UTC</span>
                                <div class="bar-track time-bar"><div class="bar-fill" style="width:${Math.min(100, s.win_rate)}%"></div></div>
                            </td>
                            <td style="color:#90EE90">${s.wins}</td>
                            <td style="color:#ff9b9b">${s.losses}</td>
                            <td>${empty ? '—' : s.win_rate + '%'}</td>
                        </tr>`);
                }
            }
            slot.innerHTML = `
                <table class="analytics-table time-table">
                    <thead><tr>
                        <th>${escapeHtml(T('analytics.colHour'))}</th>
                        <th>${escapeHtml(T('analytics.colWins'))}</th>
                        <th>${escapeHtml(T('analytics.colLosses'))}</th>
                        <th>${escapeHtml(T('analytics.colWinRate'))}</th>
                    </tr></thead>
                    <tbody>${out.join('')}</tbody>
                </table>
                ${daily ? `
                    <div class="timeline-card" style="margin-top:1rem;">
                        <div class="timeline-card-header">
                            <span class="timeline-card-title">Activity calendar</span>
                            <span class="timeline-card-meta">games per day · UTC</span>
                        </div>
                        <div class="calendar-wrap"><div class="activity-calendar-mount"></div></div>
                    </div>` : ''}`;
            if (daily) {
                renderActivityCalendar(slot.querySelector('.activity-calendar-mount'), daily);
            }
        }

        function renderMatchup(slot, data) {
            const makeTable = (entries) => {
                if (!entries || !entries.length) return `<div class="empty-state" style="padding:2rem;">—</div>`;
                return `
                    <table class="analytics-table matchup-table">
                        <thead><tr>
                            <th data-sort-type="text">${escapeHtml(T('analytics.colSquadron'))}</th>
                            <th data-sort-type="num">${escapeHtml(T('analytics.colWins'))}</th>
                            <th data-sort-type="num">${escapeHtml(T('analytics.colLosses'))}</th>
                            <th data-sort-type="num">${escapeHtml(T('analytics.colTotal'))}</th>
                        </tr></thead>
                        <tbody>
                            ${entries.map(e => `
                                <tr>
                                    <td data-sort-value="${escapeHtml((e.opponent || '').toLowerCase())}"><a href="/squadrons/${encodeURIComponent(e.opponent)}" class="sq-tag" style="text-decoration:none;">${escapeHtml(e.opponent)}</a></td>
                                    <td data-sort-value="${e.wins}" style="color:#90EE90">${e.wins}</td>
                                    <td data-sort-value="${e.losses}" style="color:#ff9b9b">${e.losses}</td>
                                    <td data-sort-value="${e.total}">${e.total}</td>
                                </tr>`).join('')}
                        </tbody>
                    </table>`;
            };
            slot.innerHTML = `
                <div class="matchup-grid">
                    <div class="matchup-col">
                        <h3><i class="fas fa-trophy" style="color:#90EE90; margin-right:0.5rem;"></i>${escapeHtml(T('analytics.matchupsWonHeader'))}</h3>
                        ${makeTable(data.won_against)}
                    </div>
                    <div class="matchup-col">
                        <h3><i class="fas fa-skull" style="color:#ff9b9b; margin-right:0.5rem;"></i>${escapeHtml(T('analytics.matchupsLostHeader'))}</h3>
                        ${makeTable(data.lost_against)}
                    </div>
                </div>
                <div style="text-align:center; margin-top:1rem; color:rgba(144,238,144,0.5); font-size:0.85rem;">
                    ${data.total_opponents} ${escapeHtml(T('analytics.uniqueOpponents'))}
                </div>`;
            slot.querySelectorAll('.matchup-table').forEach(makeTableSortable);
        }

        // Type abbreviations the backend uses, in the same display order as
        // /comp on Discord. Vehicle classes the bot recognises:
        // F=Fighter, B=Bomber, H=Helicopter, L=Light, T=Tank, AA=SPAA, ?=unknown.
        const COMP_TYPE_ORDER = ['F', 'B', 'H', 'L', 'T', 'AA', '?'];
        const COMP_TYPE_COLORS = {
            F: '#9DD9F3', B: '#C7B6E5', H: '#F5C16C',
            L: '#A8E6CF', T: '#90EE90', AA: '#F58C8C', '?': '#888',
        };

        function tVeh(v) {
            // Translate via the WT internal id when we have it; fall back to
            // whatever display string the API gave us. The vehicle-i18n module
            // has already pre-loaded the map by the time this runs in the
            // common case, but we tolerate a missing map gracefully.
            if (window.vehicleI18n && v && v.vehicle_internal) {
                return window.vehicleI18n.translate(v.vehicle_internal, v.vehicle);
            }
            return v && v.vehicle ? v.vehicle : '';
        }

        function renderTypeBadges(types) {
            // Compact badges like [3 F] [4 T] [1 AA] in canonical /comp order.
            const out = [];
            for (const code of COMP_TYPE_ORDER) {
                const n = (types && types[code]) || 0;
                if (n <= 0) continue;
                const color = COMP_TYPE_COLORS[code] || '#fff';
                out.push(`<span class="comp-type-badge" style="color:${color}; border-color:${color}55;">${n}<span class="comp-type-badge-letter">${escapeHtml(code)}</span></span>`);
            }
            return `<span class="comp-type-badges">${out.join('')}</span>`;
        }

        function renderCompLineup(vehicles) {
            // Group same-type vehicles into one row each so the lineup reads
            // like a roster sheet: "Fighters: 2× F-16C, BV-238", "Tanks: 2× T-80BVM …".
            const byType = {};
            for (const v of vehicles) {
                const t = v.type || '?';
                if (!byType[t]) byType[t] = [];
                byType[t].push(v);
            }
            const TYPE_LABEL = {
                F: T('analytics.compTypeFighters'),
                B: T('analytics.compTypeBombers'),
                H: T('analytics.compTypeHelicopters'),
                L: T('analytics.compTypeLight'),
                T: T('analytics.compTypeTanks'),
                AA: T('analytics.compTypeSPAA'),
                '?': T('analytics.compTypeUnknown'),
            };
            const lines = [];
            for (const code of COMP_TYPE_ORDER) {
                const list = byType[code];
                if (!list || !list.length) continue;
                const color = COMP_TYPE_COLORS[code] || '#fff';
                const items = list
                    .slice()
                    .sort((a, b) => (b.count - a.count) || tVeh(a).localeCompare(tVeh(b)))
                    .map(v => v.count > 1 ? `${v.count}× ${escapeHtml(tVeh(v))}` : escapeHtml(tVeh(v)))
                    .join(', ');
                lines.push(`<div class="comp-lineup-row">
                    <span class="comp-lineup-label" style="color:${color};">${escapeHtml(TYPE_LABEL[code] || code)}</span>
                    <span class="comp-lineup-items">${items}</span>
                </div>`);
            }
            return `<div class="comp-lineup">${lines.join('')}</div>`;
        }

        function compMatchesQuery(comp, query) {
            // query: {
            //   notation: "3F 4T 1AA",   // exact match against comp.notation
            //   types: { F:3, T:4, ... }, // each comp.types[code] >= n
            //   vehicles: [internal, ...] // multiset check: comp must contain
            //                              // each requested id at least the
            //                              // number of times it appears in
            //                              // the query list.
            // }
            // Empty query → matches everything.
            if (!query) return true;
            if (query.notation && comp.notation !== query.notation) return false;
            if (query.types) {
                for (const [code, n] of Object.entries(query.types)) {
                    if (!n) continue;
                    if ((comp.types && comp.types[code] || 0) < n) return false;
                }
            }
            if (query.vehicles && query.vehicles.length) {
                const compCounts = {};
                for (const v of comp.vehicles) compCounts[v.vehicle_internal] = (compCounts[v.vehicle_internal] || 0) + v.count;
                const requested = {};
                for (const id of query.vehicles) requested[id] = (requested[id] || 0) + 1;
                for (const [id, n] of Object.entries(requested)) {
                    if ((compCounts[id] || 0) < n) return false;
                }
            }
            return true;
        }

        let _compsState = null;  // Holds last-rendered comps for re-filtering by search bar.

        // If the i18n map shows up after the comps view first painted (cold
        // load, no localStorage cache yet), repaint so the names switch from
        // the English fallback to the user's language without a full reload.
        document.addEventListener('vehicle-i18n-ready', () => {
            if (panelMode === 'squadron' && activeView === 'comps' && _compsState) {
                const slot = document.getElementById('analyticsViewSlot');
                if (slot) renderComps(slot, {
                    top_vehicles: _compsState.topVehicles,
                    compositions: _compsState.compositions,
                    notations: _compsState.notations,
                    total_sessions: _compsState.totalSessions || 0,
                    min_size: _compsState.minSize || 8,
                });
            }
        });

        function renderComps(slot, data) {
            const topVehicles = data.top_vehicles || [];
            const compositions = data.compositions || [];
            const notations = data.notations || [];
            const totalSessions = data.total_sessions || 0;
            const minSize = data.min_size || 8;

            _compsState = { compositions, notations, topVehicles, totalSessions, minSize };

            const maxSpawns = Math.max(1, ...topVehicles.map(v => v.spawns));
            const maxKd = Math.max(1, ...topVehicles.map(v => v.kd));

            const vehRows = topVehicles.map((v, i) => `
                <tr>
                    <td data-sortable="false">${i + 1}</td>
                    <td data-sort-value="${escapeHtml((tVeh(v) || '').toLowerCase())}">
                        <a href="#" class="vehicle-name comp-veh-link" data-vehicle="${escapeHtml(v.vehicle)}" data-vehicle-internal="${escapeHtml(v.vehicle_internal || '')}" style="color:#A8E6CF; text-decoration:none; cursor:pointer;">${escapeHtml(tVeh(v))}</a>
                        <span class="comp-type-tag" style="color:${COMP_TYPE_COLORS[v.type] || '#888'};">${escapeHtml(v.type || '?')}</span>
                    </td>
                    <td data-sort-value="${v.spawns}" style="white-space:nowrap;">
                        ${v.spawns}<span class="micro-bar"><span style="width:${Math.min(100, (v.spawns / maxSpawns) * 100)}%"></span></span>
                    </td>
                    <td data-sort-value="${v.sessions}">${v.sessions}</td>
                    <td data-sort-value="${v.share_pct}">${v.share_pct}%</td>
                    <td data-sort-value="${v.kd}" style="white-space:nowrap;">
                        ${v.kd.toFixed(2)}<span class="micro-bar kd"><span style="width:${Math.min(100, (v.kd / maxKd) * 100)}%"></span></span>
                    </td>
                    <td data-sort-value="${v.win_rate}" style="white-space:nowrap;">
                        ${v.win_rate}%<span class="micro-bar"><span style="width:${Math.min(100, v.win_rate)}%"></span></span>
                    </td>
                </tr>`).join('');

            // Search bar: presets are the squadron's actual notations sorted by
            // match count (top 25), plus a custom builder that lets the user
            // add type counts and specific vehicles.
            const presetOptions = notations.slice(0, 25).map(n =>
                `<option value="${escapeHtml(n.notation)}">${escapeHtml(n.notation)} — ${n.games} ${escapeHtml(T('analytics.compSearchGamesShort'))}</option>`
            ).join('');

            // Tooltip names — same as the lineup labels except AA uses the
            // expanded "Anti-Aircraft/SPAA" form so users seeing the chip
            // for the first time know what the abbreviation stands for.
            const TYPE_FULL_NAME = {
                F: T('analytics.compTypeFighters'),
                B: T('analytics.compTypeBombers'),
                H: T('analytics.compTypeHelicopters'),
                L: T('analytics.compTypeLight'),
                T: T('analytics.compTypeTanks'),
                AA: T('analytics.compTypeSPAATooltip'),
            };
            const customTypeRows = COMP_TYPE_ORDER.filter(c => c !== '?').map(code => {
                const color = COMP_TYPE_COLORS[code] || '#fff';
                const fullName = TYPE_FULL_NAME[code] || code;
                return `<label class="comp-custom-type" title="${escapeHtml(fullName)}">
                    <span class="comp-type-badge" style="color:${color}; border-color:${color}55;">
                        <span class="comp-type-badge-letter">${escapeHtml(code)}</span>
                    </span>
                    <input type="number" min="0" max="16" step="1" value="0" data-comp-type="${escapeHtml(code)}" aria-label="${escapeHtml(fullName)}" />
                </label>`;
            }).join('');

            const searchSection = `
                <div class="comp-search">
                    <div class="comp-search-row">
                        <label class="comp-search-label" for="compPresetSelect">${escapeHtml(T('analytics.compSearchPresetLabel'))}</label>
                        <select id="compPresetSelect" class="comp-search-select">
                            <option value="">${escapeHtml(T('analytics.compSearchPresetAll'))}</option>
                            ${presetOptions}
                        </select>
                        <span class="comp-search-hint">${escapeHtml(T('analytics.compSearchPresetHint'))}</span>
                    </div>
                    <div class="comp-search-row">
                        <label class="comp-search-label">${escapeHtml(T('analytics.compSearchTypesLabel'))}</label>
                        <div class="comp-custom-types">${customTypeRows}</div>
                        <span class="comp-search-hint">${escapeHtml(T('analytics.compTypeCapsHint'))}</span>
                    </div>
                    <div class="comp-search-row comp-search-row-block">
                        <label class="comp-search-label">${escapeHtml(T('analytics.compSearchRefineLabel'))}</label>
                        <div id="compRefineContainer" class="comp-refine-container"></div>
                    </div>
                    <div class="comp-search-row" style="justify-content:flex-end;">
                        <button id="compSearchReset" class="comp-search-btn comp-search-btn-ghost">${escapeHtml(T('analytics.compSearchReset'))}</button>
                        <button id="compSearchApply" class="comp-search-btn comp-search-btn-primary">${escapeHtml(T('analytics.compSearchApply'))}</button>
                    </div>
                    <div class="comp-search-meta" id="compSearchMeta"></div>
                </div>`;

            const compSection = compositions.length ? `
                <div class="comps-section">
                    <div class="comps-section-header">
                        <span class="comps-section-title">${escapeHtml(T('analytics.compCompositionsTitle'))}</span>
                        <span class="comps-section-meta">${escapeHtml(T('analytics.compCompositionsMeta', { min: minSize }))}</span>
                    </div>
                    ${searchSection}
                    <div class="comps-table-wrap" id="compsTableWrap"></div>
                </div>` : `
                <div class="comps-section">
                    <div class="comps-section-header">
                        <span class="comps-section-title">${escapeHtml(T('analytics.compCompositionsTitle'))}</span>
                    </div>
                    <div class="empty-state" style="padding:1.5rem;">${escapeHtml(T('analytics.compNoRepeats'))}</div>
                </div>`;

            slot.innerHTML = `
                <div class="comps-layout">
                    <div class="comps-section">
                        <div class="comps-section-header">
                            <span class="comps-section-title">${escapeHtml(T('analytics.compTopVehiclesTitle'))}</span>
                            <span class="comps-section-meta">${totalSessions} ${escapeHtml(T('analytics.compMatchesAnalyzed'))}</span>
                        </div>
                        <div class="comps-table-wrap">
                            <table class="analytics-table comps-veh-table">
                                <thead><tr>
                                    <th data-sortable="false">#</th>
                                    <th data-sort-type="text">${escapeHtml(T('analytics.compColVehicle'))}</th>
                                    <th data-sort-type="num">${escapeHtml(T('analytics.compColSpawns'))}</th>
                                    <th data-sort-type="num">${escapeHtml(T('analytics.compColMatches'))}</th>
                                    <th data-sort-type="num">${escapeHtml(T('analytics.compColShare'))}</th>
                                    <th data-sort-type="num">K/D</th>
                                    <th data-sort-type="num">${escapeHtml(T('analytics.colWinRate'))}</th>
                                </tr></thead>
                                <tbody>${vehRows}</tbody>
                            </table>
                        </div>
                    </div>
                    ${compSection}
                </div>`;

            slot.querySelectorAll('.comps-veh-table').forEach(makeTableSortable);

            slot.querySelectorAll('.comp-veh-link').forEach(el => {
                el.addEventListener('click', (e) => {
                    e.preventDefault();
                    const internal = (el.dataset.vehicleInternal || '').toLowerCase() || null;
                    if (!internal) return;
                    // Resolve the localized display from the translation map
                    // so the inline header matches what the search dropdown
                    // would have shown.
                    const display = vehicleDisplay(internal) || el.textContent || internal;
                    setMode('vehicle');
                    pickVehicle(display, internal);
                });
            });

            if (compositions.length) {
                renderCompsFiltered(null);
                wireCompSearch();
            }
        }

        function renderCompsFiltered(query) {
            const wrap = document.getElementById('compsTableWrap');
            const meta = document.getElementById('compSearchMeta');
            if (!wrap || !_compsState) return;

            const filtered = _compsState.compositions.filter(c => compMatchesQuery(c, query));
            if (meta) {
                meta.textContent = T('analytics.compSearchMatches')
                    .replace('{shown}', filtered.length)
                    .replace('{total}', _compsState.compositions.length);
            }

            if (!filtered.length) {
                wrap.innerHTML = `<div class="empty-state" style="padding:1.5rem;">${escapeHtml(T('analytics.compSearchNoMatches'))}</div>`;
                return;
            }

            const compRows = filtered.map((c, i) => `
                <tr>
                    <td data-sortable="false">${i + 1}</td>
                    <td data-sort-value="${escapeHtml(c.notation)}">${renderTypeBadges(c.types)}</td>
                    <td>${renderCompLineup(c.vehicles)}</td>
                    <td data-sort-value="${c.games}">${c.games}</td>
                    <td data-sort-value="${c.wins}" style="color:#90EE90">${c.wins}</td>
                    <td data-sort-value="${c.losses}" style="color:#ff9b9b">${c.losses}</td>
                    <td data-sort-value="${c.win_rate}" style="white-space:nowrap;">
                        ${c.win_rate}%<span class="micro-bar"><span style="width:${Math.min(100, c.win_rate)}%"></span></span>
                    </td>
                </tr>`).join('');

            wrap.innerHTML = `
                <table class="analytics-table comps-comp-table">
                    <thead><tr>
                        <th data-sortable="false">#</th>
                        <th data-sortable="false">${escapeHtml(T('analytics.compColTypes'))}</th>
                        <th data-sortable="false">${escapeHtml(T('analytics.compColLineup'))}</th>
                        <th data-sort-type="num">${escapeHtml(T('analytics.colGames'))}</th>
                        <th data-sort-type="num">${escapeHtml(T('analytics.colWins'))}</th>
                        <th data-sort-type="num">${escapeHtml(T('analytics.colLosses'))}</th>
                        <th data-sort-type="num">${escapeHtml(T('analytics.colWinRate'))}</th>
                    </tr></thead>
                    <tbody>${compRows}</tbody>
                </table>`;
            makeTableSortable(wrap.querySelector('.comps-comp-table'));
        }

        function parseNotation(n) {
            // "3F 4T 1AA" → { F:3, T:4, AA:1 }
            const out = {};
            if (!n) return out;
            const re = /(\d+)\s*(AA|F|B|H|L|T|\?)/gi;
            let m;
            while ((m = re.exec(n)) !== null) {
                const code = m[2].toUpperCase();
                out[code] = (out[code] || 0) + parseInt(m[1]);
            }
            return out;
        }

        function readTypeCounts() {
            const counts = {};
            document.querySelectorAll('.comp-custom-type input[type="number"]').forEach(input => {
                const n = parseInt(input.value || '0');
                const code = input.dataset.compType;
                if (n > 0 && code) counts[code] = n;
            });
            return counts;
        }

        // SQB lineups are capped at 8 vehicles per team with at most 4 aviation
        // (fighters + bombers + helicopters). Clamp the just-changed input back
        // to whatever value still satisfies both rules so the user can't build
        // a search that no real comp could ever match.
        const COMP_AVIATION_CODES = new Set(['F', 'B', 'H']);
        const COMP_HARD_CAP = 8;
        const COMP_AVIATION_CAP = 4;

        function clampTypeInput(changedInput) {
            const code = changedInput.dataset.compType;
            if (!code) return;
            let aviationOther = 0, totalOther = 0;
            document.querySelectorAll('.comp-custom-type input[type="number"]').forEach(input => {
                if (input === changedInput) return;
                const n = Math.max(0, parseInt(input.value || '0'));
                totalOther += n;
                if (COMP_AVIATION_CODES.has(input.dataset.compType)) aviationOther += n;
            });
            const aviationCap = COMP_AVIATION_CODES.has(code)
                ? Math.max(0, COMP_AVIATION_CAP - aviationOther)
                : Infinity;
            const totalCap = Math.max(0, COMP_HARD_CAP - totalOther);
            const cap = Math.min(aviationCap, totalCap);
            const wanted = Math.max(0, parseInt(changedInput.value || '0') || 0);
            if (wanted > cap) changedInput.value = cap;
            else if (changedInput.value !== String(wanted)) changedInput.value = wanted;
        }

        function readCustomQuery() {
            const types = readTypeCounts();
            const vehicles = [];
            document.querySelectorAll('.comp-refine-select').forEach(sel => {
                const v = sel.dataset.value;
                if (v) vehicles.push(v);
            });
            const empty = !Object.keys(types).length && !vehicles.length;
            return empty ? null : { types, vehicles };
        }

        // Build one row per type (in canonical order) with `count` vehicle
        // slots. Each slot is a dropdown of vehicles of that type, defaulting
        // to "Any <type>" so the user can leave slots loose. We pull the
        // vehicle list from the squadron's actual top vehicles — listing
        // every WT vehicle would dwarf the page and most wouldn't match
        // anyway.
        function rebuildRefineSection() {
            const container = document.getElementById('compRefineContainer');
            if (!container) return;
            const counts = readTypeCounts();

            if (!Object.keys(counts).length) {
                container.innerHTML = `<span class="comp-refine-hint">${escapeHtml(T('analytics.compRefineHint'))}</span>`;
                return;
            }

            const TYPE_LABEL = {
                F: T('analytics.compTypeFighters'),
                B: T('analytics.compTypeBombers'),
                H: T('analytics.compTypeHelicopters'),
                L: T('analytics.compTypeLight'),
                T: T('analytics.compTypeTanks'),
                AA: T('analytics.compTypeSPAA'),
            };

            // Gather every vehicle that appears either in the top-vehicles
            // table or anywhere in the displayed comps. Top vehicles alone
            // (capped at 50 by spawn count) miss long-tail picks that still
            // show up in a low-game comp — and the user expects to be able
            // to pick anything they can see in the comp list.
            const vehiclesByType = {};
            const seenInternal = new Set();
            const addVehicle = (v) => {
                if (!v || !v.vehicle_internal || seenInternal.has(v.vehicle_internal)) return;
                seenInternal.add(v.vehicle_internal);
                if (!vehiclesByType[v.type]) vehiclesByType[v.type] = [];
                vehiclesByType[v.type].push(v);
            };
            for (const v of (_compsState ? _compsState.topVehicles : [])) addVehicle(v);
            for (const c of (_compsState ? _compsState.compositions : [])) {
                for (const v of c.vehicles) addVehicle(v);
            }

            const rows = [];
            for (const code of COMP_TYPE_ORDER) {
                const n = counts[code];
                if (!n) continue;
                const color = COMP_TYPE_COLORS[code] || '#fff';
                const list = (vehiclesByType[code] || []).slice()
                    .sort((a, b) => tVeh(a).localeCompare(tVeh(b)));
                const anyLabel = T('analytics.compRefineAny').replace('{type}', TYPE_LABEL[code] || code);
                // Custom dropdown — matches the site's existing search-drop
                // pattern. Native <select> won't pick up the skyquake font
                // for option text in Firefox / on the OS-rendered dropdown.
                const optsHtml = `<div class="comp-refine-select-opt is-placeholder is-selected" data-value="">${escapeHtml(anyLabel)}</div>` +
                    list.map(v => `<div class="comp-refine-select-opt" data-value="${escapeHtml(v.vehicle_internal)}" data-label="${escapeHtml(tVeh(v))}">${escapeHtml(tVeh(v))}</div>`).join('');
                const slots = Array.from({ length: n }, () => `
                    <div class="comp-refine-select" data-comp-type="${escapeHtml(code)}" data-value="" data-placeholder="${escapeHtml(anyLabel)}">
                        <button type="button" class="comp-refine-select-btn">
                            <span class="comp-refine-select-btn-label is-placeholder">${escapeHtml(anyLabel)}</span>
                            <i class="fas fa-chevron-down"></i>
                        </button>
                        <div class="comp-refine-select-drop">${optsHtml}</div>
                    </div>`).join('');
                rows.push(`<div class="comp-refine-row">
                    <span class="comp-refine-label" style="color:${color};">${escapeHtml(TYPE_LABEL[code] || code)} (${n})</span>
                    <div class="comp-refine-slots">${slots}</div>
                </div>`);
            }
            container.innerHTML = rows.join('');
            wireCustomDropdowns(container);
        }

        // Single document-level handler closes any open custom dropdown when
        // the click lands outside it. Installed once on first refine render.
        let _customDropdownOutsideWired = false;
        function ensureCustomDropdownOutsideHandler() {
            if (_customDropdownOutsideWired) return;
            _customDropdownOutsideWired = true;
            document.addEventListener('click', (e) => {
                document.querySelectorAll('.comp-refine-select.is-open').forEach(el => {
                    if (!el.contains(e.target)) el.classList.remove('is-open');
                });
            });
        }

        function wireCustomDropdowns(root) {
            ensureCustomDropdownOutsideHandler();
            root.querySelectorAll('.comp-refine-select').forEach(wrap => {
                const btn = wrap.querySelector('.comp-refine-select-btn');
                const labelEl = wrap.querySelector('.comp-refine-select-btn-label');
                const drop = wrap.querySelector('.comp-refine-select-drop');
                if (!btn || !labelEl || !drop) return;

                btn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    const wasOpen = wrap.classList.contains('is-open');
                    // Close any other open dropdown so only one is visible at a time.
                    document.querySelectorAll('.comp-refine-select.is-open').forEach(el => el.classList.remove('is-open'));
                    if (!wasOpen) wrap.classList.add('is-open');
                });

                drop.addEventListener('click', (e) => {
                    const opt = e.target.closest('.comp-refine-select-opt');
                    if (!opt) return;
                    const value = opt.dataset.value || '';
                    const label = opt.dataset.label || opt.textContent || '';
                    wrap.dataset.value = value;
                    if (value) {
                        labelEl.textContent = label;
                        labelEl.classList.remove('is-placeholder');
                    } else {
                        labelEl.textContent = wrap.dataset.placeholder || '';
                        labelEl.classList.add('is-placeholder');
                    }
                    drop.querySelectorAll('.comp-refine-select-opt').forEach(o => o.classList.toggle('is-selected', o === opt));
                    wrap.classList.remove('is-open');
                });
            });
        }

        function wireCompSearch() {
            const presetSel = document.getElementById('compPresetSelect');
            const applyBtn = document.getElementById('compSearchApply');
            const resetBtn = document.getElementById('compSearchReset');

            if (presetSel) {
                presetSel.addEventListener('change', () => {
                    if (!presetSel.value) { renderCompsFiltered(null); return; }
                    // Sync the type inputs and refine section so the user can
                    // see (and edit) what the preset translates to.
                    const types = parseNotation(presetSel.value);
                    document.querySelectorAll('.comp-custom-type input[type="number"]').forEach(input => {
                        input.value = types[input.dataset.compType] || 0;
                    });
                    rebuildRefineSection();
                    // Preset filters by exact notation, regardless of what the
                    // refine slots contain (those are previewed for editing).
                    renderCompsFiltered({ notation: presetSel.value });
                });
            }
            // Type-count changes clamp to SQB lineup caps (8 total, 4 aviation)
            // and regenerate the refine section.
            document.querySelectorAll('.comp-custom-type input[type="number"]').forEach(input => {
                input.addEventListener('input', () => {
                    clampTypeInput(input);
                    rebuildRefineSection();
                });
            });
            if (applyBtn) applyBtn.addEventListener('click', () => {
                if (presetSel) presetSel.value = '';
                renderCompsFiltered(readCustomQuery());
            });
            if (resetBtn) resetBtn.addEventListener('click', () => {
                if (presetSel) presetSel.value = '';
                document.querySelectorAll('.comp-custom-type input[type="number"]').forEach(i => i.value = 0);
                rebuildRefineSection();
                renderCompsFiltered(null);
            });
            rebuildRefineSection();
        }
    })();
    </script>
</body>
</html>
