From c00692916e55cd3eb5de7a3d9e2fba9da4000b4c Mon Sep 17 00:00:00 2001
From: Lucrona <113807308+Lucrona@users.noreply.github.com>
Date: Wed, 11 Nov 2026 07:29:21 +1100
Subject: [PATCH] Raise contrast in the WoW character popup header
The level, race, spec and class line uses a lighter AA grey, with the
class name in its class colour. The realm or ruleset is bright and
medium-bold, and the version and region stay a softer but still
AA-legible grey. The faction crest sits next to the character name
with a light outline, slightly larger than before.
---
apps/site-web/static/assets/app.js | 8 ++-
apps/site-web/static/assets/site.css | 11 ++-
apps/site-web/tests/wow-popup.test.cjs | 97 +++++++++++++++++++++++++-
3 files changed, 109 insertions(+), 7 deletions(-)
diff --git a/apps/site-web/static/assets/app.js b/apps/site-web/static/assets/app.js
index 47fa29b..ac60408 100644
--- a/apps/site-web/static/assets/app.js
+++ b/apps/site-web/static/assets/app.js
@@ -2241,11 +2241,13 @@ const WOWPOP = (() => {
if (pv.honor_level > 0 || pv.honorable_kills > 0 || (pv.rated || []).length) sections.push(`PvP
${pv.honor_level != null ? `
` : ""}${pv.honorable_kills != null ? `Honor level ${nf.format(pv.honor_level)} ` : ""}${(pv.rated || []).map(b => `Honorable kills ${nf.format(pv.honorable_kills)} `).join("")}${esc(b.bracket)} ${nf.format(b.rating)} Collections
${co.mounts != null ? `
` : ""}${co.pets != null ? `Mounts ${nf.format(co.mounts)} ` : ""}Pets ${nf.format(co.pets)} Professions
${pr.map(x => `
`).join("")}${esc(x.name)}${x.tier && x.tier !== x.name ? ` ${esc(x.tier)}` : ""} ${x.max ? `${x.skill}/${x.max}` : x.skill != null ? x.skill : ""}
`:'';},WH:{ok:!!opts.whOk,scan(){}},imgOk:'',
addEventListener:(name,fn)=>events[name]=fn,document:{createElement(){const node={remove(){},innerHTML:'',className:''};nodes.push(node);return node},body:{appendChild(){},classList:{add(){},remove(){}}},addEventListener(){},removeEventListener(){}},
$:(selector)=>selector==='.cp-x'?{focus(){}}:selector==='.cp-in'?inside:null,$$:()=>[],api:async()=>data,setTimeout:fn=>{timers.push(fn);return 0}};
const {WOWPOP,WOWLINK}=vm.runInNewContext(linkSource+avatarSource+popupSource+';({WOWPOP,WOWLINK})',ctx);
@@ -100,8 +100,101 @@ test('Share copies the stable URL for both versions, confirms, then resets',asyn
test('Forever characters without an Armory page stay open with a short note',()=>{
const {WOWPOP}=harness();
const html=WOWPOP.body({name:'Lucroena Ashborne',level:4,region:'us',realm:'PvP',game_id:'wow_forever',armory_unavailable:true,gear:[],stats:[]},{game_id:'wow_forever',name:'Lucroena Ashborne'});
- assert.ok(html.includes('PvP · Forever · US') || html.includes('PvP · Forever · us') || /PvP/.test(html) && /Forever/.test(html) && /US/.test(html));
+ const text=html.replace(/<[^>]+>/g,'');
+ assert.ok(text.includes('PvP · Forever · US'));
+ assert.ok(html.includes('class="cp-realm">PvP'));
+ assert.ok(html.includes('class="cp-ver"> · Forever · US'));
assert.ok(html.includes("The Armory doesn't list this character yet."));
assert.ok(!html.includes('View on Armory'));
assert.ok(html.includes('>Share'));
});
+
+function hexLum(hex){
+ const h=hex.replace('#','');
+ const n=h.length===3?h.split('').map(c=>c+c).join(''):h;
+ const rgb=[0,2,4].map(i=>parseInt(n.slice(i,i+2),16)/255);
+ const lin=c=>c<=0.04045?c/12.92:((c+0.055)/1.055)**2.4;
+ const [r,g,b]=rgb.map(lin);
+ return 0.2126*r+0.7152*g+0.0722*b;
+}
+function contrast(a,b){
+ const L1=hexLum(a),L2=hexLum(b),hi=Math.max(L1,L2),lo=Math.min(L1,L2);
+ return (hi+0.05)/(lo+0.05);
+}
+function ruleColor(css,sel){
+ const m=css.match(new RegExp(sel.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')+'\\s*\\{[^}]*\\}','m'));
+ assert.ok(m,sel+' rule');
+ const c=(m[0].match(/color:([^;}]+)/)||[])[1];
+ assert.ok(c,sel+' color');
+ const hex=(c.trim().match(/#(?:[0-9a-fA-F]{3}){1,2}/)||[])[0];
+ assert.ok(hex,sel+' hex color');
+ return hex;
+}
+
+test('popup header uses class colour, places the crest by the name, and keeps AA contrast',()=>{
+ const {WOWPOP}=harness();
+ const html=WOWPOP.body({
+ name:'Lucrona',level:22,race:'Undead',spec:'Retribution',class:'Paladin',class_id:2,
+ realm:'PvP',region:'us',game_id:'wow_forever',faction:'HORDE',
+ title:'Lucrona the Kingslayer',
+ guild:{name:'Ashen Host',rank:0}
+ },{game_id:'wow_forever',name:'Lucrona',faction:'HORDE'});
+ const text=html.replace(/<[^>]+>/g,'');
+ assert.ok(text.includes('Level 22 Undead Retribution Paladin'));
+ assert.ok(text.includes('PvP · Forever · US'));
+ assert.ok(text.includes('