@@ -301,6 +301,7 @@ function renderCharts() {
301301 renderPlayerDeathRate ( ) ;
302302 renderPlayerCampaignDiversity ( ) ;
303303 renderPlayerClassHeatmap ( ) ;
304+ renderPlayerFolkHeatmap ( ) ;
304305
305306 // Add event listener for killer detail toggle
306307 const killerToggle = document . getElementById ( 'killer-detail-toggle' ) ;
@@ -2047,7 +2048,8 @@ function renderPlayerClassHeatmap() {
20472048 const charInfo = {
20482049 name : charName ,
20492050 path : char . path ,
2050- classes : classDisplay
2051+ classes : classDisplay ,
2052+ race : char . race || ''
20512053 } ;
20522054
20532055 // Count this character for each of their classes
@@ -2117,7 +2119,127 @@ function renderPlayerClassHeatmap() {
21172119 if ( count ) {
21182120 const chars = playerClassCharacters [ player ] [ cls ] || [ ] ;
21192121 const charList = chars
2120- . map ( c => `Path ${ c . path } : ${ c . name } (${ c . classes } )` )
2122+ . map ( c => `Path ${ c . path } : ${ c . name } (${ c . classes } )${ c . race ? ' - ' + c . race : '' } ` )
2123+ . join ( '\n' ) ;
2124+ title = charList ;
2125+ }
2126+ html += `<td style="background-color: ${ color } ; color: ${ textColor } ;" title="${ title } ">${ count || '' } </td>` ;
2127+ } ) ;
2128+ html += '</tr>' ;
2129+ } ) ;
2130+
2131+ html += '</tbody></table>' ;
2132+ container . innerHTML = html ;
2133+ }
2134+
2135+ // Render player folk heatmap (Player's Folk Repertoire)
2136+ function renderPlayerFolkHeatmap ( ) {
2137+ const container = document . getElementById ( 'player-folk-heatmap' ) ;
2138+ if ( ! container ) return ;
2139+
2140+ // Helper to extract base folk from "FOLK (SUBFOLK)" format
2141+ const getBaseFolk = ( race ) => {
2142+ if ( ! race ) return '' ;
2143+ const match = race . match ( / ^ ( [ ^ ( ] + ) / ) ;
2144+ return match ? match [ 1 ] . trim ( ) : race ;
2145+ } ;
2146+
2147+ // Get all unique base folk (without subfolk)
2148+ const baseFolkSet = new Set ( ) ;
2149+ characterData . forEach ( c => {
2150+ if ( c . race ) baseFolkSet . add ( getBaseFolk ( c . race ) ) ;
2151+ } ) ;
2152+ const allBaseFolk = [ ...baseFolkSet ] . sort ( ) ;
2153+
2154+ // Count characters per player per base folk AND collect character details
2155+ const playerFolkData = { } ;
2156+ const playerFolkCharacters = { } ; // Store character details
2157+
2158+ characterData . forEach ( char => {
2159+ const player = char . category || 'Unknown' ;
2160+ const fullRace = char . race ;
2161+ if ( ! fullRace ) return ;
2162+
2163+ const baseFolk = getBaseFolk ( fullRace ) ;
2164+
2165+ if ( ! playerFolkData [ player ] ) {
2166+ playerFolkData [ player ] = { } ;
2167+ playerFolkCharacters [ player ] = { } ;
2168+ }
2169+
2170+ if ( ! playerFolkData [ player ] [ baseFolk ] ) {
2171+ playerFolkData [ player ] [ baseFolk ] = 0 ;
2172+ playerFolkCharacters [ player ] [ baseFolk ] = [ ] ;
2173+ }
2174+
2175+ // Store full character details including full race with subfolk
2176+ const charName = char . shortname || char . name || 'Unknown' ;
2177+ const classDisplay = char . class2 ? `${ char . class } /${ char . class2 } ` : char . class ;
2178+ const charInfo = {
2179+ name : charName ,
2180+ path : char . path ,
2181+ classes : classDisplay ,
2182+ race : fullRace
2183+ } ;
2184+
2185+ playerFolkData [ player ] [ baseFolk ] ++ ;
2186+ playerFolkCharacters [ player ] [ baseFolk ] . push ( charInfo ) ;
2187+ } ) ;
2188+
2189+ // Get top 15 players by total characters
2190+ const topPlayers = Object . entries ( playerFolkData )
2191+ . map ( ( [ player , folk ] ) => ( {
2192+ player,
2193+ total : Object . values ( folk ) . reduce ( ( sum , count ) => sum + count , 0 ) ,
2194+ folk
2195+ } ) )
2196+ . sort ( ( a , b ) => b . total - a . total )
2197+ . slice ( 0 , 15 ) ;
2198+
2199+ // Detect dark mode
2200+ const isDarkMode = document . documentElement . classList . contains ( 'dark-mode' ) ;
2201+
2202+ // Find max count for color scaling
2203+ const maxCount = Math . max ( ...topPlayers . flatMap ( p => Object . values ( p . folk ) ) ) ;
2204+
2205+ // Helper to get color intensity (matching Level Duration Analysis style)
2206+ const getColorIntensity = ( count ) => {
2207+ if ( ! count ) return isDarkMode ? '#334155' : '#f8f9fa' ; // Empty cells match first column
2208+ const normalized = count / maxCount ;
2209+
2210+ if ( isDarkMode ) {
2211+ // Dark mode: cyan to orange heat map
2212+ const hue = 200 - ( normalized * 85 ) ; // 200 (cyan) to 30 (orange)
2213+ const saturation = 65 + ( normalized * 15 ) ; // 65% to 80%
2214+ const lightness = 35 + ( normalized * 15 ) ; // 35% to 50%
2215+ return `hsl(${ hue } , ${ saturation } %, ${ lightness } %)` ;
2216+ } else {
2217+ // Light mode: blue gradient
2218+ const hue = 210 ; // Blue
2219+ const lightness = 85 - ( normalized * 30 ) ; // 85% to 55%
2220+ return `hsl(${ hue } , 80%, ${ lightness } %)` ;
2221+ }
2222+ } ;
2223+
2224+ // Build HTML table
2225+ let html = '<table class="player-folk-heatmap-table"><thead><tr><th>Player</th>' ;
2226+ allBaseFolk . forEach ( folk => {
2227+ html += `<th><span>${ folk } </span></th>` ;
2228+ } ) ;
2229+ html += '</tr></thead><tbody>' ;
2230+
2231+ const textColor = isDarkMode ? 'rgba(255, 255, 255, 0.95)' : 'rgba(0, 0, 0, 0.87)' ;
2232+
2233+ topPlayers . forEach ( ( { player, folk } ) => {
2234+ html += `<tr><td class="player-name" style="border-left: 4px solid ${ window . CampaignData . getPlayerColor ( player ) . replace ( '0.7' , '1' ) } ">${ player } </td>` ;
2235+ allBaseFolk . forEach ( f => {
2236+ const count = folk [ f ] || 0 ;
2237+ const color = getColorIntensity ( count ) ;
2238+ let title = '' ;
2239+ if ( count ) {
2240+ const chars = playerFolkCharacters [ player ] [ f ] || [ ] ;
2241+ const charList = chars
2242+ . map ( c => `Path ${ c . path } : ${ c . name } (${ c . classes } ) - ${ c . race } ` )
21212243 . join ( '\n' ) ;
21222244 title = charList ;
21232245 }
@@ -2148,4 +2270,10 @@ window.addEventListener('darkModeChanged', function(event) {
21482270 console . log ( '[CAMPAIGN-STATS] Re-rendering Player Class Heatmap' ) ;
21492271 renderPlayerClassHeatmap ( ) ;
21502272 }
2273+
2274+ // Re-render Player Folk Heatmap
2275+ if ( typeof characterData !== 'undefined' && characterData . length > 0 ) {
2276+ console . log ( '[CAMPAIGN-STATS] Re-rendering Player Folk Heatmap' ) ;
2277+ renderPlayerFolkHeatmap ( ) ;
2278+ }
21512279} ) ;
0 commit comments