A library room-change map with reasons, questions, and an Unassessed area
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>Stakeholder analysis · library room change</title><style>
*{box-sizing:border-box}body{margin:0;background:#f6f4f8;color:#2e293c;font:16px/1.5 system-ui,sans-serif}main{max-width:980px;padding:24px;margin:auto}h1{font-size:30px;line-height:1.15;margin:5px 0 12px}h2{font-size:21px;line-height:1.3;margin:0 0 11px}h3{font-size:17px;line-height:1.35;margin:0 0 10px}p{margin:8px 0 14px}.eyebrow{font-size:14px;letter-spacing:.05em;color:#666079}.context{max-width:70ch}.definitions{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin:20px 0}.definitions p{margin:0;padding-left:12px;border-left:3px solid #8774a5}.definitions strong{display:block}.map{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:15px 0}.quadrant{border:1px solid #b6abc5;border-top:4px solid #695084;padding:14px;background:white;min-height:145px}.quadrant:nth-child(2){border-top-color:#3b6278}.quadrant:nth-child(3){border-top-color:#897149}.quadrant:nth-child(4){border-top-color:#6b7565}.members{display:grid;gap:8px}.group{width:100%;text-align:left;padding:10px;border:1px solid #a89bb7;background:#f8f5fb;color:#2e293c;border-radius:3px;cursor:pointer;font:inherit;line-height:1.4;overflow-wrap:anywhere;min-height:44px}.group[aria-pressed=true]{outline:2px solid #67477d;outline-offset:1px;background:#eee5f5}.group small{display:block;font-size:13px;color:#655b73}.unassessed{border:2px dashed #987947;background:#fffbf2;padding:14px;margin:16px 0}.unassessed .group{background:white}.empty{color:#736d7d;font-size:15px;margin:0}.assessment{padding:18px;border-left:5px solid #695084;background:#eae4f0;margin:22px 0}fieldset{border:0;padding:0;margin:12px 0}legend{font-weight:700;margin-bottom:6px}.choices{display:flex;gap:9px;flex-wrap:wrap}.choices label{display:flex;gap:7px;align-items:center;min-height:44px;border:1px solid #9b8dad;background:white;padding:8px 12px;border-radius:4px}.choices input{width:18px;height:18px;accent-color:#67477d}button.restore{font:inherit;min-height:44px;padding:10px 14px;background:white;color:#2e293c;border:1px solid #867194;border-radius:4px;cursor:pointer}button:focus-visible,input:focus-visible{outline:3px solid #a36426;outline-offset:3px}.status{font-size:15px;color:#6a4d29;min-height:1.5em}.ledger{display:grid;gap:14px;margin:18px 0}.record{padding:15px 0;border-top:1px solid #d1c9d8}.record p:last-child{margin-bottom:0}.record .label{font-weight:700}.record .assessmentText{font-size:15px;color:#655b73}.draftFlag{color:#7b491d;font-size:15px}footer{color:#655b73;font-size:15px;margin-top:22px}[hidden]{display:none!important}@media(max-width:520px){main{padding:14px}h1{font-size:27px}.definitions,.map{grid-template-columns:1fr}.quadrant{min-height:0}.assessment{padding:14px}.choices label{flex:1;justify-content:center;padding:8px;min-width:0}}
</style></head><body><main>
<p class="eyebrow">FICTIONAL LIBRARY EXAMPLE</p><h1>Who needs to be involved?</h1><p class="context" id="projectDescription">The project changes how the library's shared meeting room is booked. These group assessments are supplied planning assumptions. Check the reason for each placement before planning a conversation.</p>
<div class="definitions"><p><strong>Influence</strong>Ability to affect this project's decisions.</p><p><strong>Interest</strong>How much the change matters to a group's work or use of the room.</p></div>
<p id="mapSummary">Four groups mapped; one assessment incomplete.</p>
<section class="map" aria-label="Influence and interest map"><section class="quadrant" id="qHighHigh"><h2>High influence / High interest</h2><div class="members"></div></section><section class="quadrant" id="qHighLow"><h2>High influence / Low interest</h2><div class="members"></div></section><section class="quadrant" id="qLowHigh"><h2>Low influence / High interest</h2><div class="members"></div></section><section class="quadrant" id="qLowLow"><h2>Low influence / Low interest</h2><div class="members"></div></section></section>
<section class="unassessed" id="unassessed"><h2>Unassessed</h2><p>Either influence or interest is Unknown. Keep the group visible while you find the missing answer.</p><div class="members"></div></section>
<section class="assessment" aria-labelledby="assessmentHeading"><h2 id="assessmentHeading">Try a different assessment</h2><h3 id="selectedName">Project sponsor</h3><p id="selectedBasis"></p><p id="selectedQuestion"></p><fieldset><legend>Influence</legend><div class="choices"><label for="influenceHigh"><input id="influenceHigh" name="influence" type="radio" value="High">High</label><label for="influenceLow"><input id="influenceLow" name="influence" type="radio" value="Low">Low</label><label for="influenceUnknown"><input id="influenceUnknown" name="influence" type="radio" value="Unknown">Unknown</label></div></fieldset><fieldset><legend>Interest</legend><div class="choices"><label for="interestHigh"><input id="interestHigh" name="interest" type="radio" value="High">High</label><label for="interestLow"><input id="interestLow" name="interest" type="radio" value="Low">Low</label><label for="interestUnknown"><input id="interestUnknown" name="interest" type="radio" value="Unknown">Unknown</label></div></fieldset><p class="status" id="notice" role="status" aria-live="polite"></p><button id="restore" class="restore" type="button">Restore example</button></section>
<section aria-labelledby="ledgerHeading"><h2 id="ledgerHeading">Why each group is here</h2><p>Low influence does not make a group's needs less important. These questions prepare a conversation; they do not record that it happened.</p><div id="ledger" class="ledger"></div></section>
<footer>Changes stay in this open example. A new label does not supply new evidence, agreement, or a preferred communication channel.</footer>
</main><script>
(()=>{'use strict';const $=s=>document.querySelector(s),fixture=[{id:'S1',name:'Facilities team',influence:'High',interest:'High',basis:'Controls room access and needs a workable staffing plan.',question:'Review opening and closing responsibilities before proposing hours.'},{id:'S2',name:'Project sponsor',influence:'High',interest:'Low',basis:'Can approve the limited project spend; the booking change has a limited effect on the sponsor\'s day-to-day work.',question:'Confirm the budget boundary and which exceptions require a decision.'},{id:'S3',name:'Regular room users',influence:'Low',interest:'High',basis:'Rely on the room but have no assigned budget authority in this example.',question:'Ask how the proposed booking change affects their usual sessions.'},{id:'S4',name:'Neighboring program team',influence:'Low',interest:'Low',basis:'Uses the room only occasionally and has no assigned authority over this change.',question:'Check for planned sessions before treating the impact as small.'},{id:'S5',name:'Volunteer hosts',influence:'Unknown',interest:'High',basis:'Run sessions; their authority to change booking steps is unconfirmed.',question:'Ask who can agree to a change in the host handoff.'}];let groups=[],selected='S2';
function destination(row){return row.influence==='Unknown'||row.interest==='Unknown'?'unassessed':'q'+row.influence+row.interest;}
function changed(row){const original=fixture.find(x=>x.id===row.id);return original.influence!==row.influence||original.interest!==row.interest;}
function makeText(tag,text,className){const el=document.createElement(tag);el.textContent=text;if(className)el.className=className;return el;}
function render(){const focusedGroup=document.activeElement?.classList.contains('group')?document.activeElement.id:null;for(const el of document.querySelectorAll('.members'))el.replaceChildren();$('#ledger').replaceChildren();for(const row of groups){const button=document.createElement('button');button.type='button';button.id='group'+row.id;button.className='group';button.setAttribute('aria-pressed',String(row.id===selected));button.append(makeText('small',row.id),makeText('span',row.name));button.addEventListener('click',()=>select(row.id));$('#'+destination(row)+' .members').append(button);const record=document.createElement('article');record.className='record';record.id='record'+row.id;record.append(makeText('h3',row.id+' · '+row.name),makeText('p','Influence: '+row.influence+' / Interest: '+row.interest,'assessmentText'),makeText('p','Supplied example basis: '+row.basis),makeText('p','Next conversation: '+row.question));if(changed(row))record.append(makeText('p','Draft assessment changed. Review the original basis before using it.','draftFlag'));$('#ledger').append(record);}for(const el of document.querySelectorAll('.members'))if(!el.children.length)el.append(makeText('p','No groups in this area.','empty'));const missing=groups.filter(row=>destination(row)==='unassessed').length;$('#mapSummary').textContent=(groups.length-missing)+' groups mapped; '+missing+' '+(missing===1?'assessment':'assessments')+' incomplete.';if(focusedGroup)document.getElementById(focusedGroup)?.focus({preventScroll:true});}
function select(id){selected=id;const row=groups.find(x=>x.id===selected);$('#selectedName').textContent=row.id+' · '+row.name;$('#selectedBasis').textContent='Supplied example basis: '+row.basis;$('#selectedQuestion').textContent='Next conversation: '+row.question;for(const dim of ['influence','interest'])for(const radio of document.querySelectorAll('input[name='+dim+']'))radio.checked=radio.value===row[dim];$('#notice').textContent='';render();}
for(const dim of ['influence','interest'])for(const radio of document.querySelectorAll('input[name='+dim+']'))radio.addEventListener('change',()=>{if(!radio.checked)return;const row=groups.find(x=>x.id===selected);row[dim]=radio.value;render();$('#notice').textContent=destination(row)==='unassessed'?row.name+' is Unassessed. Unknown is not Low.':row.name+' moved to '+row.influence+' influence / '+row.interest+' interest. Confirm the basis before using this draft change.';});
function restore(){groups=fixture.map(row=>({...row}));select('S2');$('#notice').textContent='Original five-group example restored.';}$('#restore').addEventListener('click',restore);restore();})();
</script></body></html>