Three fictional venues, a required-access check, and two weight scenarios that change the ranking
HTML for this example
Select the code and use your copy shortcut. Changes in this preview are temporary.
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Decision matrix · compare one venue</title><style>
*{box-sizing:border-box}body{margin:0;background:#f3f5f8;color:#172b40;font:16px/1.5 system-ui,sans-serif}main{max-width:950px;margin:auto;padding:22px}h1{font-size:30px;line-height:1.15;margin:6px 0 12px}h2{font-size:21px;margin:0 0 10px}p{margin:8px 0 13px}.eyebrow{font-size:16px;color:#50677a;letter-spacing:.04em}.rule{padding:13px 16px;border-left:4px solid #b56522;background:#fff6df;margin:16px 0}.weights{display:grid;grid-template-columns:repeat(3,1fr);gap:13px}label{display:block;font-weight:650}input{font:inherit;width:100%;min-width:0;padding:9px;margin-top:6px;min-height:44px;border:1px solid #8192a5;border-radius:4px}.toolbar{display:flex;flex-wrap:wrap;gap:9px;margin:14px 0}button{font:inherit;min-height:44px;padding:9px 13px;border:1px solid #57738c;border-radius:4px;color:#172b40;background:white;cursor:pointer}button.primary{background:#244b70;color:white}button:focus-visible,input:focus-visible{outline:3px solid #b56522;outline-offset:3px}table{width:100%;border-collapse:collapse;margin:20px 0;background:white}caption{text-align:left;font-weight:700;padding:0 0 9px}th,td{text-align:left;vertical-align:top;border:1px solid #becad5;padding:11px}thead{background:#e4ebf2}tbody th{font-weight:700}.status{display:block;font-size:16px;margin-top:4px}.excluded{background:#f6eee5}#result{padding:17px;background:white;border-top:5px solid #244b70;margin:18px 0}#recommendation{font-size:23px;font-weight:750;margin:0 0 8px}#calculations{padding-left:22px}#notice{color:#8c3d18;font-weight:650}footer{color:#50677a;margin:20px 0}[hidden]{display:none!important}@media(max-width:540px){main{padding:12px}h1{font-size:26px}.weights{grid-template-columns:1fr;gap:11px}.weights label{display:grid;grid-template-columns:1fr 95px;align-items:center;gap:8px}.weights input{margin:0}table,tbody,tr,td,tbody th{display:block}caption{display:block;width:100%}thead{display:none}tr{border:1px solid #becad5;margin:12px 0}td,tbody th{border:0;border-bottom:1px solid #e0e5eb;padding:9px 12px}td::before{content:attr(data-label) ': ';font-weight:650}.excluded{background:#f6eee5}}
</style></head><body><main><p class="eyebrow">ORIGINAL FICTIONAL FIXTURE · CHOOSE ONE VENUE</p><h1>Decision matrix</h1><p>Compare the eligible options, then test whether a change in priorities changes the leader.</p><div class="rule"><strong>Must-have: step-free access.</strong> Maple and Loft meet the supplied requirement. Gallery does not and is excluded. These are invented teaching facts, not venue or access assessments.</div><section aria-labelledby="weightsHeading"><h2 id="weightsHeading">Preference weights</h2><div class="weights"><label for="affordability">Affordability (%)<input id="affordability" type="number" min="0" max="100" step="any" value="50"></label><label for="layout">Room layout (%)<input id="layout" type="number" min="0" max="100" step="any" value="30"></label><label for="transit">Transit access (%)<input id="transit" type="number" min="0" max="100" step="any" value="20"></label></div><div class="toolbar"><button id="original" type="button">Original priorities</button><button id="layoutMore" type="button">Layout matters more</button><button id="compare" class="primary" type="button">Compare venues</button></div><p id="notice" role="status" aria-live="polite"></p></section><section id="result" aria-live="polite" aria-label="Calculated venue comparison"><p id="recommendation">Maple leads: 4.20 / 5</p><p id="gap">Loft: 4.00. The difference is 0.20 points.</p><ul id="calculations"><li>Maple: 5 × 50% + 3 × 30% + 4 × 20% = 4.20</li><li>Loft: 3 × 50% + 5 × 30% + 5 × 20% = 4.00</li></ul><p id="exclusion">Gallery remains excluded: the supplied step-free-access requirement is not met.</p></section><table><caption>Supplied preference ratings · 1 to 5, where 5 is favorable</caption><thead><tr><th scope="col">Venue and eligibility</th><th scope="col">Affordability</th><th scope="col">Room layout</th><th scope="col">Transit access</th></tr></thead><tbody><tr><th scope="row">Maple<span class="status">Meets access requirement</span></th><td data-label="Affordability">5</td><td data-label="Room layout">3</td><td data-label="Transit access">4</td></tr><tr><th scope="row">Loft<span class="status">Meets access requirement</span></th><td data-label="Affordability">3</td><td data-label="Room layout">5</td><td data-label="Transit access">5</td></tr><tr class="excluded"><th scope="row">Gallery<span class="status">Excluded: required access is not met</span></th><td data-label="Affordability">4</td><td data-label="Room layout">4</td><td data-label="Transit access">3</td></tr></tbody></table><footer>Ratings are illustrative. A preference total does not establish an objectively best choice. Confirm actual evidence and record the real decision before booking. Edits stay in this open page.</footer></main><script>
(()=>{'use strict';const $=s=>document.querySelector(s),keys=['affordability','layout','transit'],names=['Affordability','Room layout','Transit access'],options=[{name:'Maple',ratings:[5,3,4],eligible:true},{name:'Loft',ratings:[3,5,5],eligible:true},{name:'Gallery',ratings:[4,4,3],eligible:false}];
function compare(){const weights=[];$('#result').hidden=true;for(let i=0;i<keys.length;i++){const raw=$('#'+keys[i]).value.trim(),value=Number(raw);if(raw===''){$('#notice').textContent='Enter '+names[i]+' weight. A missing value is not zero.';return;}if(!Number.isFinite(value)||value<0||value>100){$('#notice').textContent=names[i]+' must be a number from 0 to 100.';return;}weights.push(value);}const sum=weights.reduce((a,b)=>a+b,0);if(Math.abs(sum-100)>1e-8){$('#notice').textContent='Weights must total 100%. Current total: '+Number(sum.toFixed(8))+'%.';return;}const ranked=options.filter(o=>o.eligible).map(o=>({...o,total:o.ratings.reduce((n,r,i)=>n+r*weights[i]/100,0)})).sort((a,b)=>b.total-a.total);const tied=Math.abs(ranked[0].total-ranked[1].total)<1e-9;$('#recommendation').textContent=tied?'Tie: Maple and Loft ('+ranked[0].total.toFixed(2)+' / 5)':ranked[0].name+' leads: '+ranked[0].total.toFixed(2)+' / 5';$('#gap').textContent=tied?'The supplied preferences do not separate the eligible venues. Discuss the evidence and priorities.':ranked[1].name+': '+ranked[1].total.toFixed(2)+'. The difference is '+(ranked[0].total-ranked[1].total).toFixed(2)+' points.';$('#calculations').replaceChildren();for(const o of ranked){const li=document.createElement('li');li.textContent=o.name+': '+o.ratings.map((r,i)=>r+' × '+weights[i]+'%').join(' + ')+' = '+o.total.toFixed(2);$('#calculations').append(li);}$('#result').hidden=false;$('#notice').textContent='Comparison updated. Gallery is excluded before scoring.';}
for(const k of keys)$('#'+k).addEventListener('input',()=>{$('#result').hidden=true;$('#notice').textContent='Weights changed. Compare again to update the result.';});function load(values){keys.forEach((k,i)=>{$('#'+k).value=values[i];});compare();}$('#original').addEventListener('click',()=>load([50,30,20]));$('#layoutMore').addEventListener('click',()=>load([40,40,20]));$('#compare').addEventListener('click',compare);})();
</script></body></html>