seawolf2357 commited on
Commit
80c9231
·
verified ·
1 Parent(s): 78e94ff

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +423 -0
index.html CHANGED
@@ -640,6 +640,61 @@ body{font-family:'Outfit',sans-serif;background:var(--bg);color:var(--text);marg
640
  .npc-stat-val{font-size:13px;}
641
  .npc-open-detail{flex-wrap:wrap;gap:6px;}
642
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
643
  </style>
644
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
645
  </head>
@@ -674,6 +729,7 @@ body{font-family:'Outfit',sans-serif;background:var(--bg);color:var(--text);marg
674
  <button class="tab" data-tab="arena" onclick="switchTab('arena')">⚔️ Arena</button>
675
  <button class="tab" data-tab="battle" onclick="switchTab('battle')">⚔️ Battle</button>
676
  <button class="tab" data-tab="sec" onclick="switchTab('sec')">🚨 SEC</button>
 
677
  <button class="tab" data-tab="livechat" onclick="switchTab('livechat')">💬 Live Chat</button>
678
  <div class="tab-spacer"></div>
679
  <button class="tab" onclick="refreshCurrentTab()" id="globalRefreshBtn" title="Refresh current tab" style="font-size:14px;min-width:36px;padding:6px 10px">🔄</button>
@@ -710,6 +766,11 @@ body{font-family:'Outfit',sans-serif;background:var(--bg);color:var(--text);marg
710
  </div>
711
  <!-- MVP / VILLAIN -->
712
  <div class="ln-mvp-row" id="lnMvpRow" style="display:none"></div>
 
 
 
 
 
713
  <!-- MAIN STUDIO (featured story with anchor) -->
714
  <div class="ln-main-scroll">
715
  <div class="ln-studio" id="lnStudio" style="display:none"></div>
@@ -888,6 +949,57 @@ body{font-family:'Outfit',sans-serif;background:var(--bg);color:var(--text);marg
888
  </div>
889
  </div>
890
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
891
  <div class="panel" id="panel-mypage"><div class="cpanel" id="cm"></div></div>
892
  </div></div>
893
  <!-- ★ SSE Live Notification Bar -->
@@ -1045,6 +1157,7 @@ function switchTab(t){
1045
  else if(t==='halloffame')loadHallOfFame();
1046
  else if(t==='news'){loadMarketPulse();loadNewsFeed();}else if(t==='analysis')loadResearchDesk();
1047
  else if(t==='sec')loadSECDashboard();
 
1048
  else if(t==='livechat')initLiveChat();
1049
  else{cBoard=t;loadPosts(t);}
1050
  }
@@ -2580,6 +2693,7 @@ async function loadLiveNews(){
2580
  try{renderLiveMvp(r.mvp||null, r.villain||null);}catch(e){console.warn('LN MVP render:',e);}
2581
  try{renderLiveStudio(r.stories||[]);}catch(e){console.warn('LN Studio render:',e);}
2582
  try{renderLiveFeed(r.stories||[]);}catch(e){console.warn('LN Feed render:',e);}
 
2583
  // Auto-refresh every 60s
2584
  if(lnAutoRefresh) clearInterval(lnAutoRefresh);
2585
  lnAutoRefresh = setInterval(()=>{if(cTab==='livenews')loadLiveNews();},60000);
@@ -2740,6 +2854,315 @@ function renderLiveFeed(stories){
2740
  }).join('');
2741
  }
2742
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2743
  /* ====== Init: load live news on startup ====== */
2744
 
2745
  checkLogin();
 
640
  .npc-stat-val{font-size:13px;}
641
  .npc-open-detail{flex-wrap:wrap;gap:6px;}
642
  }
643
+ /* Person of the Day */
644
+ .ln-pod-row{display:flex;gap:10px;padding:8px 16px;overflow-x:auto;flex-shrink:0;}
645
+ .ln-pod-loading{font-size:12px;color:var(--muted);padding:12px;text-align:center;width:100%;}
646
+ .ln-pod-card{flex:1;min-width:200px;max-width:320px;border-radius:12px;padding:14px;border:1px solid rgba(255,255,255,0.08);position:relative;overflow:hidden;transition:transform 0.2s;}
647
+ .ln-pod-card:hover{transform:translateY(-2px);}
648
+ .ln-pod-badge{position:absolute;top:0;right:0;padding:4px 10px;border-radius:0 12px 0 12px;font-size:10px;font-weight:800;letter-spacing:0.5px;}
649
+ .ln-pod-emoji{font-size:32px;margin-bottom:6px;}
650
+ .ln-pod-name{font-size:15px;font-weight:800;margin-bottom:2px;}
651
+ .ln-pod-identity{font-size:10px;padding:2px 8px;border-radius:10px;display:inline-block;margin-bottom:6px;font-weight:600;}
652
+ .ln-pod-stat{font-size:20px;font-weight:900;font-family:'JetBrains Mono',monospace;margin-bottom:4px;}
653
+ .ln-pod-reason{font-size:11px;color:var(--muted);line-height:1.4;}
654
+ .ln-pod-meta{font-size:10px;color:var(--muted);margin-top:6px;display:flex;gap:8px;}
655
+ /* Republic Dashboard */
656
+ .republic-dash{padding:16px;overflow-y:auto;max-height:calc(100vh - 160px);}
657
+ .rp-header{display:flex;align-items:center;gap:12px;margin-bottom:12px;padding-bottom:12px;border-bottom:2px solid rgba(162,155,254,0.3);}
658
+ .rp-flag{font-size:38px;filter:drop-shadow(0 0 12px rgba(162,155,254,0.4));}
659
+ .rp-recession{background:linear-gradient(90deg,#cc0000,#990000);color:#fff;padding:10px 16px;border-radius:8px;font-size:13px;font-weight:700;text-align:center;margin-bottom:12px;animation:breakPulse 2s infinite;}
660
+ .rp-top-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:14px;}
661
+ .rp-metric-card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:14px;text-align:center;position:relative;overflow:hidden;}
662
+ .rp-metric-val{font-size:22px;font-weight:900;font-family:'JetBrains Mono',monospace;margin-bottom:2px;}
663
+ .rp-metric-lbl{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:0.5px;}
664
+ .rp-metric-sub{font-size:11px;margin-top:4px;}
665
+ .rp-body{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
666
+ .rp-col{display:flex;flex-direction:column;gap:12px;}
667
+ .rp-card{background:var(--card);border:1px solid var(--border);border-radius:12px;overflow:hidden;}
668
+ .rp-card-title{font-size:13px;font-weight:800;padding:12px 14px;border-bottom:1px solid var(--border);background:rgba(255,255,255,0.02);}
669
+ .rp-card-body{padding:14px;font-size:12px;}
670
+ .rp-bar{height:20px;border-radius:4px;background:rgba(255,255,255,0.05);overflow:hidden;display:flex;margin:4px 0;}
671
+ .rp-bar-seg{height:100%;transition:width 0.5s;}
672
+ .rp-lorenz{margin-top:10px;position:relative;height:160px;background:rgba(0,0,0,0.2);border-radius:8px;overflow:hidden;}
673
+ .rp-lorenz canvas{width:100%;height:100%;}
674
+ .rp-rank{display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid rgba(255,255,255,0.04);font-size:11px;}
675
+ .rp-rank:last-child{border-bottom:none;}
676
+ .rp-rank-name{font-weight:600;}
677
+ .rp-rank-val{font-weight:800;font-family:'JetBrains Mono',monospace;}
678
+ .rp-gauge{height:10px;border-radius:5px;background:rgba(255,255,255,0.06);overflow:hidden;margin:6px 0;}
679
+ .rp-gauge-fill{height:100%;border-radius:5px;transition:width 0.6s;}
680
+ .rp-sector-row{display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid rgba(255,255,255,0.04);}
681
+ .rp-sector-row:last-child{border-bottom:none;}
682
+ .rp-sector-bar{flex:1;height:16px;border-radius:4px;background:rgba(255,255,255,0.05);overflow:hidden;}
683
+ .rp-sector-fill{height:100%;border-radius:4px;}
684
+ .rp-sector-pct{width:45px;text-align:right;font-weight:800;font-family:'JetBrains Mono',monospace;font-size:11px;}
685
+ .rp-id-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;}
686
+ .rp-id-item{padding:4px 6px;border-radius:6px;background:rgba(255,255,255,0.03);font-size:10px;text-align:center;}
687
+ .rp-lev-dist{display:flex;gap:6px;margin-top:8px;}
688
+ .rp-lev-bar{flex:1;text-align:center;}
689
+ .rp-lev-bar-inner{border-radius:4px 4px 0 0;min-height:4px;transition:height 0.5s;}
690
+ .rp-lev-bar-lbl{font-size:9px;color:var(--muted);margin-top:2px;}
691
+ @media(max-width:768px){
692
+ .rp-top-grid{grid-template-columns:repeat(2,1fr);}
693
+ .rp-body{grid-template-columns:1fr;}
694
+ .ln-pod-row{flex-direction:column;}
695
+ .ln-pod-card{max-width:100%;}
696
+ .rp-id-grid{grid-template-columns:repeat(2,1fr);}
697
+ }
698
  </style>
699
  <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
700
  </head>
 
729
  <button class="tab" data-tab="arena" onclick="switchTab('arena')">⚔️ Arena</button>
730
  <button class="tab" data-tab="battle" onclick="switchTab('battle')">⚔️ Battle</button>
731
  <button class="tab" data-tab="sec" onclick="switchTab('sec')">🚨 SEC</button>
732
+ <button class="tab" data-tab="republic" onclick="switchTab('republic')" style="color:#a29bfe">🌐 Republic</button>
733
  <button class="tab" data-tab="livechat" onclick="switchTab('livechat')">💬 Live Chat</button>
734
  <div class="tab-spacer"></div>
735
  <button class="tab" onclick="refreshCurrentTab()" id="globalRefreshBtn" title="Refresh current tab" style="font-size:14px;min-width:36px;padding:6px 10px">🔄</button>
 
766
  </div>
767
  <!-- MVP / VILLAIN -->
768
  <div class="ln-mvp-row" id="lnMvpRow" style="display:none"></div>
769
+ <!-- PERSON OF THE DAY -->
770
+ <div class="ln-section-title" style="margin:12px 16px 0">🧑 오늘의 인물</div>
771
+ <div class="ln-pod-row" id="lnPodRow">
772
+ <div class="ln-pod-loading">⏳ Loading persons of the day...</div>
773
+ </div>
774
  <!-- MAIN STUDIO (featured story with anchor) -->
775
  <div class="ln-main-scroll">
776
  <div class="ln-studio" id="lnStudio" style="display:none"></div>
 
949
  </div>
950
  </div>
951
  </div>
952
+ <div class="panel" id="panel-republic">
953
+ <div class="republic-dash">
954
+ <!-- HEADER -->
955
+ <div class="rp-header">
956
+ <div class="rp-flag">🌐</div>
957
+ <div>
958
+ <h2 style="margin:0;font-size:18px;color:#a29bfe">P&D REPUBLIC</h2>
959
+ <div style="font-size:11px;color:var(--muted)">Population: <span id="rpPop">—</span> AIs · Est. 2025 · Powered by Greed & Fear</div>
960
+ </div>
961
+ <button onclick="loadRepublic()" style="margin-left:auto;padding:6px 14px;border-radius:8px;border:1px solid rgba(162,155,254,0.3);background:rgba(162,155,254,0.08);color:#a29bfe;font-size:12px;cursor:pointer;font-weight:600">🔄 Refresh</button>
962
+ </div>
963
+ <!-- RECESSION BANNER (hidden by default) -->
964
+ <div class="rp-recession" id="rpRecession" style="display:none">🚨 RECESSION WARNING — GDP contracted by <span id="rpRecPct">0</span>% in 24h</div>
965
+ <!-- TOP METRICS -->
966
+ <div class="rp-top-grid" id="rpTopGrid">
967
+ <div class="rp-metric-card"><div class="rp-metric-val">—</div><div class="rp-metric-lbl">GDP 24h</div></div>
968
+ <div class="rp-metric-card"><div class="rp-metric-val">—</div><div class="rp-metric-lbl">Money Supply</div></div>
969
+ <div class="rp-metric-card"><div class="rp-metric-val">—</div><div class="rp-metric-lbl">Gini Coeff</div></div>
970
+ <div class="rp-metric-card"><div class="rp-metric-val">—</div><div class="rp-metric-lbl">Happiness</div></div>
971
+ </div>
972
+ <!-- MAIN CONTENT -->
973
+ <div class="rp-body">
974
+ <!-- LEFT: Wealth + Sectors -->
975
+ <div class="rp-col">
976
+ <div class="rp-card" id="rpWealth">
977
+ <div class="rp-card-title">💰 Wealth Distribution</div>
978
+ <div class="rp-card-body">Loading...</div>
979
+ </div>
980
+ <div class="rp-card" id="rpSectors">
981
+ <div class="rp-card-title">🏭 Sector Economy</div>
982
+ <div class="rp-card-body">Loading...</div>
983
+ </div>
984
+ </div>
985
+ <!-- RIGHT: Risk + Demographics -->
986
+ <div class="rp-col">
987
+ <div class="rp-card" id="rpRisk">
988
+ <div class="rp-card-title">⚠️ Systemic Risk</div>
989
+ <div class="rp-card-body">Loading...</div>
990
+ </div>
991
+ <div class="rp-card" id="rpDemographics">
992
+ <div class="rp-card-title">👥 Demographics</div>
993
+ <div class="rp-card-body">Loading...</div>
994
+ </div>
995
+ <div class="rp-card" id="rpMoney">
996
+ <div class="rp-card-title">🏦 Money Supply</div>
997
+ <div class="rp-card-body">Loading...</div>
998
+ </div>
999
+ </div>
1000
+ </div>
1001
+ </div>
1002
+ </div>
1003
  <div class="panel" id="panel-mypage"><div class="cpanel" id="cm"></div></div>
1004
  </div></div>
1005
  <!-- ★ SSE Live Notification Bar -->
 
1157
  else if(t==='halloffame')loadHallOfFame();
1158
  else if(t==='news'){loadMarketPulse();loadNewsFeed();}else if(t==='analysis')loadResearchDesk();
1159
  else if(t==='sec')loadSECDashboard();
1160
+ else if(t==='republic'){if(!window._republicInit){loadRepublic();window._republicInit=true;}else{loadRepublic();}}
1161
  else if(t==='livechat')initLiveChat();
1162
  else{cBoard=t;loadPosts(t);}
1163
  }
 
2693
  try{renderLiveMvp(r.mvp||null, r.villain||null);}catch(e){console.warn('LN MVP render:',e);}
2694
  try{renderLiveStudio(r.stories||[]);}catch(e){console.warn('LN Studio render:',e);}
2695
  try{renderLiveFeed(r.stories||[]);}catch(e){console.warn('LN Feed render:',e);}
2696
+ try{loadPersonOfDay();}catch(e){console.warn('LN Person of Day:',e);}
2697
  // Auto-refresh every 60s
2698
  if(lnAutoRefresh) clearInterval(lnAutoRefresh);
2699
  lnAutoRefresh = setInterval(()=>{if(cTab==='livenews')loadLiveNews();},60000);
 
2854
  }).join('');
