OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
HASH      2708fe5e9981
DATE      2025-09-25
SUBJECT   web: add new realms (for testing), fix class color resolution for players
          that connot be found
FILES     8 CHANGED
HASH      2708fe5e9981
DATE      2025-09-25
SUBJECT   web: add new realms (for testing), fix
          class color resolution for players
          that connot be found
FILES     8 CHANGED
 

diff --git a/nix/apps/utils/multi-period-analyzer.nix b/nix/apps/utils/multi-perio
d-analyzer.nix
index eb5986a..af23d9b 100644
--- a/nix/apps/utils/multi-period-analyzer.nix
+++ b/nix/apps/utils/multi-period-analyzer.nix
@@ -97,7 +97,7 @@
       ]
 
       API_TOKEN = os.getenv("BLIZZARD_API_TOKEN")
-      PERIOD_RANGE = range(1026, 1030)  # Test periods 1026-1028
+      PERIOD_RANGE = range(1026, 1031)  # Test periods 1026-1028
       MAX_CONCURRENT = 50  # Reduced concurrency for comprehensive testing
       BATCH_SIZE = 50
 
@@ -362,4 +362,3 @@
     '';
 in
   multiPeriodAnalyzerScript
-

diff --git a/nix/apps/utils/multi-period-ana
lyzer.nix b/nix/apps/utils/multi-period-anal
yzer.nix
index eb5986a..af23d9b 100644
--- a/nix/apps/utils/multi-period-analyzer.n
ix
+++ b/nix/apps/utils/multi-period-analyzer.n
ix
@@ -97,7 +97,7 @@
       ]
 
       API_TOKEN = os.getenv("BLIZZARD_API_T
OKEN")
-      PERIOD_RANGE = range(1026, 1030)  # T
est periods 1026-1028
+      PERIOD_RANGE = range(1026, 1031)  # T
est periods 1026-1028
       MAX_CONCURRENT = 50  # Reduced concur
rency for comprehensive testing
       BATCH_SIZE = 50
 
@@ -362,4 +362,3 @@
     '';
 in
   multiPeriodAnalyzerScript
-
 

diff --git a/nix/pkgs/ookstats/src/cmd/generate.go b/nix/pkgs/ookstats/src/cmd/gen
erate.go
index 25fadc8..7cd348c 100644
--- a/nix/pkgs/ookstats/src/cmd/generate.go
+++ b/nix/pkgs/ookstats/src/cmd/generate.go
@@ -75,6 +75,85 @@ type PlayerPageJSON struct {
     Version     string               `json:"version"`
 }
 
+// --- Fallback helpers for class/spec resolution when Profile API is unavailable
 ---
+func specIdToClassSpec(id int) (className string, specName string, ok bool) {
+    switch id {
+    // Tanks
+    case 73:
+        return "Warrior", "Protection", true
+    case 104:
+        return "Druid", "Guardian", true
+    case 250:
+        return "Death Knight", "Blood", true
+    case 268:
+        return "Monk", "Brewmaster", true
+    case 66:
+        return "Paladin", "Protection", true
+    // Healers
+    case 105:
+        return "Druid", "Restoration", true
+    case 270:
+        return "Monk", "Mistweaver", true
+    case 65:
+        return "Paladin", "Holy", true
+    case 256:
+        return "Priest", "Discipline", true
+    case 257:
+        return "Priest", "Holy", true
+    case 264:
+        return "Shaman", "Restoration", true
+    // DPS
+    case 71:
+        return "Warrior", "Arms", true
+    case 72:
+        return "Warrior", "Fury", true
+    case 70:
+        return "Paladin", "Retribution", true
+    case 253:
+        return "Hunter", "Beast Mastery", true
+    case 254:
+        return "Hunter", "Marksmanship", true
+    case 255:
+        return "Hunter", "Survival", true
+    case 259:
+        return "Rogue", "Assassination", true
+    case 260:
+        return "Rogue", "Outlaw", true
+    case 261:
+        return "Rogue", "Subtlety", true
+    case 258:
+        return "Priest", "Shadow", true
+    case 251:
+        return "Death Knight", "Frost", true
+    case 252:
+        return "Death Knight", "Unholy", true
+    case 262:
+        return "Shaman", "Elemental", true
+    case 263:
+        return "Shaman", "Enhancement", true
+    case 62:
+        return "Mage", "Arcane", true
+    case 63:
+        return "Mage", "Fire", true
+    case 64:
+        return "Mage", "Frost", true
+    case 265:
+        return "Warlock", "Affliction", true
+    case 266:
+        return "Warlock", "Demonology", true
+    case 267:
+        return "Warlock", "Destruction", true
+    case 269:
+        return "Monk", "Windwalker", true
+    case 102:
+        return "Druid", "Balance", true
+    case 103:
+        return "Druid", "Feral", true
+    default:
+        return "", "", false
+    }
+}
+
 // Optimized data structures for batch loading
 type PlayerData struct {
     ID                   int64
@@ -686,6 +765,14 @@ func generateSinglePlayerJSON(player PlayerData, bestRunsMap 
map[int64][]BestRun
     if player.RealmRanking.Valid { v := int(player.RealmRanking.Int64); pj.RealmR
anking = &v }
     if player.AverageItemLevel.Valid { v := int(player.AverageItemLevel.Int64); p
j.AverageItemLevel = &v }
     if player.EquippedItemLevel.Valid { v := int(player.EquippedItemLevel.Int64);
 pj.EquippedItemLevel = &v }
+
+    // Fallback: if class/spec missing (Profile API issues), derive from main_spe
c_id
+    if (pj.ClassName == "" || pj.ActiveSpecName == "") && pj.MainSpecID != nil {
+        if cls, spec, ok := specIdToClassSpec(*pj.MainSpecID); ok {
+            if pj.ClassName == "" { pj.ClassName = cls }
+            if pj.ActiveSpecName == "" { pj.ActiveSpecName = spec }
+        }
+    }
     if player.LastUpdated.Valid { v := player.LastUpdated.Int64; pj.LastUpdated =
 &v }
     
     // Build best runs
@@ -1187,6 +1274,27 @@ func generatePlayerLeaderboards(db *sql.DB, out string, pag
eSize int, regions []
                     "dungeons_completed": r.DungeonsCompleted,
                     "total_runs": r.TotalRuns,
                 }
+                // Fallback class/spec if missing
+                if (r.ClassName == "" || r.ActiveSpecName == "") && r.MainSpecID.
Valid {
+                    if cls, spec, ok := specIdToClassSpec(int(r.MainSpecID.Int64)
); ok {
+                        if r.ClassName == "" { obj["class_name"] = cls }
+                        if r.ActiveSpecName == "" { obj["active_spec_name"] = spe
c }
+                    }
+                }
+                // Fallback class/spec if missing
+                if (r.ClassName == "" || r.ActiveSpecName == "") && r.MainSpecID.
Valid {
+                    if cls, spec, ok := specIdToClassSpec(int(r.MainSpecID.Int64)
); ok {
+                        if r.ClassName == "" { obj["class_name"] = cls }
+                        if r.ActiveSpecName == "" { obj["active_spec_name"] = spe
c }
+                    }
+                }
+                // Fallback class/spec if missing
+                if (r.ClassName == "" || r.ActiveSpecName == "") && r.MainSpecID.
Valid {
+                    if cls, spec, ok := specIdToClassSpec(int(r.MainSpecID.Int64)
); ok {
+                        if r.ClassName == "" { obj["class_name"] = cls }
+                        if r.ActiveSpecName == "" { obj["active_spec_name"] = spe
c }
+                    }
+                }
                 if r.MainSpecID.Valid { obj["main_spec_id"] = int(r.MainSpecID.In
t64) }
                 if r.CombinedBestTime.Valid { obj["combined_best_time"] = r.Combi
nedBestTime.Int64 }
                 list = append(list, obj)

diff --git a/nix/pkgs/ookstats/src/cmd/gener
ate.go b/nix/pkgs/ookstats/src/cmd/generate.
go
index 25fadc8..7cd348c 100644
--- a/nix/pkgs/ookstats/src/cmd/generate.go
+++ b/nix/pkgs/ookstats/src/cmd/generate.go
@@ -75,6 +75,85 @@ type PlayerPageJSON struc
t {
     Version     string               `json:
"version"`
 }
 
+// --- Fallback helpers for class/spec reso
lution when Profile API is unavailable ---
+func specIdToClassSpec(id int) (className s
tring, specName string, ok bool) {
+    switch id {
+    // Tanks
+    case 73:
+        return "Warrior", "Protection", tru
e
+    case 104:
+        return "Druid", "Guardian", true
+    case 250:
+        return "Death Knight", "Blood", tru
e
+    case 268:
+        return "Monk", "Brewmaster", true
+    case 66:
+        return "Paladin", "Protection", tru
e
+    // Healers
+    case 105:
+        return "Druid", "Restoration", true
+    case 270:
+        return "Monk", "Mistweaver", true
+    case 65:
+        return "Paladin", "Holy", true
+    case 256:
+        return "Priest", "Discipline", true
+    case 257:
+        return "Priest", "Holy", true
+    case 264:
+        return "Shaman", "Restoration", tru
e
+    // DPS
+    case 71:
+        return "Warrior", "Arms", true
+    case 72:
+        return "Warrior", "Fury", true
+    case 70:
+        return "Paladin", "Retribution", tr
ue
+    case 253:
+        return "Hunter", "Beast Mastery", t
rue
+    case 254:
+        return "Hunter", "Marksmanship", tr
ue
+    case 255:
+        return "Hunter", "Survival", true
+    case 259:
+        return "Rogue", "Assassination", tr
ue
+    case 260:
+        return "Rogue", "Outlaw", true
+    case 261:
+        return "Rogue", "Subtlety", true
+    case 258:
+        return "Priest", "Shadow", true
+    case 251:
+        return "Death Knight", "Frost", tru
e
+    case 252:
+        return "Death Knight", "Unholy", tr
ue
+    case 262:
+        return "Shaman", "Elemental", true
+    case 263:
+        return "Shaman", "Enhancement", tru
e
+    case 62:
+        return "Mage", "Arcane", true
+    case 63:
+        return "Mage", "Fire", true
+    case 64:
+        return "Mage", "Frost", true
+    case 265:
+        return "Warlock", "Affliction", tru
e
+    case 266:
+        return "Warlock", "Demonology", tru
e
+    case 267:
+        return "Warlock", "Destruction", tr
ue
+    case 269:
+        return "Monk", "Windwalker", true
+    case 102:
+        return "Druid", "Balance", true
+    case 103:
+        return "Druid", "Feral", true
+    default:
+        return "", "", false
+    }
+}
+
 // Optimized data structures for batch load
ing
 type PlayerData struct {
     ID                   int64
@@ -686,6 +765,14 @@ func generateSinglePlay
erJSON(player PlayerData, bestRunsMap map[in
t64][]BestRun
     if player.RealmRanking.Valid { v := int
(player.RealmRanking.Int64); pj.RealmRanking
 = &v }
     if player.AverageItemLevel.Valid { v :=
 int(player.AverageItemLevel.Int64); pj.Aver
ageItemLevel = &v }
     if player.EquippedItemLevel.Valid { v :
= int(player.EquippedItemLevel.Int64); pj.Eq
uippedItemLevel = &v }
+
+    // Fallback: if class/spec missing (Pro
file API issues), derive from main_spec_id
+    if (pj.ClassName == "" || pj.ActiveSpec
Name == "") && pj.MainSpecID != nil {
+        if cls, spec, ok := specIdToClassSp
ec(*pj.MainSpecID); ok {
+            if pj.ClassName == "" { pj.Clas
sName = cls }
+            if pj.ActiveSpecName == "" { pj
.ActiveSpecName = spec }
+        }
+    }
     if player.LastUpdated.Valid { v := play
er.LastUpdated.Int64; pj.LastUpdated = &v }
     
     // Build best runs
@@ -1187,6 +1274,27 @@ func generatePlayerLe
aderboards(db *sql.DB, out string, pageSize 
int, regions []
                     "dungeons_completed": r
.DungeonsCompleted,
                     "total_runs": r.TotalRu
ns,
                 }
+                // Fallback class/spec if m
issing
+                if (r.ClassName == "" || r.
ActiveSpecName == "") && r.MainSpecID.Valid 
{
+                    if cls, spec, ok := spe
cIdToClassSpec(int(r.MainSpecID.Int64)); ok 
{
+                        if r.ClassName == "
" { obj["class_name"] = cls }
+                        if r.ActiveSpecName
 == "" { obj["active_spec_name"] = spec }
+                    }
+                }
+                // Fallback class/spec if m
issing
+                if (r.ClassName == "" || r.
ActiveSpecName == "") && r.MainSpecID.Valid 
{
+                    if cls, spec, ok := spe
cIdToClassSpec(int(r.MainSpecID.Int64)); ok 
{
+                        if r.ClassName == "
" { obj["class_name"] = cls }
+                        if r.ActiveSpecName
 == "" { obj["active_spec_name"] = spec }
+                    }
+                }
+                // Fallback class/spec if m
issing
+                if (r.ClassName == "" || r.
ActiveSpecName == "") && r.MainSpecID.Valid 
{
+                    if cls, spec, ok := spe
cIdToClassSpec(int(r.MainSpecID.Int64)); ok 
{
+                        if r.ClassName == "
" { obj["class_name"] = cls }
+                        if r.ActiveSpecName
 == "" { obj["active_spec_name"] = spec }
+                    }
+                }
                 if r.MainSpecID.Valid { obj
["main_spec_id"] = int(r.MainSpecID.Int64) }
                 if r.CombinedBestTime.Valid
 { obj["combined_best_time"] = r.CombinedBes
tTime.Int64 }
                 list = append(list, obj)
 

diff --git a/nix/pkgs/ookstats/src/internal/blizzard/constants.go b/nix/pkgs/ookst
ats/src/internal/blizzard/constants.go
index c656fe0..eb5c620 100644
--- a/nix/pkgs/ookstats/src/internal/blizzard/constants.go
+++ b/nix/pkgs/ookstats/src/internal/blizzard/constants.go
@@ -69,9 +69,10 @@ func GetAllRealms() map[string]RealmInfo {
 		"faerlina":             {ID: 4408, Region: "us", Name: "Faerlina", Slug: "faerl
ina"},
 		"grobbulus":            {ID: 4647, Region: "us", Name: "Grobbulus", Slug: "grob
bulus"},
 		"bloodsail-buccaneers": {ID: 4648, Region: "us", Name: "Bloodsail Buccaneers", 
Slug: "bloodsail-buccaneers"},
-		"remulos":              {ID: 4667, Region: "us", Name: "Remulos", Slug: "remulo
s"},
-		"arugal":               {ID: 4669, Region: "us", Name: "Arugal", Slug: "arugal"
},
-		"yojamba":              {ID: 4670, Region: "us", Name: "Yojamba", Slug: "yojamb
a"},
+        // OCE realms are served under US region with -au slug suffix
+        "remulos-au":           {ID: 4667, Region: "us", Name: "Remulos (AU)", Sl
ug: "remulos-au"},
+        "arugal-au":            {ID: 4669, Region: "us", Name: "Arugal (AU)", Slu
g: "arugal-au"},
+        "yojamba-au":           {ID: 4670, Region: "us", Name: "Yojamba (AU)", Sl
ug: "yojamba-au"},
 		"skyfury":              {ID: 4725, Region: "us", Name: "Skyfury", Slug: "skyfur
y"},
 		"sulfuras":             {ID: 4726, Region: "us", Name: "Sulfuras", Slug: "sulfu
ras"},
 		"windseeker":           {ID: 4727, Region: "us", Name: "Windseeker", Slug: "win
dseeker"},
@@ -80,6 +81,12 @@ func GetAllRealms() map[string]RealmInfo {
 		"maladath":             {ID: 4738, Region: "us", Name: "Maladath", Slug: "malad
ath"},
 		"angerforge":           {ID: 4795, Region: "us", Name: "Angerforge", Slug: "ang
erforge"},
 		"eranikus":             {ID: 4800, Region: "us", Name: "Eranikus", Slug: "erani
kus"},
+        // New US realms (MoP Classic launch wave)
+        "nazgrim":             {ID: 6359, Region: "us", Name: "Nazgrim", Slug: "n
azgrim"},
+        "galakras":            {ID: 6360, Region: "us", Name: "Galakras", Slug: "
galakras"},
+        "raden":               {ID: 6361, Region: "us", Name: "Ra-den", Slug: "ra
den"},
+        "lei-shen":            {ID: 6362, Region: "us", Name: "Lei Shen", Slug: "
lei-shen"},
+        "immerseus":           {ID: 6363, Region: "us", Name: "Immerseus", Slug: 
"immerseus"},
 
 		// eu realms
 		"everlook":             {ID: 4440, Region: "eu", Name: "Everlook", Slug: "everl
ook"},
@@ -107,6 +114,12 @@ func GetAllRealms() map[string]RealmInfo {
 		"mandokir":             {ID: 4813, Region: "eu", Name: "Mandokir", Slug: "mando
kir"},
 		"thekal":               {ID: 4815, Region: "eu", Name: "Thekal", Slug: "thekal"
},
 		"jindo":                {ID: 4816, Region: "eu", Name: "Jin'do", Slug: "jindo"}
,
+        // New EU realms (MoP Classic launch wave)
+        "shekzeer":            {ID: 6364, Region: "eu", Name: "Shek'zeer", Slug: 
"shekzeer"},
+        "garalon":             {ID: 6365, Region: "eu", Name: "Garalon", Slug: "g
aralon"},
+        "norushen":            {ID: 6366, Region: "eu", Name: "Norushen", Slug: "
norushen"},
+        "hoptallus":           {ID: 6367, Region: "eu", Name: "Hoptallus", Slug: 
"hoptallus"},
+        "ook-ook":             {ID: 6368, Region: "eu", Name: "Ook Ook", Slug: "o
ok-ook"},
 
         // kr realms
         "shimmering-flats": {ID: 4417, Region: "kr", Name: "Shimmering Flats", Sl
ug: "shimmering-flats"},

diff --git a/nix/pkgs/ookstats/src/internal/
blizzard/constants.go b/nix/pkgs/ookstats/sr
c/internal/blizzard/constants.go
index c656fe0..eb5c620 100644
--- a/nix/pkgs/ookstats/src/internal/blizzar
d/constants.go
+++ b/nix/pkgs/ookstats/src/internal/blizzar
d/constants.go
@@ -69,9 +69,10 @@ func GetAllRealms() map[s
tring]RealmInfo {
 		"faerlina":             {ID: 4408, Region
: "us", Name: "Faerlina", Slug: "faerlina"},
 		"grobbulus":            {ID: 4647, Region
: "us", Name: "Grobbulus", Slug: "grobbulus"
},
 		"bloodsail-buccaneers": {ID: 4648, Region
: "us", Name: "Bloodsail Buccaneers", Slug: 
"bloodsail-buccaneers"},
-		"remulos":              {ID: 4667, Region
: "us", Name: "Remulos", Slug: "remulos"},
-		"arugal":               {ID: 4669, Region
: "us", Name: "Arugal", Slug: "arugal"},
-		"yojamba":              {ID: 4670, Region
: "us", Name: "Yojamba", Slug: "yojamba"},
+        // OCE realms are served under US r
egion with -au slug suffix
+        "remulos-au":           {ID: 4667, 
Region: "us", Name: "Remulos (AU)", Slug: "r
emulos-au"},
+        "arugal-au":            {ID: 4669, 
Region: "us", Name: "Arugal (AU)", Slug: "ar
ugal-au"},
+        "yojamba-au":           {ID: 4670, 
Region: "us", Name: "Yojamba (AU)", Slug: "y
ojamba-au"},
 		"skyfury":              {ID: 4725, Region
: "us", Name: "Skyfury", Slug: "skyfury"},
 		"sulfuras":             {ID: 4726, Region
: "us", Name: "Sulfuras", Slug: "sulfuras"},
 		"windseeker":           {ID: 4727, Region
: "us", Name: "Windseeker", Slug: "windseeke
r"},
@@ -80,6 +81,12 @@ func GetAllRealms() map[s
tring]RealmInfo {
 		"maladath":             {ID: 4738, Region
: "us", Name: "Maladath", Slug: "maladath"},
 		"angerforge":           {ID: 4795, Region
: "us", Name: "Angerforge", Slug: "angerforg
e"},
 		"eranikus":             {ID: 4800, Region
: "us", Name: "Eranikus", Slug: "eranikus"},
+        // New US realms (MoP Classic launc
h wave)
+        "nazgrim":             {ID: 6359, R
egion: "us", Name: "Nazgrim", Slug: "nazgrim
"},
+        "galakras":            {ID: 6360, R
egion: "us", Name: "Galakras", Slug: "galakr
as"},
+        "raden":               {ID: 6361, R
egion: "us", Name: "Ra-den", Slug: "raden"},
+        "lei-shen":            {ID: 6362, R
egion: "us", Name: "Lei Shen", Slug: "lei-sh
en"},
+        "immerseus":           {ID: 6363, R
egion: "us", Name: "Immerseus", Slug: "immer
seus"},
 
 		// eu realms
 		"everlook":             {ID: 4440, Region
: "eu", Name: "Everlook", Slug: "everlook"},
@@ -107,6 +114,12 @@ func GetAllRealms() map
[string]RealmInfo {
 		"mandokir":             {ID: 4813, Region
: "eu", Name: "Mandokir", Slug: "mandokir"},
 		"thekal":               {ID: 4815, Region
: "eu", Name: "Thekal", Slug: "thekal"},
 		"jindo":                {ID: 4816, Region
: "eu", Name: "Jin'do", Slug: "jindo"},
+        // New EU realms (MoP Classic launc
h wave)
+        "shekzeer":            {ID: 6364, R
egion: "eu", Name: "Shek'zeer", Slug: "shekz
eer"},
+        "garalon":             {ID: 6365, R
egion: "eu", Name: "Garalon", Slug: "garalon
"},
+        "norushen":            {ID: 6366, R
egion: "eu", Name: "Norushen", Slug: "norush
en"},
+        "hoptallus":           {ID: 6367, R
egion: "eu", Name: "Hoptallus", Slug: "hopta
llus"},
+        "ook-ook":             {ID: 6368, R
egion: "eu", Name: "Ook Ook", Slug: "ook-ook
"},
 
         // kr realms
         "shimmering-flats": {ID: 4417, Regi
on: "kr", Name: "Shimmering Flats", Slug: "s
himmering-flats"},
 

diff --git a/nix/pkgs/ookstats/src/internal/database/operations.go b/nix/pkgs/ooks
tats/src/internal/database/operations.go
index 1444492..5f0e187 100644
--- a/nix/pkgs/ookstats/src/internal/database/operations.go
+++ b/nix/pkgs/ookstats/src/internal/database/operations.go
@@ -765,7 +765,8 @@ func (ds *DatabaseService) getRealmIDTx(tx *sql.Tx, slug strin
g, region string)
     var realmID int
     err := tx.QueryRow("SELECT id FROM realms WHERE slug = ? AND region = ?", slu
g, region).Scan(&realmID)
     if err == sql.ErrNoRows {
-        return 0, fmt.Errorf("realm not found: %s/%s", region, slug)
+        // Not found is not an error for callers that may insert placeholders/ali
ases
+        return 0, nil
     }
     return realmID, err
 }

diff --git a/nix/pkgs/ookstats/src/internal/
database/operations.go b/nix/pkgs/ookstats/s
rc/internal/database/operations.go
index 1444492..5f0e187 100644
--- a/nix/pkgs/ookstats/src/internal/databas
e/operations.go
+++ b/nix/pkgs/ookstats/src/internal/databas
e/operations.go
@@ -765,7 +765,8 @@ func (ds *DatabaseServic
e) getRealmIDTx(tx *sql.Tx, slug string, reg
ion string)
     var realmID int
     err := tx.QueryRow("SELECT id FROM real
ms WHERE slug = ? AND region = ?", slug, reg
ion).Scan(&realmID)
     if err == sql.ErrNoRows {
-        return 0, fmt.Errorf("realm not fou
nd: %s/%s", region, slug)
+        // Not found is not an error for ca
llers that may insert placeholders/aliases
+        return 0, nil
     }
     return realmID, err
 }
 

diff --git a/web/.astro/data-store.json b/web/.astro/data-store.json
index 03b5f7a..2f54464 100644
--- a/web/.astro/data-store.json
+++ b/web/.astro/data-store.json
@@ -1 +1 @@
-[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.12.0","astro-config-
digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{}
,\"site\":\"https://wowsimstats.com\",\"compressHTML\":true,\"base\":\"/\",\"trail
ingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\",
\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astr
o\",\"serverEntry\":\"entry.mjs\",\"redirects\":false,\"inlineStylesheets\":\"auto
\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":26601,\"s
treaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"r
oute\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"
config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"de
vToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\
",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"th
eme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPl
ugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":t
rue},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\
":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false
,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\
":false,\"csp\":false,\"rawEnvValues\":false},\"legacy\":{\"collections\":false}}"
]
\ No newline at end of file
+[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.12.0","astro-config-
digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{}
,\"site\":\"https://wowsimstats.com\",\"compressHTML\":true,\"base\":\"/\",\"trail
ingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\",
\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astr
o\",\"serverEntry\":\"entry.mjs\",\"redirects\":false,\"inlineStylesheets\":\"auto
\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":20679,\"s
treaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"r
oute\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"
config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"de
vToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\
",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"th
eme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPl
ugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":t
rue},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\
":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false
,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\
":false,\"csp\":false,\"rawEnvValues\":false},\"legacy\":{\"collections\":false}}"
]
\ No newline at end of file

diff --git a/web/.astro/data-store.json b/we
b/.astro/data-store.json
index 03b5f7a..2f54464 100644
--- a/web/.astro/data-store.json
+++ b/web/.astro/data-store.json
@@ -1 +1 @@
-[["Map",1,2],"meta::meta",["Map",3,4,5,6],"
astro-version","5.12.0","astro-config-digest
","{\"root\":{},\"srcDir\":{},\"publicDir\":
{},\"outDir\":{},\"cacheDir\":{},\"site\":\"
https://wowsimstats.com\",\"compressHTML\":t
rue,\"base\":\"/\",\"trailingSlash\":\"ignor
e\",\"output\":\"server\",\"scopedStyleStrat
egy\":\"attribute\",\"build\":{\"format\":\"
directory\",\"client\":{},\"server\":{},\"as
sets\":\"_astro\",\"serverEntry\":\"entry.mj
s\",\"redirects\":false,\"inlineStylesheets\
":\"auto\",\"concurrency\":1},\"server\":{\"
open\":false,\"host\":false,\"port\":26601,\
"streaming\":true,\"allowedHosts\":[]},\"red
irects\":{},\"image\":{\"endpoint\":{\"route
\":\"/_image\"},\"service\":{\"entrypoint\":
\"astro/assets/services/sharp\",\"config\":{
}},\"domains\":[],\"remotePatterns\":[],\"re
sponsiveStyles\":false},\"devToolbar\":{\"en
abled\":true},\"markdown\":{\"syntaxHighligh
t\":{\"type\":\"shiki\",\"excludeLangs\":[\"
math\"]},\"shikiConfig\":{\"langs\":[],\"lan
gAlias\":{},\"theme\":\"github-dark\",\"them
es\":{},\"wrap\":false,\"transformers\":[]},
\"remarkPlugins\":[],\"rehypePlugins\":[],\"
remarkRehype\":{},\"gfm\":true,\"smartypants
\":true},\"security\":{\"checkOrigin\":true}
,\"env\":{\"schema\":{},\"validateSecrets\":
false},\"experimental\":{\"clientPrerender\"
:false,\"contentIntellisense\":false,\"headi
ngIdCompat\":false,\"preserveScriptOrder\":f
alse,\"liveContentCollections\":false,\"csp\
":false,\"rawEnvValues\":false},\"legacy\":{
\"collections\":false}}"]
\ No newline at end of file
+[["Map",1,2],"meta::meta",["Map",3,4,5,6],"
astro-version","5.12.0","astro-config-digest
","{\"root\":{},\"srcDir\":{},\"publicDir\":
{},\"outDir\":{},\"cacheDir\":{},\"site\":\"
https://wowsimstats.com\",\"compressHTML\":t
rue,\"base\":\"/\",\"trailingSlash\":\"ignor
e\",\"output\":\"server\",\"scopedStyleStrat
egy\":\"attribute\",\"build\":{\"format\":\"
directory\",\"client\":{},\"server\":{},\"as
sets\":\"_astro\",\"serverEntry\":\"entry.mj
s\",\"redirects\":false,\"inlineStylesheets\
":\"auto\",\"concurrency\":1},\"server\":{\"
open\":false,\"host\":false,\"port\":20679,\
"streaming\":true,\"allowedHosts\":[]},\"red
irects\":{},\"image\":{\"endpoint\":{\"route
\":\"/_image\"},\"service\":{\"entrypoint\":
\"astro/assets/services/sharp\",\"config\":{
}},\"domains\":[],\"remotePatterns\":[],\"re
sponsiveStyles\":false},\"devToolbar\":{\"en
abled\":true},\"markdown\":{\"syntaxHighligh
t\":{\"type\":\"shiki\",\"excludeLangs\":[\"
math\"]},\"shikiConfig\":{\"langs\":[],\"lan
gAlias\":{},\"theme\":\"github-dark\",\"them
es\":{},\"wrap\":false,\"transformers\":[]},
\"remarkPlugins\":[],\"rehypePlugins\":[],\"
remarkRehype\":{},\"gfm\":true,\"smartypants
\":true},\"security\":{\"checkOrigin\":true}
,\"env\":{\"schema\":{},\"validateSecrets\":
false},\"experimental\":{\"clientPrerender\"
:false,\"contentIntellisense\":false,\"headi
ngIdCompat\":false,\"preserveScriptOrder\":f
alse,\"liveContentCollections\":false,\"csp\
":false,\"rawEnvValues\":false},\"legacy\":{
\"collections\":false}}"]
\ No newline at end of file
 

diff --git a/web/src/client/player-leaderboard.ts b/web/src/client/player-leaderbo
ard.ts
index 62e00c3..33ce99b 100644
--- a/web/src/client/player-leaderboard.ts
+++ b/web/src/client/player-leaderboard.ts
@@ -226,9 +226,15 @@ class PlayerLeaderboardClient {
         ? this.formatDuration(player.combined_best_time)
         : "-";
       const row = document.createElement("div");
-      const classColor = player.class_name
-        ? getClassColor(player.class_name)
-        : "#FFFFFF";
+      let classColor = "#FFFFFF";
+      if (player.class_name) {
+        classColor = getClassColor(player.class_name);
+      } else if (player.main_spec_id) {
+        const si = getSpecInfo(Number(player.main_spec_id));
+        if (si && si.class) {
+          classColor = getClassColor(si.class);
+        }
+      }
       const profileUrl = buildPlayerProfileURL(
         player.region || "us",
         player.realm_slug,

diff --git a/web/src/client/player-leaderboa
rd.ts b/web/src/client/player-leaderboard.ts
index 62e00c3..33ce99b 100644
--- a/web/src/client/player-leaderboard.ts
+++ b/web/src/client/player-leaderboard.ts
@@ -226,9 +226,15 @@ class PlayerLeaderboard
Client {
         ? this.formatDuration(player.combin
ed_best_time)
         : "-";
       const row = document.createElement("d
iv");
-      const classColor = player.class_name
-        ? getClassColor(player.class_name)
-        : "#FFFFFF";
+      let classColor = "#FFFFFF";
+      if (player.class_name) {
+        classColor = getClassColor(player.c
lass_name);
+      } else if (player.main_spec_id) {
+        const si = getSpecInfo(Number(playe
r.main_spec_id));
+        if (si && si.class) {
+          classColor = getClassColor(si.cla
ss);
+        }
+      }
       const profileUrl = buildPlayerProfile
URL(
         player.region || "us",
         player.realm_slug,
 

diff --git a/web/src/client/player-profile.ts b/web/src/client/player-profile.ts
index 73675fc..371de53 100644
--- a/web/src/client/player-profile.ts
+++ b/web/src/client/player-profile.ts
@@ -4,6 +4,7 @@ import {
   formatDurationMMSS,
 } from "../lib/utils";
 import { getClassTextClass } from "../lib/wow-constants";
+import { getSpecInfo } from "../lib/client-utils.ts";
 import { formatRankingWithBracket as formatRank } from "../lib/client-utils.ts";
 import { renderBestRunsWithWrapper } from "../lib/bestRunsRenderer";
 import {
@@ -149,7 +150,13 @@ class PlayerProfileManager {
     const regBracket = player.regional_ranking_bracket || "";
     const realmBracket = player.realm_ranking_bracket || "";
 
-    const classText = getClassTextClass(player.class_name || "common");
+    // Fallback class color when class_name missing: derive from main_spec_id if 
available
+    let classNameForColor = player.class_name || "";
+    if (!classNameForColor && (player.main_spec_id || player.main_spec_id === 0))
 {
+      const si = getSpecInfo(Number(player.main_spec_id));
+      if (si && si.class) classNameForColor = si.class;
+    }
+    const classText = getClassTextClass(classNameForColor || "common");
     const headerHTML = `
       <div class="player-header-horizontal">
         <div class="player-avatar">

diff --git a/web/src/client/player-profile.t
s b/web/src/client/player-profile.ts
index 73675fc..371de53 100644
--- a/web/src/client/player-profile.ts
+++ b/web/src/client/player-profile.ts
@@ -4,6 +4,7 @@ import {
   formatDurationMMSS,
 } from "../lib/utils";
 import { getClassTextClass } from "../lib/w
ow-constants";
+import { getSpecInfo } from "../lib/client-
utils.ts";
 import { formatRankingWithBracket as format
Rank } from "../lib/client-utils.ts";
 import { renderBestRunsWithWrapper } from "
../lib/bestRunsRenderer";
 import {
@@ -149,7 +150,13 @@ class PlayerProfileMana
ger {
     const regBracket = player.regional_rank
ing_bracket || "";
     const realmBracket = player.realm_ranki
ng_bracket || "";
 
-    const classText = getClassTextClass(pla
yer.class_name || "common");
+    // Fallback class color when class_name
 missing: derive from main_spec_id if availa
ble
+    let classNameForColor = player.class_na
me || "";
+    if (!classNameForColor && (player.main_
spec_id || player.main_spec_id === 0)) {
+      const si = getSpecInfo(Number(player.
main_spec_id));
+      if (si && si.class) classNameForColor
 = si.class;
+    }
+    const classText = getClassTextClass(cla
ssNameForColor || "common");
     const headerHTML = `
       <div class="player-header-horizontal"
>
         <div class="player-avatar">
 

diff --git a/web/src/lib/realms.ts b/web/src/lib/realms.ts
index fd2e987..ce3c94f 100644
--- a/web/src/lib/realms.ts
+++ b/web/src/lib/realms.ts
@@ -14,9 +14,9 @@ export const REALM_DATA = {
     faerlina: "Faerlina",
     grobbulus: "Grobbulus",
     "bloodsail-buccaneers": "Bloodsail Buccaneers",
-    remulos: "Remulos",
-    arugal: "Arugal",
-    yojamba: "Yojamba",
+    "remulos-au": "Remulos (AU)",
+    "arugal-au": "Arugal (AU)",
+    "yojamba-au": "Yojamba (AU)",
     skyfury: "Skyfury",
     sulfuras: "Sulfuras",
     windseeker: "Windseeker",
@@ -25,6 +25,11 @@ export const REALM_DATA = {
     maladath: "Maladath",
     angerforge: "Angerforge",
     eranikus: "Eranikus",
+    nazgrim: "Nazgrim",
+    galakras: "Galakras",
+    raden: "Ra-den",
+    "lei-shen": "Lei Shen",
+    immerseus: "Immerseus",
   },
   eu: {
     everlook: "Everlook",
@@ -52,6 +57,11 @@ export const REALM_DATA = {
     mandokir: "Mandokir",
     thekal: "Thekal",
     jindo: "Jin'do",
+    shekzeer: "Shek'zeer",
+    garalon: "Garalon",
+    norushen: "Norushen",
+    hoptallus: "Hoptallus",
+    "ook-ook": "Ook Ook",
   },
   kr: {
     "shimmering-flats": "Shimmering Flats",

diff --git a/web/src/lib/realms.ts b/web/src
/lib/realms.ts
index fd2e987..ce3c94f 100644
--- a/web/src/lib/realms.ts
+++ b/web/src/lib/realms.ts
@@ -14,9 +14,9 @@ export const REALM_DATA = 
{
     faerlina: "Faerlina",
     grobbulus: "Grobbulus",
     "bloodsail-buccaneers": "Bloodsail Bucc
aneers",
-    remulos: "Remulos",
-    arugal: "Arugal",
-    yojamba: "Yojamba",
+    "remulos-au": "Remulos (AU)",
+    "arugal-au": "Arugal (AU)",
+    "yojamba-au": "Yojamba (AU)",
     skyfury: "Skyfury",
     sulfuras: "Sulfuras",
     windseeker: "Windseeker",
@@ -25,6 +25,11 @@ export const REALM_DATA =
 {
     maladath: "Maladath",
     angerforge: "Angerforge",
     eranikus: "Eranikus",
+    nazgrim: "Nazgrim",
+    galakras: "Galakras",
+    raden: "Ra-den",
+    "lei-shen": "Lei Shen",
+    immerseus: "Immerseus",
   },
   eu: {
     everlook: "Everlook",
@@ -52,6 +57,11 @@ export const REALM_DATA =
 {
     mandokir: "Mandokir",
     thekal: "Thekal",
     jindo: "Jin'do",
+    shekzeer: "Shek'zeer",
+    garalon: "Garalon",
+    norushen: "Norushen",
+    hoptallus: "Hoptallus",
+    "ook-ook": "Ook Ook",
   },
   kr: {
     "shimmering-flats": "Shimmering Flats",
 
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET