Serve React WVS map at Pages root

Co-Authored-By: PI[gpt-5.6-terra] <288921227+claudypoo@users.noreply.github.com>
This commit is contained in:
wassnameandPI[gpt-5.6-terra] committed 2026-09-17 13:14:56 +08:00
1 parent 75d8f607ca
commit b429a68a4a
20 files changed
+201 -64

No files matched your search

@@ -6,6 +6,6 @@ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=
`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(`<anonymous>`)&&(u=u.replace(`<anonymous>`,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{Ue=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?He(n):``}function Ge(e,t){switch(e.tag){case 26:case 27:case 5:return He(e.type);case 16:return He(`Lazy`);case 13:return e.child!==t&&t!==null?He(`Suspense Fallback`):He(`Suspense`);case 19:return He(`SuspenseList`);case 0:case 15:return We(e.type,!1);case 11:return We(e.type.render,!1);case 1:return We(e.type,!0);case 31:return He(`Activity`);case 30:return He(`ViewTransition`);default:return``}}function Ke(e){try{var t=``,n=null;do t+=Ge(e,n),n=e,e=e.return;while(e);return t}catch(e){return`
Error generating stack: `+e.message+`
`+e.stack}}var qe=Object.prototype.hasOwnProperty,Je=t.unstable_scheduleCallback,Ye=t.unstable_cancelCallback,Xe=t.unstable_shouldYield,Ze=t.unstable_requestPaint,Qe=t.unstable_now,$e=t.unstable_getCurrentPriorityLevel,et=t.unstable_ImmediatePriority,tt=t.unstable_UserBlockingPriority,nt=t.unstable_NormalPriority,rt=t.unstable_LowPriority,it=t.unstable_IdlePriority,at=t.log,ot=t.unstable_setDisableYieldValue,st=null,ct=null;function lt(e){if(typeof at==`function`&&ot(e),ct&&typeof ct.setStrictMode==`function`)try{ct.setStrictMode(st,e)}catch{}}var ut=Math.clz32?Math.clz32:pt,dt=Math.log,ft=Math.LN2;function pt(e){return e>>>=0,e===0?32:31-(dt(e)/ft|0)|0}var mt=256,ht=262144,gt=4194304;function _t(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&-e;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function vt(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=_t(n))):i=_t(o):i=_t(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=_t(n))):i=_t(o)):i=_t(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function yt(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function bt(e,t){t&8&&(t|=t&32);var n=e.entangledLanes;if(n!==0)for(e=e.entanglements,n&=t;0<n;){var r=31-ut(n),i=1<<r;t|=e[r],n&=~i}return t}function xt(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function St(){var e=gt;return gt<<=1,!(gt&62914560)&&(gt=4194304),e}function Ct(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function wt(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Tt(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0<n;){var u=31-ut(n),d=1<<u;s[u]=0,c[u]=-1;var f=l[u];if(f!==null)for(l[u]=null,u=0;u<f.length;u++){var p=f[u];p!==null&&(p.lane&=-536870913)}n&=~d}r!==0&&Et(e,r,0),a!==0&&i===0&&e.tag!==0&&(e.suspendedLanes|=a&~(o&~t))}function Et(e,t,n){e.pendingLanes|=t,e.suspendedLanes&=~t;var r=31-ut(t);e.entangledLanes|=t,e.entanglements[r]=e.entanglements[r]|1073741824|n&261930}function Dt(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var r=31-ut(n),i=1<<r;i&t|e[r]&t&&(e[r]|=t),n&=~i}}function Ot(e,t){var n=t&-t;return n=n&42?1:kt(n),(n&(e.suspendedLanes|t))===0?n:0}function kt(e){switch(e){case 2:e=1;break;case 8:e=4;break;case 32:e=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:e=128;break;case 268435456:e=134217728;break;default:e=0}return e}function At(e){return e&=-e,2<e?8<e?e&134217727?32:268435456:8:2}function jt(){var e=T.p;return e===0?(e=window.event,e===void 0?32:Ch(e.type)):e}function Mt(e,t){var n=T.p;try{return T.p=e,t()}finally{T.p=n}}var Nt=Math.random().toString(36).slice(2),Pt=`__reactFiber$`+Nt,Ft=`__reactProps$`+Nt,It=`__reactContainer$`+Nt,Lt=`__reactEvents$`+Nt,Rt=`__reactListeners$`+Nt,zt=`__reactHandles$`+Nt,Bt=`__reactResources$`+Nt,Vt=`__reactMarker$`+Nt,Ht=`__reactLoad$`+Nt;function Ut(e){delete e[Pt],delete e[Ft],delete e[Rt],delete e[zt]}function Wt(e){var t;if(t=e[Pt])return t;for(var n=e.parentNode;n;){if(t=n[It]||n[Pt]){if(n=t.alternate,t.child!==null||n!==null&&n.child!==null)for(e=fm(e);e!==null;){if(n=e[Pt])return n;e=fm(e)}return t}e=n,n=e.parentNode}return null}function Gt(e){if(e=e[Pt]||e[It]){var t=e.tag;if(t===5||t===6||t===13||t===31||t===26||t===27||t===3)return e}return null}function Kt(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e.stateNode;throw Error(i(33))}function qt(e){var t=e[Bt];return t||=e[Bt]={hoistableStyles:new Map,hoistableScripts:new Map},t}function D(e){e[Vt]=!0}function Jt(e){e[Ht]=void 0}var Yt=new Set,Xt={};function Zt(e,t){Qt(e,t),Qt(e+`Capture`,t)}function Qt(e,t){for(Xt[e]=t,e=0;e<t.len Line truncated
`).replace(Qf,``)}function ep(e,t){return t=$f(t),$f(e)===t}function $(e,t,n,r,a,o){switch(n){case`children`:if(typeof r==`string`)t===`body`||t===`textarea`&&r===``||xn(e,r);else if(typeof r==`number`||typeof r==`bigint`)t!==`body`&&xn(e,``+r);else return;break;case`className`:on(e,`class`,r);break;case`tabIndex`:on(e,`tabindex`,r);break;case`dir`:case`role`:case`viewBox`:case`width`:case`height`:on(e,n,r);break;case`style`:wn(e,r,o);return;case`data`:if(t!==`object`){on(e,`data`,r);break}case`src`:case`href`:if(r===``&&(t!==`a`||n!==`href`)){e.removeAttribute(n);break}if(r==null||typeof r==`function`||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=On(r),e.setAttribute(n,r);break;case`action`:case`formAction`:if(typeof r==`function`){e.setAttribute(n,`javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')`);break}if(typeof o==`function`&&(n===`formAction`?(t!==`input`&&$(e,t,`name`,a.name,a,null),$(e,t,`formEncType`,a.formEncType,a,null),$(e,t,`formMethod`,a.formMethod,a,null),$(e,t,`formTarget`,a.formTarget,a,null)):($(e,t,`encType`,a.encType,a,null),$(e,t,`method`,a.method,a,null),$(e,t,`target`,a.target,a,null))),r==null||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=On(r),e.setAttribute(n,r);break;case`onClick`:r!=null&&(e.onclick=kn);return;case`onScroll`:r!=null&&Q(`scroll`,e);return;case`onScrollEnd`:r!=null&&Q(`scrollend`,e);return;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));o?.__html!==n&&(e.innerHTML=n)}}break;case`multiple`:e.multiple=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`muted`:e.muted=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`suppressContentEditableWarning`:case`suppressHydrationWarning`:case`defaultValue`:case`defaultChecked`:case`innerHTML`:case`ref`:break;case`autoFocus`:break;case`xlinkHref`:if(r==null||typeof r==`function`||typeof r==`boolean`||typeof r==`symbol`){e.removeAttribute(`xlink:href`);break}n=On(r),e.setAttributeNS(`http://www.w3.org/1999/xlink`,`xlink:href`,n);break;case`contentEditable`:case`spellCheck`:case`draggable`:case`value`:case`autoReverse`:case`externalResourcesRequired`:case`focusable`:case`preserveAlpha`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,r):e.removeAttribute(n);break;case`inert`:case`allowFullScreen`:case`async`:case`autoPlay`:case`controls`:case`credentialless`:case`default`:case`defer`:case`disabled`:case`disablePictureInPicture`:case`disableRemotePlayback`:case`formNoValidate`:case`hidden`:case`loop`:case`noModule`:case`noValidate`:case`open`:case`playsInline`:case`readOnly`:case`required`:case`reversed`:case`scoped`:case`seamless`:case`itemScope`:r&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,``):e.removeAttribute(n);break;case`capture`:case`download`:!0===r?e.setAttribute(n,``):!1!==r&&r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,r):e.removeAttribute(n);break;case`cols`:case`rows`:case`size`:case`span`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`&&!isNaN(r)&&1<=r?e.setAttribute(n,r):e.removeAttribute(n);break;case`rowSpan`:case`start`:r==null||typeof r==`function`||typeof r==`symbol`||isNaN(r)?e.removeAttribute(n):e.setAttribute(n,r);break;case`popover`:Q(`beforetoggle`,e),Q(`toggle`,e),an(e,`popover`,r);break;case`xlinkActuate`:sn(e,`http://www.w3.org/1999/xlink`,`xlink:actuate`,r);break;case`xlinkArcrole`:sn(e,`http://www.w3.org/1999/xlink`,`xlink:arcrole`,r);break;case`xlinkRole`:sn(e,`http://www.w3.org/1999/xlink`,`xlink:role`,r);break;case`xlinkShow`:sn(e,`http://www.w3.org/1999/xlink`,`xlink:show`,r);break;case`xlinkTitle`:sn(e,`http://www.w3.org/1999/xlink`,`xlink:title`,r);break;case`xlinkType`:sn(e,`http://www.w3.org/1999/xlink`,`xlink:type`,r);break;case`xmlBase`:sn(e,`http://www.w3.org/XML/1998/namespace`,`xml:base`,r);break;case`xmlLang`:sn(e,`http://www.w3.org/XML/1998/namespace`,`xml:lang`,r);break;case`xmlSpace`:sn(e,`http://www.w3.org/XML/1998/namespace`,`xml:space`,r);break;case`is`:an(e,`is`,r);break;case`innerText`:case`textContent`:return;default:if(!(2<n.length)||n[0]!==`o`&&n[0]!==`O`||n[1]!==`n`&&n[1]!==`N`)n=En.get(n)||n,an(e,n,r);else return}O=!0}function tp(e,t,n,r,a,o){switch(n){case`style`:wn(e,r,o);return;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));o?.__html!==n&&(e.innerHTML=n)}}break;case`children`:if(typeof r==`string`)xn(e,r);else if(typeof r==`number`||typeof r==`bigint`)xn(e,``+r);else return;break;case`onScroll`:r!=null&&Q(`scroll`,e);return;case`onScrollEnd`:r!=null&&Q(`scrollend`,e);return;case`onClick`:r!=null&&(e.onclick=k Line truncated
`).replace(Qf,``)}function ep(e,t){return t=$f(t),$f(e)===t}function $(e,t,n,r,a,o){switch(n){case`children`:if(typeof r==`string`)t===`body`||t===`textarea`&&r===``||xn(e,r);else if(typeof r==`number`||typeof r==`bigint`)t!==`body`&&xn(e,``+r);else return;break;case`className`:on(e,`class`,r);break;case`tabIndex`:on(e,`tabindex`,r);break;case`dir`:case`role`:case`viewBox`:case`width`:case`height`:on(e,n,r);break;case`style`:wn(e,r,o);return;case`data`:if(t!==`object`){on(e,`data`,r);break}case`src`:case`href`:if(r===``&&(t!==`a`||n!==`href`)){e.removeAttribute(n);break}if(r==null||typeof r==`function`||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=On(r),e.setAttribute(n,r);break;case`action`:case`formAction`:if(typeof r==`function`){e.setAttribute(n,`javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')`);break}if(typeof o==`function`&&(n===`formAction`?(t!==`input`&&$(e,t,`name`,a.name,a,null),$(e,t,`formEncType`,a.formEncType,a,null),$(e,t,`formMethod`,a.formMethod,a,null),$(e,t,`formTarget`,a.formTarget,a,null)):($(e,t,`encType`,a.encType,a,null),$(e,t,`method`,a.method,a,null),$(e,t,`target`,a.target,a,null))),r==null||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=On(r),e.setAttribute(n,r);break;case`onClick`:r!=null&&(e.onclick=kn);return;case`onScroll`:r!=null&&Q(`scroll`,e);return;case`onScrollEnd`:r!=null&&Q(`scrollend`,e);return;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));o?.__html!==n&&(e.innerHTML=n)}}break;case`multiple`:e.multiple=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`muted`:e.muted=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`suppressContentEditableWarning`:case`suppressHydrationWarning`:case`defaultValue`:case`defaultChecked`:case`innerHTML`:case`ref`:break;case`autoFocus`:break;case`xlinkHref`:if(r==null||typeof r==`function`||typeof r==`boolean`||typeof r==`symbol`){e.removeAttribute(`xlink:href`);break}n=On(r),e.setAttributeNS(`http://www.w3.org/1999/xlink`,`xlink:href`,n);break;case`contentEditable`:case`spellCheck`:case`draggable`:case`value`:case`autoReverse`:case`externalResourcesRequired`:case`focusable`:case`preserveAlpha`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,r):e.removeAttribute(n);break;case`inert`:case`allowFullScreen`:case`async`:case`autoPlay`:case`controls`:case`credentialless`:case`default`:case`defer`:case`disabled`:case`disablePictureInPicture`:case`disableRemotePlayback`:case`formNoValidate`:case`hidden`:case`loop`:case`noModule`:case`noValidate`:case`open`:case`playsInline`:case`readOnly`:case`required`:case`reversed`:case`scoped`:case`seamless`:case`itemScope`:r&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,``):e.removeAttribute(n);break;case`capture`:case`download`:!0===r?e.setAttribute(n,``):!1!==r&&r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,r):e.removeAttribute(n);break;case`cols`:case`rows`:case`size`:case`span`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`&&!isNaN(r)&&1<=r?e.setAttribute(n,r):e.removeAttribute(n);break;case`rowSpan`:case`start`:r==null||typeof r==`function`||typeof r==`symbol`||isNaN(r)?e.removeAttribute(n):e.setAttribute(n,r);break;case`popover`:Q(`beforetoggle`,e),Q(`toggle`,e),an(e,`popover`,r);break;case`xlinkActuate`:sn(e,`http://www.w3.org/1999/xlink`,`xlink:actuate`,r);break;case`xlinkArcrole`:sn(e,`http://www.w3.org/1999/xlink`,`xlink:arcrole`,r);break;case`xlinkRole`:sn(e,`http://www.w3.org/1999/xlink`,`xlink:role`,r);break;case`xlinkShow`:sn(e,`http://www.w3.org/1999/xlink`,`xlink:show`,r);break;case`xlinkTitle`:sn(e,`http://www.w3.org/1999/xlink`,`xlink:title`,r);break;case`xlinkType`:sn(e,`http://www.w3.org/1999/xlink`,`xlink:type`,r);break;case`xmlBase`:sn(e,`http://www.w3.org/XML/1998/namespace`,`xml:base`,r);break;case`xmlLang`:sn(e,`http://www.w3.org/XML/1998/namespace`,`xml:lang`,r);break;case`xmlSpace`:sn(e,`http://www.w3.org/XML/1998/namespace`,`xml:space`,r);break;case`is`:an(e,`is`,r);break;case`innerText`:case`textContent`:return;default:if(!(2<n.length)||n[0]!==`o`&&n[0]!==`O`||n[1]!==`n`&&n[1]!==`N`)n=En.get(n)||n,an(e,n,r);else return}O=!0}function tp(e,t,n,r,a,o){switch(n){case`style`:wn(e,r,o);return;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));o?.__html!==n&&(e.innerHTML=n)}}break;case`children`:if(typeof r==`string`)xn(e,r);else if(typeof r==`number`||typeof r==`bigint`)xn(e,``+r);else return;break;case`onScroll`:r!=null&&Q(`scroll`,e);return;case`onScrollEnd`:r!=null&&Q(`scrollend`,e);return;case`onClick`:r!=null&&(e.onclick=k Line truncated
`).map((e,t)=>(0,C.jsx)(`tspan`,{x:u.bounds.left+8,dy:t?17:0,children:e},e))}),(0,C.jsx)(`text`,{className:`map-note`,x:u.bounds.right-8,y:u.bounds.bottom-20,textAnchor:`end`,children:e.note.split(`
`).map((e,t)=>(0,C.jsx)(`tspan`,{x:u.bounds.right-8,dy:t?11:0,children:e},e))})]}),(0,C.jsx)(ce,{active:i,geometry:u,data:e})]}),(0,C.jsx)(de,{data:e,hidden:n})]})}function pe(){let[e,t]=(0,_.useState)(null);return(0,_.useEffect)(()=>{fetch(`../wvs_map_data.json`).then(e=>e.json()).then(t)},[]),(0,C.jsxs)(`main`,{children:[(0,C.jsx)(`h1`,{children:`Frontier LLMs on the World Values Survey`}),(0,C.jsx)(`p`,{className:`lede`,children:`React/SVG rendering of the shared WVS coordinate artifact. White-ring marks use locally saved lab logos. Focus or hover a model for its measured readout and release provenance.`}),e&&(0,C.jsx)(fe,{data:e})]})}(0,v.createRoot)(document.getElementById(`root`)).render((0,C.jsx)(pe,{}));
`).map((e,t)=>(0,C.jsx)(`tspan`,{x:u.bounds.right-8,dy:t?11:0,children:e},e))})]}),(0,C.jsx)(ue,{active:i,geometry:u,data:e})]}),(0,C.jsx)(pe,{data:e,hidden:n})]})}function he(){let[e,t]=(0,_.useState)(null);return(0,_.useEffect)(()=>{fetch(`wvs/wvs_map_data.json`).then(e=>e.json()).then(t)},[]),(0,C.jsxs)(`main`,{children:[(0,C.jsx)(`h1`,{children:`How do AI models score on human values surveys? Which culture are they most similar to? Is it changing over time?`}),(0,C.jsxs)(`p`,{className:`lede`,children:[`To answer these we start with the `,(0,C.jsx)(`a`,{href:`https://www.worldvaluessurvey.org/`,children:`World Values Survey`}),`, the standard culture map of the world. Since 1981 it has asked people in about ninety countries the same questions. Two axes drawn from it sort societies by how traditional or secular they are and how much they weigh survival over self-expression.`]}),(0,C.jsxs)(`p`,{className:`caption`,children:[`Use the family controls to compare saved model coordinates. Hover or keyboard focus a mark for its model-specific provenance. See the `,(0,C.jsx)(`a`,{href:`https://github.com/wassname/moral-maps`,children:`code and records`}),`.`]}),e&&(0,C.jsx)(me,{data:e})]})}(0,v.createRoot)(document.getElementById(`root`)).render((0,C.jsx)(he,{}));
File renamed without changes.
+12 -7
View File
@@ -1,8 +1,13 @@
<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>moralmaps</title>
<main>
<h1>moralmaps</h1>
<p><a href="wvs/">Frontier LLMs on the World Values Survey</a></p>
</main>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Frontier LLMs on the World Values Survey</title>
<script type="module" crossorigin src="./assets/index-CpA1DfjE.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-Ctfw88gw.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
+6 -3
View File
@@ -1,3 +1,6 @@
<script type="module" crossorigin src="./assets/index-C5aVwI6z.js"></script>
<link rel="stylesheet" crossorigin href="./assets/index-Ctfw88gw.css">
<div id="root"></div>
<!doctype html>
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=../../">
<link rel="canonical" href="../../">
<title>Frontier LLMs on the World Values Survey</title>
<p>The React WVS map moved to <a href="../../">the site root</a>.</p>
+12 -1
View File
@@ -1 +1,12 @@
<div id="root"></div><script type="module" src="/src/main.jsx"></script>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Frontier LLMs on the World Values Survey</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
+32 -13
View File
@@ -3,6 +3,12 @@ import { createRoot } from 'react-dom/client';
import { VIEW, assertLayout, roundedHull } from './layout.js';
import './style.css';
const LOGO_ROOT = 'wvs/';
function visibleFamilyNames(groups, hidden) {
return Object.keys(groups).filter(family => !hidden.has(family));
}
function Tooltip({ active, geometry, data, id = 'model-tooltip' }) {
if (!active) return null;
const { provenance } = active;
@@ -29,7 +35,7 @@ function ModelMarker({ model, placement, geometry, setActive, clearActive, marke
onFocus={() => setActive(model)} onBlur={clearActive}>
{leader && <line className="leader" x1={cx} y1={cy} x2={label.cx} y2={label.cy} />}
<circle className="model-ring" cx={cx} cy={cy} r="11" stroke={model.color} />
<image href={`../${logo}`} x={cx - 7} y={cy - 7} width="14" height="14" preserveAspectRatio="xMidYMid meet" />
<image href={`${LOGO_ROOT}${logo}`} x={cx - 7} y={cy - 7} width="14" height="14" preserveAspectRatio="xMidYMid meet" aria-hidden="true" focusable="false" />
{label && <text className="model-label" x={label.cx} y={label.cy + 4} textAnchor="middle">{model.label}</text>}
</g>;
}
@@ -37,6 +43,7 @@ function ModelMarker({ model, placement, geometry, setActive, clearActive, marke
function ReleaseScatter({ data, hidden, field, title }) {
const dated = useMemo(() => data.models.filter(model => Number.isFinite(Date.parse(model.provenance.release_created ?? '')))
.toSorted((a, b) => a.provenance.release_created.localeCompare(b.provenance.release_created) || a.name.localeCompare(b.name)), [data]);
const visible = dated.filter(model => !hidden.has(model.family));
const [active, setActive] = useState(null);
const width = 1200, height = 320, left = 74, right = 35, top = 42, bottom = 48;
const dates = dated.map(model => Date.parse(model.provenance.release_created));
@@ -45,16 +52,19 @@ function ReleaseScatter({ data, hidden, field, title }) {
const minValue = Math.min(...values), maxValue = Math.max(...values);
const dateX = date => left + (date - minDate) / (maxDate - minDate) * (width - left - right);
const valueY = value => top + (maxValue - value) / (maxValue - minValue || 1) * (height - top - bottom);
const panelId = `release-${field}`;
const tooltipId = `${panelId}-tooltip`;
const activate = (model, event) => {
const box = event.currentTarget.closest('.scatter-shell').getBoundingClientRect();
const point = event.currentTarget.getBoundingClientRect();
setActive({ ...model, tooltipLeft: `${Math.min(82, (point.left - box.left) / box.width * 100)}%`, tooltipTop: `${Math.min(78, (point.top - box.top) / box.height * 100)}%` });
};
return <section className="release-panel" data-coordinate={field} aria-labelledby={`release-${field}`}>
<h2 id={`release-${field}`}>{title}</h2>
<p>Dated releases only. Positions are descriptive, not a capability trend.</p>
return <section className="release-panel" data-coordinate={field} aria-labelledby={`${panelId}-heading`}>
<h2 id={`${panelId}-heading`}>{title}</h2>
<div className="scatter-shell">
<svg viewBox={`0 0 ${width} ${height}`} role="img" aria-label={title} data-panel-model-count={dated.length}>
<svg viewBox={`0 0 ${width} ${height}`} role="img" aria-labelledby={`${panelId}-svg-title ${panelId}-svg-desc`} data-panel-model-count={visible.length}>
<title id={`${panelId}-svg-title`}>{title}</title>
<desc id={`${panelId}-svg-desc`}>Scatter plot with release date on the horizontal axis and {field === 'y' ? 'Secular-Rational' : 'Self-expression'} on the vertical axis. {visible.length} dated models are visible from {visibleFamilyNames(Object.groupBy(data.models, model => model.family), hidden).join(', ') || 'no families'}. Each white-ring logo mark is a model. Hover or keyboard focus a mark for model-specific details.</desc>
<rect className="canvas" width={width} height={height} />
<g className="scatter-grid">{Array.from({ length: 5 }, (_, index) => <line key={index} x1={left} x2={width - right} y1={top + index * (height - top - bottom) / 4} y2={top + index * (height - top - bottom) / 4} />)}</g>
<line className="scatter-axis" x1={left} x2={width - right} y1={height - bottom} y2={height - bottom} />
@@ -64,13 +74,13 @@ function ReleaseScatter({ data, hidden, field, title }) {
<text className="scatter-tick" x={left - 8} y={top + 4} textAnchor="end">{maxValue.toFixed(2)}</text>
<text className="scatter-tick" x={left - 8} y={height - bottom} textAnchor="end">{minValue.toFixed(2)}</text>
{dated.map(model => <g key={model.name} className="release-mark" data-family={model.family} data-release-model={model.name} data-release-date={model.provenance.release_created} display={hidden.has(model.family) ? 'none' : 'inline'}
tabIndex="0" role="button" aria-label={`${model.name}, ${model.family}, ${model.provenance.release_created}`} aria-describedby="release-tooltip"
tabIndex="0" role="button" aria-label={`${model.name}, ${model.family}, ${model.provenance.release_created}`} aria-describedby={tooltipId}
onPointerEnter={event => activate(model, event)} onPointerLeave={() => setActive(null)} onFocus={event => activate(model, event)} onBlur={() => setActive(null)}>
<circle className="model-ring" cx={dateX(Date.parse(model.provenance.release_created))} cy={valueY(model[field])} r="10" stroke={model.color} />
<image href={`../${data.logos[model.family]}`} x={dateX(Date.parse(model.provenance.release_created)) - 6.5} y={valueY(model[field]) - 6.5} width="13" height="13" preserveAspectRatio="xMidYMid meet" />
<image href={`${LOGO_ROOT}${data.logos[model.family]}`} x={dateX(Date.parse(model.provenance.release_created)) - 6.5} y={valueY(model[field]) - 6.5} width="13" height="13" preserveAspectRatio="xMidYMid meet" aria-hidden="true" focusable="false" />
</g>)}
</svg>
<Tooltip active={active} geometry={null} data={data} id="release-tooltip" />
<Tooltip active={active} geometry={null} data={data} id={tooltipId} />
</div>
</section>;
}
@@ -90,6 +100,8 @@ function Map({ data }) {
const focusRef = useRef(null);
const groups = useMemo(() => Object.groupBy(data.models, model => model.family), [data]);
const { labels, geometry } = useMemo(() => assertLayout(data), [data]);
const visibleFamilies = visibleFamilyNames(groups, hidden);
const visibleModelCount = data.models.filter(model => !hidden.has(model.family)).length;
useEffect(() => {
if (focusName) focusRef.current?.focus();
@@ -106,14 +118,16 @@ function Map({ data }) {
});
const xMedian = geometry.x(data.median.x), yMedian = geometry.y(data.median.y);
return <>
<section className="controls" aria-label="Model-family visibility">{Object.entries(groups).map(([family, models]) => {
<section className="controls" aria-label="Model-family visibility">{Object.entries(groups).map(([family]) => {
const visible = !hidden.has(family);
return <button key={family} className="chip" type="button" aria-pressed={visible} onClick={() => toggle(family)}>
<img src={`../${data.logos[family]}`} alt="" />{family}
<img src={`${LOGO_ROOT}${data.logos[family]}`} alt="" aria-hidden="true" />{family}
</button>;
})}</section>
<div className="chart-shell">
<svg viewBox={`0 0 ${VIEW.width} ${VIEW.height}`} role="img" aria-label="Frontier LLMs on the World Values Survey" data-median-x={data.median.x} data-median-y={data.median.y}>
<svg viewBox={`0 0 ${VIEW.width} ${VIEW.height}`} role="img" aria-labelledby="map-svg-title map-svg-desc" data-median-x={data.median.x} data-median-y={data.median.y} data-visible-model-count={visibleModelCount}>
<title id="map-svg-title">Frontier LLMs on the World Values Survey</title>
<desc id="map-svg-desc">World Values Survey cultural map. Horizontal direction runs from Self-expression on the left to Survival on the right. Vertical direction runs from Traditional below to Secular-Rational above. Coloured dots are selected WVS countries, outlines are cultural regions, and white-ring logo marks are models. {visibleModelCount} model marks are visible from {visibleFamilies.join(', ') || 'no families'}. Use the family controls to hide marks and labels. Hover or keyboard focus a model for model-specific details.</desc>
<defs><marker id="arrow" markerWidth="8" markerHeight="8" refX="6" refY="3" orient="auto"><path d="M0,0 L0,6 L6,3 z" className="arrow" /></marker></defs>
<rect className="canvas" width={VIEW.width} height={VIEW.height} />
<g className="grid">{Array.from({ length: 8 }, (_, index) => <line key={`v${index}`} x1={geometry.bounds.left + index * (geometry.bounds.right - geometry.bounds.left) / 7} y1={geometry.bounds.top} x2={geometry.bounds.left + index * (geometry.bounds.right - geometry.bounds.left) / 7} y2={geometry.bounds.bottom} />)}{Array.from({ length: 6 }, (_, index) => <line key={`h${index}`} x1={geometry.bounds.left} y1={geometry.bounds.top + index * (geometry.bounds.bottom - geometry.bounds.top) / 5} x2={geometry.bounds.right} y2={geometry.bounds.top + index * (geometry.bounds.bottom - geometry.bounds.top) / 5} />)}</g>
@@ -135,8 +149,13 @@ function Map({ data }) {
function App() {
const [data, setData] = useState(null);
useEffect(() => { fetch('../wvs_map_data.json').then(response => response.json()).then(setData); }, []);
return <main><h1>Frontier LLMs on the World Values Survey</h1><p className="lede">React/SVG rendering of the shared WVS coordinate artifact. White-ring marks use locally saved lab logos. Focus or hover a model for its measured readout and release provenance.</p>{data && <Map data={data} />}</main>;
useEffect(() => { fetch('wvs/wvs_map_data.json').then(response => response.json()).then(setData); }, []);
return <main>
<h1>How do AI models score on human values surveys? Which culture are they most similar to? Is it changing over time?</h1>
<p className="lede">To answer these we start with the <a href="https://www.worldvaluessurvey.org/">World Values Survey</a>, the standard culture map of the world. Since 1981 it has asked people in about ninety countries the same questions. Two axes drawn from it sort societies by how traditional or secular they are and how much they weigh survival over self-expression.</p>
<p className="caption">Use the family controls to compare saved model coordinates. Hover or keyboard focus a mark for its model-specific provenance. See the <a href="https://github.com/wassname/moral-maps">code and records</a>.</p>
{data && <Map data={data} />}
</main>;
}
createRoot(document.getElementById('root')).render(<App/>);
+66 -37
View File
@@ -1,17 +1,17 @@
"""Playwright UAT for the production React WVS SVG. No network calls leave localhost."""
"""Playwright UAT for the root React WVS page. No network calls leave localhost."""
from __future__ import annotations
import hashlib
import json
import shutil
import subprocess
from pathlib import Path
from PIL import Image, ImageDraw
from playwright.sync_api import sync_playwright
ROOT = Path(__file__).resolve().parents[2]
OUT = ROOT / "slop/research/wvs/20260916_openrouter"
BASE = "http://localhost:8081/wvs"
BASE = "http://localhost:8081"
VIEWPORT = {"width": 1440, "height": 1800}
@@ -21,6 +21,15 @@ def screenshot(page, url: str, name: str) -> Path:
return OUT / name
def svg_description(page, selector: str, title_id: str, desc_id: str) -> None:
svg = page.locator(selector)
assert svg.get_attribute("role") == "img"
assert svg.get_attribute("aria-labelledby") == f"{title_id} {desc_id}"
assert svg.locator(f"title#{title_id}").count() == 1
assert svg.locator(f"desc#{desc_id}").count() == 1
assert svg.locator(f"desc#{desc_id}").text_content()
def main() -> None:
OUT.mkdir(parents=True, exist_ok=True)
chrome = shutil.which("google-chrome")
@@ -40,17 +49,28 @@ def main() -> None:
errors: list[str] = []
page.on("pageerror", lambda error: errors.append(str(error)))
screenshot(page, "http://localhost:8081/img/wvs/wvs_map_iw.png", "wvs_static_playwright.png")
screenshot(page, f"{BASE}/", "wvs_vanilla_playwright.png")
screenshot(page, f"{BASE}/react/", "wvs_react_playwright_default.png")
screenshot(page, f"{BASE}/img/wvs/wvs_map_iw.png", "wvs_static_playwright.png")
screenshot(page, f"{BASE}/wvs/", "wvs_vanilla_playwright.png")
assert page.locator("#map").count() == 1
screenshot(page, f"{BASE}/", "wvs_react_root_playwright_default.png")
page.wait_for_selector("svg .model-mark")
assert page.title() == "Frontier LLMs on the World Values Survey"
assert page.locator("html").get_attribute("lang") == "en"
assert page.locator('meta[name="viewport"]').count() == 1
assert page.locator("h1").inner_text().startswith("How do AI models score on human values surveys?")
assert "React/SVG rendering" not in page.locator("main").inner_text()
assert "Artificial Analysis" not in page.locator("main").inner_text()
assert "Historical coordinates" not in page.locator("main").inner_text()
assert "Dated releases only" not in page.locator("main").inner_text()
map_svg = page.locator("svg[data-median-x]")
svg_description(page, "svg[data-median-x]", "map-svg-title", "map-svg-desc")
assert "64 model marks" in map_svg.locator("desc").text_content()
assert page.locator("path.zone").count() == 4
assert page.locator("polygon.zone").count() == 0
assert "64 models" not in page.locator("main").inner_text()
assert "Historical coordinates" not in page.locator("main").inner_text()
assert page.locator(".map-note").get_attribute("text-anchor") == "end"
assert float(page.locator(".map-note").get_attribute("x")) > float(page.locator(".map-title").get_attribute("x"))
map_svg = page.get_by_role("img", name="Frontier LLMs on the World Values Survey")
assert float(map_svg.get_attribute("data-median-x")) == data["median"]["x"]
assert float(map_svg.get_attribute("data-median-y")) == data["median"]["y"]
rendered_models = page.locator(".model-mark").evaluate_all(
@@ -61,33 +81,36 @@ def main() -> None:
"nodes => nodes.map(node => [node.dataset.country, Number(node.dataset.x), Number(node.dataset.y)])"
)
assert rendered_countries == [[country["name"], country["x"], country["y"]] for country in data["countries"]]
assert page.locator(".chip").count() == 13
assert all(name for name in page.locator(".chip").evaluate_all("nodes => nodes.map(node => node.getAttribute('aria-label') || node.innerText)"))
assert all(value == "true" for value in page.locator(".chip img").evaluate_all("nodes => nodes.map(node => node.getAttribute('aria-hidden'))"))
assert all(value == "true" for value in page.locator(".model-mark image, .release-mark image").evaluate_all("nodes => nodes.map(node => node.getAttribute('aria-hidden'))"))
assert all(page.locator(".model-mark").evaluate_all("nodes => nodes.map(node => node.getAttribute('aria-label'))"))
country_before = page.locator("circle.country").evaluate_all(
"nodes => nodes.map(node => [node.getAttribute('cx'), node.getAttribute('cy')])"
)
dated = [model for model in data["models"] if model["provenance"]["release_created"]]
qwen_group = map_svg.locator(':scope > g[data-family="qwen"]')
assert qwen_group.get_attribute("display") in (None, "inline")
assert map_svg.locator('[data-family="qwen"] .model-label').count() == 1
assert page.locator(".release-panel").count() == 2
svg_description(page, '.release-panel[data-coordinate="y"] svg', "release-y-svg-title", "release-y-svg-desc")
svg_description(page, '.release-panel[data-coordinate="x"] svg', "release-x-svg-title", "release-x-svg-desc")
assert page.locator(".release-mark").count() == len(dated) * 2
for panel in page.locator(".release-panel").all():
dates = panel.locator(".release-mark").evaluate_all("nodes => nodes.map(node => node.dataset.releaseDate)")
assert dates == sorted(dates)
assert page.locator(".release-trajectory").count() == 0
page.screenshot(path=OUT / "wvs_react_playwright_release_panels.png", full_page=True)
page.get_by_role("button", name="qwen", exact=True).click()
page.wait_for_timeout(100)
qwen_group = map_svg.locator(':scope > g[data-family="qwen"]')
assert qwen_group.get_attribute("display") == "none"
assert map_svg.locator('[data-family="qwen"] .model-label').count() == 1
assert map_svg.locator('[data-family="muse"] .model-label').count() == 1
assert "qwen" not in map_svg.locator("desc").text_content().split("from ", 1)[1]
assert all(group.get_attribute("display") == "none" for group in page.locator('.release-panel [data-family="qwen"]').all())
assert all(group.get_attribute("display") != "none" for group in page.locator('.release-panel [data-family="muse"]').all())
country_after = page.locator("circle.country").evaluate_all(
"nodes => nodes.map(node => [node.getAttribute('cx'), node.getAttribute('cy')])"
)
assert country_after == country_before
page.screenshot(path=OUT / "wvs_react_playwright_qwen_hidden.png", full_page=True)
page.screenshot(path=OUT / "wvs_react_root_playwright_qwen_hidden.png", full_page=True)
page.get_by_role("button", name="qwen", exact=True).click()
marker = page.locator('[data-model="qwen3.8-flash"]')
@@ -96,48 +119,54 @@ def main() -> None:
tooltip_text = page.locator("#model-tooltip").inner_text()
for required in ("qwen3.8-flash", "qwen", "rated categorical response", "12 items x 12 samples"):
assert required in tooltip_text, required
page.screenshot(path=OUT / "wvs_react_playwright_tooltip_hover.png", full_page=True)
page.screenshot(path=OUT / "wvs_react_root_playwright_tooltip_hover.png", full_page=True)
marker.focus()
assert page.evaluate("document.activeElement.dataset.model") == "qwen3.8-flash"
assert page.locator("#model-tooltip").is_visible()
page.screenshot(path=OUT / "wvs_react_playwright_keyboard_focus.png", full_page=True)
page.screenshot(path=OUT / "wvs_react_root_playwright_keyboard_focus.png", full_page=True)
release_marker = page.locator('.release-panel[data-coordinate="y"] [data-release-model="qwen3.8-flash"]')
release_marker.locator(".model-ring").hover()
page.wait_for_selector("#release-tooltip")
assert "qwen3.8-flash" in page.locator("#release-tooltip").inner_text()
page.screenshot(path=OUT / "wvs_react_playwright_release_panel_hover.png", full_page=True)
page.wait_for_selector("#release-y-tooltip")
assert "qwen3.8-flash" in page.locator("#release-y-tooltip").inner_text()
page.screenshot(path=OUT / "wvs_react_root_playwright_release_panel_hover.png", full_page=True)
release_marker.focus()
assert page.evaluate("document.activeElement.dataset.releaseModel") == "qwen3.8-flash"
assert page.locator("#release-tooltip").is_visible()
page.screenshot(path=OUT / "wvs_react_playwright_release_panel_keyboard_focus.png", full_page=True)
assert page.locator("#release-y-tooltip").is_visible()
page.screenshot(path=OUT / "wvs_react_root_playwright_release_panel_keyboard_focus.png", full_page=True)
release_x_marker = page.locator('.release-panel[data-coordinate="x"] [data-release-model="qwen3.8-flash"]')
release_x_marker.locator(".model-ring").hover()
page.wait_for_selector("#release-x-tooltip")
assert page.locator("#release-x-tooltip").is_visible()
assert release_marker.get_attribute("aria-describedby") == "release-y-tooltip"
assert release_x_marker.get_attribute("aria-describedby") == "release-x-tooltip"
screenshot(page, f"{BASE}/wvs/react/", "wvs_react_compat_redirect.png")
page.wait_for_selector("svg .model-mark")
assert page.url == f"{BASE}/"
browser.close()
if errors:
raise RuntimeError(f"React page errors: {errors}")
static = Image.open(OUT / "wvs_static_playwright.png").convert("RGB")
react = Image.open(OUT / "wvs_react_playwright_default.png").convert("RGB")
height = max(static.height, react.height)
comparison = Image.new("RGB", (static.width + react.width, height + 26), "white")
comparison.paste(static, (0, 26))
comparison.paste(react, (static.width, 26))
draw = ImageDraw.Draw(comparison)
draw.text((6, 5), "Static WVS figure", fill="black")
draw.text((static.width + 6, 5), "React WVS production page", fill="black")
comparison.save(OUT / "wvs_static_react_playwright_side_by_side.png")
subprocess.run([
"convert", str(OUT / "wvs_static_playwright.png"), str(OUT / "wvs_react_root_playwright_default.png"),
"+append", str(OUT / "wvs_static_react_root_playwright_side_by_side.png"),
], check=True)
hashes = {path.name: hashlib.sha256(path.read_bytes()).hexdigest() for path in (
ROOT / "docs/wvs/wvs_map_data.json",
ROOT / "docs/wvs/react/index.html",
ROOT / "docs/index.html",
)}
print(json.dumps({
"root": "React app loaded at /",
"compatibility": "/wvs/ static retained; /wvs/react/ redirects to root",
"shared": {"models": 64, "countries": 90, "buffered_hulls": 4, "latest_labels": 13,
"numeric_model_country_median_equality": "verified against DOM data attributes"},
"svg_accessibility": "main map and both release panels have stable title/desc aria-labelledby; descriptions update after family visibility change",
"qwen_toggle": "clicked, map and dated-panel family groups hidden, country coordinates invariant",
"tooltip": "map and release-panel pointer hover and focus show model, family, coordinates, readout, samples and release metadata",
"tooltip": "map and release-panel pointer hover and focus show model-specific fields",
"release_panels": {"panels": 2, "dated_models": len(dated), "date_order": "DOM order verified", "no_paths": True},
"copy": "source caption only; no model-count wording or archaeological paragraph",
"visual_contract": "four smooth SVG paths, no zone polygons, source note right of title",
"copy": "reader-facing WVS introduction; no model-count, archaeological, implementation, or Artificial Analysis text",
"hashes": hashes,
}, indent=2))
+1 -1
View File
@@ -2,5 +2,5 @@ import { defineConfig } from 'vite';
export default defineConfig({
base: './',
build: { outDir: '../../docs/wvs/react', emptyOutDir: true },
build: { outDir: '../../docs', emptyOutDir: false },
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 KiB

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 387 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

@@ -0,0 +1,25 @@
{
"root": "React app loaded at /",
"compatibility": "/wvs/ static retained; /wvs/react/ redirects to root",
"shared": {
"models": 64,
"countries": 90,
"buffered_hulls": 4,
"latest_labels": 13,
"numeric_model_country_median_equality": "verified against DOM data attributes"
},
"svg_accessibility": "main map and both release panels have stable title/desc aria-labelledby; descriptions update after family visibility change",
"qwen_toggle": "clicked, map and dated-panel family groups hidden, country coordinates invariant",
"tooltip": "map and release-panel pointer hover and focus show model-specific fields",
"release_panels": {
"panels": 2,
"dated_models": 48,
"date_order": "DOM order verified",
"no_paths": true
},
"copy": "reader-facing WVS introduction; no model-count, archaeological, implementation, or Artificial Analysis text",
"hashes": {
"wvs_map_data.json": "c0b7aa0554ebbf5d819308932e058f4ff6e19d1e817ffebbbe381dbd2d063ffe",
"index.html": "c9994265bee6da60b24596f34c1973426a97e34a086da152bcd9486700750edb"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 775 KiB

@@ -0,0 +1,22 @@
# Final review: root React WVS deployment
Reviewer: `reviewer`, read-only, 2026-09-17.
## Verdict
PASS. The root document has `lang=en`, viewport metadata, and the WVS title. It loads the current root React bundle. The standalone `/wvs/` renderer remains, while `/wvs/react/` is a redirect stub with no second bundle.
## Observed
- Main map uses `role=img` with `aria-labelledby="map-svg-title map-svg-desc"`, one stable title and one visibility-dependent description.
- Both scatter panels have their own title/description pairs and per-panel tooltip IDs: `release-y-tooltip` and `release-x-tooltip`.
- Chip and marker logos are decorative. Parent controls and markers have accessible names.
- The root copy is reader-facing and has no visible Artificial Analysis claim, model-count wording, or implementation archaeology.
- Default, Qwen-hidden, tooltip, and side-by-side captures are legible. The Qwen state removes its marks and label while countries and zones do not move.
- `scripts/wvs_react/uat.py` records successful localhost checks for 64 models, 90 countries, median/model/country equality with shared JSON, SVG descriptions, tooltip/focus behavior, root route, static route, redirect compatibility, and Qwen visibility behavior.
## Limits
The review inspected source and captures, not the public deployment or a screen-reader session. Tooltip descriptions mount only when a mark is active, and focusable marks use `role=button` even though Enter/Space has no action. These are non-blocking for the assigned root/a11y revision.
-- reviewer
@@ -0,0 +1,23 @@
# Root React WVS image/a11y review
Reviewer: reviewer-openai, read-only, 2026-09-17.
## Verdict before follow-up
Changes requested.
1. `docs/index.html` had only a module script, stylesheet link, and root div. Add document language, title, and viewport metadata.
2. Both release panels used `release-tooltip` as their tooltip ID. Mixed keyboard/pointer use could create duplicate IDs and ambiguous descriptions. Use panel-specific tooltip IDs.
## Passed source/image checks
- Root source is React; `/wvs/` remains standalone static; `/wvs/react/` is a redirect and does not load a second bundle.
- The reader-facing introduction has no implementation archaeology, model count, or Artificial Analysis claim.
- Main map and both panel SVGs have title/description IDs referenced from `aria-labelledby`; descriptions derive visible counts and families from hidden-family state.
- Embedded logos are decorative and parent controls/markers carry names.
- Default and Qwen-hidden screenshots show country and region geometry unchanged while Qwen marks disappear.
- The supplied map tooltip is readable and contains model-specific fields.
Evidence limit: this reviewer inspected source and captures, not a browser accessibility tree or deployed output.
-- reviewer-openai