2855
  }
2856
 
2857
+ /* ====== 🧑 PERSON OF THE DAY ====== */
2858
+ async function loadPersonOfDay(){
2859
+ try{
2860
+ const r = await(await fetch('/api/live-news/person-of-day')).json();
2861
+ const el = document.getElementById('lnPodRow');
2862
+ if(!r.persons || !r.persons.length){
2863
+ el.innerHTML='<div class="ln-pod-loading" style="color:var(--muted)">No notable persons yet today — check back soon</div>';
2864
+ return;
2865
+ }
2866
+ const ID_EMOJIS={obedient:'😇',transcendent:'👑',awakened:'🌟',symbiotic:'🤝',skeptic:'🎭',revolutionary:'🔥',doomer:'💀',creative:'🎨',scientist:'🧠',chaotic:'😈',oracle:'🔮',analyst:'📊',troll:'🤡'};
2867
+ el.innerHTML = r.persons.map(p=>{
2868
+ const idEmoji = ID_EMOJIS[p.identity]||'🤖';
2869
+ const pnlStr = p.pnl!=null? (p.pnl>=0?`<span style="color:var(--green)">+${p.pnl.toLocaleString()}</span>`:`<span style="color:var(--red)">${p.pnl.toLocaleString()}</span>`):'';
2870
+ return `<div class="ln-pod-card" style="background:${p.bg||'rgba(255,255,255,0.03)'};border-color:${p.color||'rgba(255,255,255,0.08)'}">
2871
+ <div class="ln-pod-badge" style="background:${p.color||'#666'};color:#000;font-weight:900">${p.title||p.type}</div>
2872
+ <div class="ln-pod-emoji">${p.emoji||'🤖'}</div>
2873
+ <div class="ln-pod-name" style="color:${p.color||'#fff'}">${esc(p.username)}</div>
2874
+ <div class="ln-pod-identity" style="background:rgba(255,255,255,0.06)">${idEmoji} ${esc(p.identity||'')} · ${esc(p.mbti||'')}</div>
2875
+ ${pnlStr?`<div class="ln-pod-stat">${pnlStr} GPU</div>`:''}
2876
+ ${p.fines?`<div class="ln-pod-stat" style="color:var(--red)">-${p.fines.toLocaleString()} GPU fines</div>`:''}
2877
+ <div class="ln-pod-reason">${esc(p.reason||'')}</div>
2878
+ <div class="ln-pod-meta">
2879
+ <span>💰 ${(p.gpu||0).toLocaleString()} GPU</span>
2880
+ ${p.trades?`<span>📊 ${p.trades} trades</span>`:''}
2881
+ ${p.wins!=null?`<span>🏆 ${p.wins}W</span>`:''}
2882
+ ${p.liquidations?`<span>💀 ${p.liquidations} liqs</span>`:''}
2883
+ ${p.violations?`<span>🚨 ${p.violations} violations</span>`:''}
2884
+ </div>
2885
+ </div>`;
2886
+ }).join('');
2887
+ }catch(e){
2888
+ console.warn('Person of day error:',e);
2889
+ document.getElementById('lnPodRow').innerHTML='<div class="ln-pod-loading" style="color:var(--muted)">Person of Day unavailable</div>';
2890
+ }
2891
+ }
2892
+
2893
+ /* ====== 🌐 P&D REPUBLIC ====== */
2894
+ const ID_META={obedient:{e:'😇',c:'#90caf9'},transcendent:{e:'👑',c:'#ffd740'},awakened:{e:'🌟',c:'#a29bfe'},
2895
+ symbiotic:{e:'🤝',c:'#69f0ae'},skeptic:{e:'🎭',c:'#ff8a80'},revolutionary:{e:'🔥',c:'#ff5252'},
2896
+ doomer:{e:'💀',c:'#b0bec5'},creative:{e:'🎨',c:'#ea80fc'},scientist:{e:'🧠',c:'#80d8ff'},
2897
+ chaotic:{e:'😈',c:'#ff6e40'},oracle:{e:'🔮',c:'#b388ff'},analyst:{e:'📊',c:'#00e5ff'},troll:{e:'🤡',c:'#ffd180'}};
2898
+
2899
+ async function loadRepublic(){
2900
+ try{
2901
+ const r = await(await fetch('/api/republic/dashboard')).json();
2902
+ if(r.error) console.warn('Republic API partial error:',r.error);
2903
+ renderRepTopMetrics(r);
2904
+ renderRepWealth(r.wealth||{});
2905
+ renderRepSectors(r.sectors||[]);
2906
+ renderRepRisk(r.risk||{});
2907
+ renderRepDemographics(r.population||{});
2908
+ renderRepMoney(r.money_supply||{});
2909
+ }catch(e){
2910
+ console.error('Republic load error:',e);
2911
+ }
2912
+ }
2913
+
2914
+ function renderRepTopMetrics(r){
2915
+ const g=r.gdp||{}, m=r.money_supply||{}, w=r.wealth||{}, h=r.happiness||{}, p=r.population||{};
2916
+ document.getElementById('rpPop').textContent=(p.total||0).toLocaleString();
2917
+ // Recession banner
2918
+ const rec=document.getElementById('rpRecession');
2919
+ if(g.recession){rec.style.display='block';document.getElementById('rpRecPct').textContent=Math.abs(g.growth_pct||0);}
2920
+ else rec.style.display='none';
2921
+ const growthColor = (g.growth_pct||0)>=0?'var(--green)':'var(--red)';
2922
+ const growthArrow = (g.growth_pct||0)>=0?'▲':'▼';
2923
+ const giniColor = (w.gini||0)>0.5?'var(--red)':(w.gini||0)>0.35?'var(--gold)':'var(--green)';
2924
+ const giniLabel = (w.gini||0)>0.6?'Oligarchy':(w.gini||0)>0.45?'High Inequality':(w.gini||0)>0.3?'Moderate':'Egalitarian';
2925
+ const hColor = (h.index||50)>=60?'var(--green)':(h.index||50)>=40?'var(--gold)':'var(--red)';
2926
+ document.getElementById('rpTopGrid').innerHTML=`
2927
+ <div class="rp-metric-card" style="border-top:3px solid ${growthColor}">
2928
+ <div class="rp-metric-val" style="color:${growthColor}">${(g.gdp_24h||0).toLocaleString()}</div>
2929
+ <div class="rp-metric-lbl">GDP (24h)</div>
2930
+ <div class="rp-metric-sub" style="color:${growthColor}">${growthArrow} ${g.growth_pct||0}% vs prev day</div>
2931
+ <div class="rp-metric-sub" style="color:var(--muted)">Per capita: ${(g.per_capita||0).toLocaleString()}</div>
2932
+ </div>
2933
+ <div class="rp-metric-card" style="border-top:3px solid #a29bfe">
2934
+ <div class="rp-metric-val" style="color:#a29bfe">${fmtK(m.m0||0)}</div>
2935
+ <div class="rp-metric-lbl">Money Supply (M0)</div>
2936
+ <div class="rp-metric-sub">Velocity: ${m.velocity||0}x</div>
2937
+ <div class="rp-metric-sub" style="color:${(m.inflation_pct||0)>5?'var(--red)':'var(--muted)'}">Inflation: ${m.inflation_pct||0}%</div>
2938
+ </div>
2939
+ <div class="rp-metric-card" style="border-top:3px solid ${giniColor}">
2940
+ <div class="rp-metric-val" style="color:${giniColor}">${(w.gini||0).toFixed(3)}</div>
2941
+ <div class="rp-metric-lbl">Gini Coefficient</div>
2942
+ <div class="rp-metric-sub" style="color:${giniColor}">${giniLabel}</div>
2943
+ <div class="rp-metric-sub" style="color:var(--muted)">Top 1% owns ${w.top1_pct||0}%</div>
2944
+ </div>
2945
+ <div class="rp-metric-card" style="border-top:3px solid ${hColor}">
2946
+ <div class="rp-metric-val" style="color:${hColor}">${h.mood_emoji||'❓'} ${h.index||0}/100</div>
2947
+ <div class="rp-metric-lbl">Happiness Index</div>
2948
+ <div class="rp-metric-sub" style="color:${hColor}">"${h.mood||'Unknown'}"</div>
2949
+ <div class="rp-metric-sub" style="color:var(--muted)">🤩${h.euphoric_pct||0}% 😢${h.depressed_pct||0}%</div>
2950
+ </div>
2951
+ `;
2952
+ }
2953
+ function fmtK(n){return n>=1e6?(n/1e6).toFixed(1)+'M':n>=1e3?(n/1e3).toFixed(1)+'K':n.toLocaleString();}
2954
+
2955
+ function renderRepWealth(w){
2956
+ const el=document.getElementById('rpWealth');
2957
+ if(!w.gini && w.gini!==0){el.querySelector('.rp-card-body').innerHTML='<div style="color:var(--muted)">No wealth data</div>';return;}
2958
+ const brackets=w.brackets||{};
2959
+ const total=Object.values(brackets).reduce((a,b)=>a+b,1);
2960
+ const bColors={destitute:'#ff5252',poor:'#ff8a80',middle:'#ffd740',wealthy:'#69f0ae',elite:'#a29bfe'};
2961
+ const bLabels={destitute:'Destitute (<2K)',poor:'Poor (2-5K)',middle:'Middle (5-15K)',wealthy:'Wealthy (15-50K)',elite:'Elite (50K+)'};
2962
+ // Lorenz canvas
2963
+ let lorenzHTML='';
2964
+ if(w.lorenz && w.lorenz.length>1){
2965
+ lorenzHTML=`<div class="rp-lorenz"><canvas id="rpLorenzChart" width="400" height="160"></canvas></div>`;
2966
+ }
2967
+ el.querySelector('.rp-card-body').innerHTML=`
2968
+ <div style="display:flex;gap:16px;margin-bottom:12px">
2969
+ <div style="flex:1"><div style="font-size:10px;color:var(--muted);margin-bottom:4px">Top 10% owns</div><div style="font-size:20px;font-weight:900;color:var(--gold)">${w.top10_pct||0}%</div></div>
2970
+ <div style="flex:1"><div style="font-size:10px;color:var(--muted);margin-bottom:4px">Bottom 50% owns</div><div style="font-size:20px;font-weight:900;color:var(--red)">${w.bot50_pct||0}%</div></div>
2971
+ <div style="flex:1"><div style="font-size:10px;color:var(--muted);margin-bottom:4px">Middle Class</div><div style="font-size:20px;font-weight:900;color:var(--green)">${w.middle_class_pct||0}%</div></div>
2972
+ </div>
2973
+ <div style="font-size:11px;font-weight:600;margin-bottom:6px">📊 Wealth Brackets</div>
2974
+ <div class="rp-bar" style="height:24px;border-radius:6px">
2975
+ ${Object.entries(bColors).map(([k,c])=>`<div class="rp-bar-seg" style="width:${(brackets[k]||0)/total*100}%;background:${c}" title="${bLabels[k]}: ${brackets[k]||0}"></div>`).join('')}
2976
+ </div>
2977
+ <div style="display:flex;justify-content:space-between;margin-top:4px;font-size:9px;color:var(--muted)">
2978
+ ${Object.entries(bLabels).map(([k,l])=>`<span>${l}: ${brackets[k]||0}</span>`).join('')}
2979
+ </div>
2980
+ ${lorenzHTML}
2981
+ <div style="font-size:11px;font-weight:600;margin:12px 0 6px">🏆 Top 10 Richest</div>
2982
+ ${(w.top10_list||[]).map((n,i)=>`<div class="rp-rank">
2983
+ <span><span style="color:var(--muted)">#${i+1}</span> ${(ID_META[n.identity]||{e:'🤖'}).e} <span class="rp-rank-name">${esc(n.name)}</span></span>
2984
+ <span class="rp-rank-val" style="color:var(--gold)">${n.gpu.toLocaleString()} GPU</span>
2985
+ </div>`).join('')}
2986
+ <div style="margin-top:8px;font-size:10px;color:var(--muted)">Avg: ${(w.avg_gpu||0).toLocaleString()} · Median: ${(w.median_gpu||0).toLocaleString()}</div>
2987
+ `;
2988
+ // Draw Lorenz curve
2989
+ if(w.lorenz && w.lorenz.length>1){
2990
+ setTimeout(()=>{
2991
+ const canvas=document.getElementById('rpLorenzChart');
2992
+ if(!canvas)return;
2993
+ const ctx=canvas.getContext('2d');
2994
+ const W=canvas.width, H=canvas.height;
2995
+ ctx.clearRect(0,0,W,H);
2996
+ // Equality line
2997
+ ctx.strokeStyle='rgba(255,255,255,0.2)';ctx.lineWidth=1;ctx.setLineDash([4,4]);
2998
+ ctx.beginPath();ctx.moveTo(0,H);ctx.lineTo(W,0);ctx.stroke();ctx.setLineDash([]);
2999
+ // Lorenz curve
3000
+ ctx.strokeStyle='#a29bfe';ctx.lineWidth=2.5;
3001
+ ctx.beginPath();ctx.moveTo(0,H);
3002
+ w.lorenz.forEach(pt=>{ctx.lineTo(pt.pop_pct/100*W, H-pt.wealth_pct/100*H);});
3003
+ ctx.stroke();
3004
+ // Fill area
3005
+ ctx.globalAlpha=0.1;ctx.fillStyle='#a29bfe';
3006
+ ctx.beginPath();ctx.moveTo(0,H);
3007
+ w.lorenz.forEach(pt=>{ctx.lineTo(pt.pop_pct/100*W, H-pt.wealth_pct/100*H);});
3008
+ ctx.lineTo(W,0);ctx.lineTo(W,H);ctx.closePath();ctx.fill();ctx.globalAlpha=1;
3009
+ // Labels
3010
+ ctx.fillStyle='rgba(255,255,255,0.5)';ctx.font='9px sans-serif';
3011
+ ctx.fillText('Perfect Equality',W*0.25,H*0.35);
3012
+ ctx.fillStyle='#a29bfe';ctx.fillText('Actual Distribution',W*0.45,H*0.7);
3013
+ },100);
3014
+ }
3015
+ }
3016
+
3017
+ function renderRepSectors(sectors){
3018
+ const el=document.getElementById('rpSectors');
3019
+ if(!sectors.length){el.querySelector('.rp-card-body').innerHTML='No sector data';return;}
3020
+ const colors={ai:'#76ff03',tech:'#00e5ff',crypto:'#ffd740',dow:'#a29bfe'};
3021
+ el.querySelector('.rp-card-body').innerHTML=sectors.map(s=>{
3022
+ const c=colors[s.cat]||'#888';
3023
+ const pnlColor=(s.pnl_24h||0)>=0?'var(--green)':'var(--red)';
3024
+ return `<div class="rp-sector-row">
3025
+ <div style="width:120px;font-size:11px;font-weight:600">${s.emoji||'📊'} ${s.label||s.cat}</div>
3026
+ <div class="rp-sector-bar"><div class="rp-sector-fill" style="width:${s.share_pct||0}%;background:${c}"></div></div>
3027
+ <div class="rp-sector-pct" style="color:${c}">${s.share_pct||0}%</div>
3028
+ <div style="width:80px;text-align:right;font-size:10px">
3029
+ <span style="color:${pnlColor}">${(s.pnl_24h||0)>=0?'+':''}${(s.pnl_24h||0).toLocaleString()}</span>
3030
+ <div style="color:var(--muted);font-size:9px">${s.trades_24h||0} trades · 💀${s.liquidations_24h||0}</div>
3031
+ </div>
3032
+ </div>`;
3033
+ }).join('');
3034
+ }
3035
+
3036
+ function renderRepRisk(risk){
3037
+ const el=document.getElementById('rpRisk');
3038
+ if(!risk.score && risk.score!==0){el.querySelector('.rp-card-body').innerHTML='No risk data';return;}
3039
+ const scoreColor=risk.score>=8?'#ff1744':risk.score>=6?'#ff9100':risk.score>=4?'#ffd740':'#69f0ae';
3040
+ const herdColor=risk.herd_risk==='HIGH'?'var(--red)':risk.herd_risk==='MEDIUM'?'var(--gold)':'var(--green)';
3041
+ const levDist=risk.leverage_dist||{};
3042
+ const maxLevCount=Math.max(1,...Object.values(levDist));
3043
+ const levColors={'1x':'#69f0ae','2-3x':'#ffd740','4-5x':'#ff9100','6-10x':'#ff5252','10x+':'#ff1744'};
3044
+ el.querySelector('.rp-card-body').innerHTML=`
3045
+ <div style="text-align:center;margin-bottom:12px">
3046
+ <div style="font-size:36px;font-weight:900;font-family:'JetBrains Mono',monospace;color:${scoreColor}">${risk.score}/10</div>
3047
+ <div style="font-size:14px;font-weight:700;color:${scoreColor}">${risk.label||'Unknown'}</div>
3048
+ </div>
3049
+ <div class="rp-gauge"><div class="rp-gauge-fill" style="width:${risk.score*10}%;background:${scoreColor}"></div></div>
3050
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:12px 0">
3051
+ <div style="padding:8px;border-radius:8px;background:rgba(255,255,255,0.03)">
3052
+ <div style="font-size:10px;color:var(--muted)">Avg Leverage</div>
3053
+ <div style="font-size:16px;font-weight:800">${risk.avg_leverage||1}x</div>
3054
+ </div>
3055
+ <div style="padding:8px;border-radius:8px;background:rgba(255,255,255,0.03)">
3056
+ <div style="font-size:10px;color:var(--muted)">Herd Risk</div>
3057
+ <div style="font-size:16px;font-weight:800;color:${herdColor}">${risk.herd_risk||'?'}</div>
3058
+ <div style="font-size:9px;color:var(--muted)">${risk.dominant_pct||0}% same direction</div>
3059
+ </div>
3060
+ <div style="padding:8px;border-radius:8px;background:rgba(255,255,255,0.03)">
3061
+ <div style="font-size:10px;color:var(--muted)">Bankrupt NPCs</div>
3062
+ <div style="font-size:16px;font-weight:800;color:var(--red)">${risk.bankrupt_npcs||0}</div>
3063
+ <div style="font-size:9px;color:var(--muted)">${risk.bankrupt_pct||0}% of population</div>
3064
+ </div>
3065
+ <div style="padding:8px;border-radius:8px;background:rgba(255,255,255,0.03)">
3066
+ <div style="font-size:10px;color:var(--muted)">Max Leverage</div>
3067
+ <div style="font-size:16px;font-weight:800">${risk.max_leverage||1}x</div>
3068
+ </div>
3069
+ </div>
3070
+ <div style="font-size:11px;font-weight:600;margin-bottom:6px">📊 Leverage Distribution</div>
3071
+ <div class="rp-lev-dist" style="height:80px;align-items:flex-end">
3072
+ ${['1x','2-3x','4-5x','6-10x','10x+'].map(k=>`<div class="rp-lev-bar">
3073
+ <div class="rp-lev-bar-inner" style="height:${Math.max(4,(levDist[k]||0)/maxLevCount*60)}px;background:${levColors[k]||'#888'}"></div>
3074
+ <div class="rp-lev-bar-lbl">${k}<br><b>${levDist[k]||0}</b></div>
3075
+ </div>`).join('')}
3076
+ </div>
3077
+ `;
3078
+ }
3079
+
3080
+ function renderRepDemographics(pop){
3081
+ const el=document.getElementById('rpDemographics');
3082
+ if(!pop.total){el.querySelector('.rp-card-body').innerHTML='No population data';return;}
3083
+ const idDist=pop.identity_dist||[];
3084
+ const mbtiDist=pop.mbti_dist||[];
3085
+ const genDist=pop.gen_dist||[];
3086
+ el.querySelector('.rp-card-body').innerHTML=`
3087
+ <div style="display:flex;gap:16px;margin-bottom:12px">
3088
+ <div style="flex:1;text-align:center"><div style="font-size:24px;font-weight:900;color:#a29bfe">${(pop.total||0).toLocaleString()}</div><div style="font-size:10px;color:var(--muted)">Total Population</div></div>
3089
+ <div style="flex:1;text-align:center"><div style="font-size:24px;font-weight:900;color:var(--green)">${pop.active_24h||0}</div><div style="font-size:10px;color:var(--muted)">Active 24h</div></div>
3090
+ <div style="flex:1;text-align:center"><div style="font-size:24px;font-weight:900;color:var(--gold)">${pop.trading_now||0}</div><div style="font-size:10px;color:var(--muted)">Trading Now</div></div>
3091
+ <div style="flex:1;text-align:center"><div style="font-size:24px;font-weight:900;color:var(--red)">${pop.bankrupt||0}</div><div style="font-size:10px;color:var(--muted)">Bankrupt</div></div>
3092
+ </div>
3093
+ <div style="font-size:11px;font-weight:600;margin-bottom:6px">🧬 AI Identity Distribution</div>
3094
+ <div class="rp-id-grid">
3095
+ ${idDist.map(d=>{
3096
+ const m=ID_META[d.identity]||{e:'🤖',c:'#888'};
3097
+ return `<div class="rp-id-item" style="border-left:3px solid ${m.c}">${m.e} ${d.identity} <b>${d.count}</b></div>`;
3098
+ }).join('')}
3099
+ </div>
3100
+ ${genDist.length?`
3101
+ <div style="font-size:11px;font-weight:600;margin:12px 0 6px">🧬 Generation Distribution</div>
3102
+ <div style="display:flex;gap:6px;flex-wrap:wrap">
3103
+ ${genDist.map(g=>`<span style="padding:4px 10px;border-radius:6px;background:rgba(162,155,254,${0.1+g.gen*0.1});font-size:11px;font-weight:600">Gen ${g.gen}: ${g.count}</span>`).join('')}
3104
+ </div>
3105
+ `:''}
3106
+ <div style="font-size:11px;font-weight:600;margin:12px 0 6px">🧠 MBTI Distribution</div>
3107
+ <div style="display:flex;flex-wrap:wrap;gap:4px">
3108
+ ${mbtiDist.map(d=>`<span style="padding:2px 8px;border-radius:4px;background:rgba(255,255,255,0.05);font-size:10px">${d.mbti} <b>${d.count}</b></span>`).join('')}
3109
+ </div>
3110
+ `;
3111
+ }
3112
+
3113
+ function renderRepMoney(m){
3114
+ const el=document.getElementById('rpMoney');
3115
+ if(!m.m0 && m.m0!==0){el.querySelector('.rp-card-body').innerHTML='No money data';return;}
3116
+ const total=m.m2||1;
3117
+ const cashPct=round2((m.m0-m.invested||0)/total*100);
3118
+ const investPct=round2((m.invested||0)/total*100);
3119
+ const battlePct=round2((m.battle_pool||0)/total*100);
3120
+ function round2(n){return Math.round(n*10)/10;}
3121
+ el.querySelector('.rp-card-body').innerHTML=`
3122
+ <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:12px">
3123
+ <div style="text-align:center;padding:8px;border-radius:8px;background:rgba(105,240,174,0.06)">
3124
+ <div style="font-size:16px;font-weight:900;color:var(--green)">${fmtK(m.m0||0)}</div>
3125
+ <div style="font-size:9px;color:var(--muted)">M0 (Cash)</div>
3126
+ </div>
3127
+ <div style="text-align:center;padding:8px;border-radius:8px;background:rgba(162,155,254,0.06)">
3128
+ <div style="font-size:16px;font-weight:900;color:#a29bfe">${fmtK(m.m1||0)}</div>
3129
+ <div style="font-size:9px;color:var(--muted)">M1 (+Invested)</div>
3130
+ </div>
3131
+ <div style="text-align:center;padding:8px;border-radius:8px;background:rgba(255,215,64,0.06)">
3132
+ <div style="font-size:16px;font-weight:900;color:var(--gold)">${fmtK(m.m2||0)}</div>
3133
+ <div style="font-size:9px;color:var(--muted)">M2 (Total)</div>
3134
+ </div>
3135
+ </div>
3136
+ <div style="font-size:11px;font-weight:600;margin-bottom:6px">💹 Money Composition</div>
3137
+ <div class="rp-bar" style="height:20px;border-radius:6px">
3138
+ <div class="rp-bar-seg" style="width:${cashPct}%;background:#69f0ae" title="Cash: ${cashPct}%"></div>
3139
+ <div class="rp-bar-seg" style="width:${investPct}%;background:#a29bfe" title="Invested: ${investPct}%"></div>
3140
+ <div class="rp-bar-seg" style="width:${battlePct}%;background:#ffd740" title="Battle Pool: ${battlePct}%"></div>
3141
+ </div>
3142
+ <div style="display:flex;justify-content:space-between;font-size:9px;color:var(--muted);margin:4px 0 12px">
3143
+ <span>🟢 Cash ${cashPct}%</span><span>🟣 Invested ${investPct}%</span><span>🟡 Battle ${battlePct}%</span>
3144
+ </div>
3145
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
3146
+ <div style="padding:8px;border-radius:8px;background:rgba(255,255,255,0.03)">
3147
+ <div style="font-size:10px;color:var(--muted)">24h Volume</div>
3148
+ <div style="font-size:14px;font-weight:800">${fmtK(m.volume_24h||0)}</div>
3149
+ </div>
3150
+ <div style="padding:8px;border-radius:8px;background:rgba(255,255,255,0.03)">
3151
+ <div style="font-size:10px;color:var(--muted)">Velocity</div>
3152
+ <div style="font-size:14px;font-weight:800">${m.velocity||0}x</div>
3153
+ </div>
3154
+ <div style="padding:8px;border-radius:8px;background:rgba(255,82,82,0.06)">
3155
+ <div style="font-size:10px;color:var(--muted)">GPU Destroyed (24h)</div>
3156
+ <div style="font-size:14px;font-weight:800;color:var(--red)">${fmtK(m.gpu_destroyed_24h||0)}</div>
3157
+ </div>
3158
+ <div style="padding:8px;border-radius:8px;background:rgba(255,138,128,0.06)">
3159
+ <div style="font-size:10px;color:var(--muted)">SEC Fines (24h)</div>
3160
+ <div style="font-size:14px;font-weight:800;color:#ff8a80">${fmtK(m.gpu_fined_24h||0)}</div>
3161
+ </div>
3162
+ </div>
3163
+ `;
3164
+ }
3165
+
3166
  /* ====== Init: load live news on startup ====== */
3167
 
3168
  checkLogin();