Skip to content

Instantly share code, notes, and snippets.

@wjhrdy
Last active June 2, 2026 13:40
Show Gist options
  • Select an option

  • Save wjhrdy/ec9acbc0528389d983e8e200f7f6bd91 to your computer and use it in GitHub Desktop.

Select an option

Save wjhrdy/ec9acbc0528389d983e8e200f7f6bd91 to your computer and use it in GitHub Desktop.
Jira stand-up random-order roller bookmarklet
javascript:!function()%7B%22use%20strict%22%3Bvar%20e%3D%22__standup_roller__%22%2Ct%3D%5B%22unassigned%22%2C%22assigned%20to%20me%22%2C%22only%20my%20issues%22%2C%22recently%20updated%22%2C%22deployed%20to%20production%22%5D%3Bfunction%20n()%7Bvar%20e%3Ddocument.querySelector('fieldset%5Bdata-testid%3D%22software-filters.ui.filter-selection-bar.filter-selection-bar%22%5D')%3Bif(e)return%20e%3Bvar%20t%3Ddocument.querySelector('input%5Bid%5E%3D%22checkbox-id-%22%5D')%3Breturn%20t%3Ft.closest(%22fieldset%22)%3Anull%7Dfunction%20o()%7Bvar%20e%3Dn()%3Bif(!e)return%5B%5D%3Bvar%20o%3D%5B%5D%2Cr%3D%7B%7D%3Breturn%20e.querySelectorAll('input%5Btype%3D%22checkbox%22%5D').forEach(function(n)%7Bif(n.id%26%26!r%5Bn.id%5D)%7Bvar%20i%3Dfunction(e%2Ct)%7Bvar%20n%3D'label%5Bfor%3D%22'%2B(window.CSS%26%26CSS.escape%3FCSS.escape(t)%3At)%2B'%22%5D'%2Co%3De.querySelector(n)%3Breturn%20o%3Fo.textContent.trim()%3A%22%22%7D(e%2Cn.id)%3Bi%26%26-1%3D%3D%3Dt.indexOf(i.toLowerCase())%26%26(r%5Bn.id%5D%3D1%2Co.push(%7Bid%3An.id%2Cname%3Ai%7D))%7D%7D)%2Co%7Dfunction%20r(e%2Ct)%7Be.forEach(function(e)%7Bvar%20o%2Cr%3Bo%3Dfunction(e)%7Bvar%20t%3Ddocument.getElementById(e.id)%3Bif(t%26%26%22checkbox%22%3D%3D%3Dt.type)return%20t%3Bvar%20o%3Dn()%3Bif(!o)return%20null%3Bfor(var%20r%3Do.querySelectorAll(%22label%22)%2Ci%3D0%3Bi%3Cr.length%3Bi%2B%2B)if(r%5Bi%5D.textContent.trim()%3D%3D%3De.name)%7Bvar%20l%3Dr%5Bi%5D.getAttribute(%22for%22)%2Ca%3Dl%26%26document.getElementById(l)%3Bif(a)return%20a%7Dreturn%20null%7D(e)%2Cr%3De%3D%3D%3Dt%2Co%26%26o.checked!%3D%3Dr%26%26o.click()%7D)%7Dvar%20i%3D%5B%5D%2Cl%3D%5B%5D%2Ca%3D-1%2Cc%3Ddocument.getElementById(e)%3Bc%26%26c.remove()%3Bvar%20d%3Ddocument.createElement(%22div%22)%3Bd.id%3De%2Cd.style.cssText%3D%5B%22position%3Afixed%22%2C%22left%3A20px%22%2C%22bottom%3A20px%22%2C%22z-index%3A2147483647%22%2C'font-family%3A-apple-system%2CBlinkMacSystemFont%2C%22Segoe%20UI%22%2CRoboto%2Csans-serif'%2C%22min-width%3A220px%22%2C%22max-width%3A320px%22%2C%22background%3A%231d2125%22%2C%22color%3A%23fff%22%2C%22border%3A1px%20solid%20rgba(255%2C255%2C255%2C.12)%22%2C%22border-radius%3A14px%22%2C%22box-shadow%3A0%2012px%2032px%20rgba(0%2C0%2C0%2C.45)%22%2C%22overflow%3Ahidden%22%2C%22user-select%3Anone%22%5D.join(%22%3B%22)%3Bvar%20s%3Ddocument.createElement(%22div%22)%3Bs.style.cssText%3D%22display%3Aflex%3Balign-items%3Acenter%3Bjustify-content%3Aspace-between%3Bpadding%3A8px%2012px%3Bbackground%3Argba(255%2C255%2C255%2C.06)%3Bfont-size%3A12px%3Bfont-weight%3A600%3Bletter-spacing%3A.04em%3Btext-transform%3Auppercase%3Bcolor%3A%239fadbc%22%3Bvar%20u%3Ddocument.createElement(%22span%22)%3Bu.textContent%3D%22Stand-up%20%F0%9F%8E%B2%22%3Bvar%20p%3Ddocument.createElement(%22div%22)%3Bp.style.cssText%3D%22display%3Aflex%3Bgap%3A6px%22%3Bvar%20f%3Ddocument.createElement(%22button%22)%3Bf.textContent%3D%22%E2%86%BA%22%2Cf.title%3D%22Reset%22%3Bvar%20x%3Ddocument.createElement(%22button%22)%3Bx.textContent%3D%22%E2%9C%95%22%2Cx.title%3D%22Close%22%2C%5Bf%2Cx%5D.forEach(function(e)%7Be.style.cssText%3D%22all%3Aunset%3Bcursor%3Apointer%3Bcolor%3A%239fadbc%3Bfont-size%3A13px%3Bline-height%3A1%3Bpadding%3A2px%205px%3Bborder-radius%3A6px%22%2Ce.onmouseenter%3Dfunction()%7Be.style.color%3D%22%23fff%22%2Ce.style.background%3D%22rgba(255%2C255%2C255%2C.12)%22%7D%2Ce.onmouseleave%3Dfunction()%7Be.style.color%3D%22%239fadbc%22%2Ce.style.background%3D%22transparent%22%7D%7D)%2Cp.appendChild(f)%2Cp.appendChild(x)%2Cs.appendChild(u)%2Cs.appendChild(p)%3Bvar%20m%3Ddocument.createElement(%22button%22)%3Bm.style.cssText%3D%22all%3Aunset%3Bdisplay%3Ablock%3Bcursor%3Apointer%3Bwidth%3A100%25%3Bbox-sizing%3Aborder-box%3Bpadding%3A16px%2016px%2018px%22%3Bvar%20g%3Ddocument.createElement(%22div%22)%3Bg.style.cssText%3D%22font-size%3A24px%3Bfont-weight%3A700%3Bline-height%3A1.15%3Bword-break%3Abreak-word%22%3Bvar%20b%3Ddocument.createElement(%22div%22)%3Bfunction%20h()%7Bvar%20e%3Dl.length%3Bif(-1%3D%3D%3Da)g.textContent%3D%22%F0%9F%8E%B2%20Start%20stand-up%22%2Cg.style.color%3D%22%23fff%22%2Cb.textContent%3Di.length%2B(1%3D%3D%3Di.length%3F%22%20person%22%3A%22%20people%22)%2B%22%20%C2%B7%20click%20to%20begin%22%3Belse%20if(a%3E%3De)g.textContent%3D%22%E2%9C%85%20All%20done!%22%2Cg.style.color%3D%22%234bce97%22%2Cb.textContent%3D%22click%20to%20run%20again%22%3Belse%7Bvar%20t%3Dl%5Ba%5D%3Bg.textContent%3Dt.name%2Cg.style.color%3D%22%23669df1%22%3Bvar%20n%3Da%3D%3D%3De-1%3Bb.textContent%3Da%2B1%2B%22%20%2F%20%22%2Be%2B%22%20%C2%B7%20%22%2B(n%3F%22last%20%E2%80%94%20click%20to%20finish%22%3A%22click%20for%20next%22)%7D%7Db.style.cssText%3D%22margin-top%3A6px%3Bfont-size%3A12px%3Bcolor%3A%239fadbc%22%2Cm.appendChild(g)%2Cm.appendChild(b)%2Cm.onmouseenter%3Dfunction()%7Bm.style.background%3D%22rgba(255%2C255%2C255%2C.05)%22%7D%2Cm.onmouseleave%3Dfunction()%7Bm.style.background%3D%22transparent%22%7D%2Cd.appendChild(s)%2Cd.appendChild(m)%2Cdocument.body.appendChild(d)%2Cm.addEventListener(%22click%22%2Cfunction()%7Bif(-1%3D%3D%3Da%7C%7Ca%3E%3Dl.length)%7Bif(!(i%3Do()).length)return%20g.textContent%3D%22No%20people%20found%22%2Cg.style.color%3D%22%23f87168%22%2Cvoid(b.textContent%3D%22open%20the%20assignee%20filter%20bar%20first%22)%3Bl%3Dfunction(e)%7Bfor(var%20t%3De.length-1%3Bt%3E0%3Bt--)%7Bvar%20n%3DMath.floor(Math.random()*(t%2B1))%2Co%3De%5Bt%5D%3Be%5Bt%5D%3De%5Bn%5D%2Ce%5Bn%5D%3Do%7Dreturn%20e%7D(i.slice())%2Ca%3D0%2Cr(i%2Cl%5B0%5D)%7Delse%20a%3Cl.length-1%3F(a%2B%2B%2Cr(i%2Cl%5Ba%5D))%3A(a%3Dl.length%2Cr(i%2Cnull))%3Bh()%7D)%2Cf.addEventListener(%22click%22%2Cfunction(e)%7Be.stopPropagation()%2Ci%3Do()%2Cl%3D%5B%5D%2Ca%3D-1%2Cr(i%2Cnull)%2Ch()%7D)%2Cx.addEventListener(%22click%22%2Cfunction(e)%7Be.stopPropagation()%2Cd.remove()%7D)%2Ci%3Do()%2Ch()%7D()%3B
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Stand-up Roller — install</title>
<style>
:root{color-scheme:dark}
body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;
background:#0b0f14;color:#c7d1db;
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;line-height:1.55}
.card{max-width:560px;margin:32px;padding:32px;background:#1d2125;border:1px solid rgba(255,255,255,.12);
border-radius:16px;box-shadow:0 12px 32px rgba(0,0,0,.45)}
h1{margin:0 0 4px;font-size:22px;color:#fff}
p{margin:12px 0}
.drag{display:inline-block;margin:18px 0;padding:14px 22px;border-radius:12px;
background:#669df1;color:#0b0f14;font-weight:700;font-size:16px;text-decoration:none;
cursor:grab;box-shadow:0 6px 18px rgba(102,157,241,.4)}
.drag:active{cursor:grabbing}
ol{padding-left:20px}
code{background:rgba(255,255,255,.08);padding:2px 6px;border-radius:6px;font-size:13px}
.muted{color:#9fadbc;font-size:13px}
</style>
</head>
<body>
<div class="card">
<h1>Stand-up Roller 🎲</h1>
<p>Picks a random stand-up order from a Jira board's assignee filter bar and toggles each person as you go.</p>
<p><strong>Drag this button up to your bookmarks bar:</strong></p>
<p><a class="drag" href="javascript:!function()%7B%22use%20strict%22%3Bvar%20e%3D%22__standup_roller__%22%2Ct%3D%5B%22unassigned%22%2C%22assigned%20to%20me%22%2C%22only%20my%20issues%22%2C%22recently%20updated%22%2C%22deployed%20to%20production%22%5D%3Bfunction%20n()%7Bvar%20e%3Ddocument.querySelector('fieldset%5Bdata-testid%3D%22software-filters.ui.filter-selection-bar.filter-selection-bar%22%5D')%3Bif(e)return%20e%3Bvar%20t%3Ddocument.querySelector('input%5Bid%5E%3D%22checkbox-id-%22%5D')%3Breturn%20t%3Ft.closest(%22fieldset%22)%3Anull%7Dfunction%20o()%7Bvar%20e%3Dn()%3Bif(!e)return%5B%5D%3Bvar%20o%3D%5B%5D%2Cr%3D%7B%7D%3Breturn%20e.querySelectorAll('input%5Btype%3D%22checkbox%22%5D').forEach(function(n)%7Bif(n.id%26%26!r%5Bn.id%5D)%7Bvar%20i%3Dfunction(e%2Ct)%7Bvar%20n%3D'label%5Bfor%3D%22'%2B(window.CSS%26%26CSS.escape%3FCSS.escape(t)%3At)%2B'%22%5D'%2Co%3De.querySelector(n)%3Breturn%20o%3Fo.textContent.trim()%3A%22%22%7D(e%2Cn.id)%3Bi%26%26-1%3D%3D%3Dt.indexOf(i.toLowerCase())%26%26(r%5Bn.id%5D%3D1%2Co.push(%7Bid%3An.id%2Cname%3Ai%7D))%7D%7D)%2Co%7Dfunction%20r(e%2Ct)%7Be.forEach(function(e)%7Bvar%20o%2Cr%3Bo%3Dfunction(e)%7Bvar%20t%3Ddocument.getElementById(e.id)%3Bif(t%26%26%22checkbox%22%3D%3D%3Dt.type)return%20t%3Bvar%20o%3Dn()%3Bif(!o)return%20null%3Bfor(var%20r%3Do.querySelectorAll(%22label%22)%2Ci%3D0%3Bi%3Cr.length%3Bi%2B%2B)if(r%5Bi%5D.textContent.trim()%3D%3D%3De.name)%7Bvar%20l%3Dr%5Bi%5D.getAttribute(%22for%22)%2Ca%3Dl%26%26document.getElementById(l)%3Bif(a)return%20a%7Dreturn%20null%7D(e)%2Cr%3De%3D%3D%3Dt%2Co%26%26o.checked!%3D%3Dr%26%26o.click()%7D)%7Dvar%20i%3D%5B%5D%2Cl%3D%5B%5D%2Ca%3D-1%2Cc%3Ddocument.getElementById(e)%3Bc%26%26c.remove()%3Bvar%20d%3Ddocument.createElement(%22div%22)%3Bd.id%3De%2Cd.style.cssText%3D%5B%22position%3Afixed%22%2C%22left%3A20px%22%2C%22bottom%3A20px%22%2C%22z-index%3A2147483647%22%2C'font-family%3A-apple-system%2CBlinkMacSystemFont%2C%22Segoe%20UI%22%2CRoboto%2Csans-serif'%2C%22min-width%3A220px%22%2C%22max-width%3A320px%22%2C%22background%3A%231d2125%22%2C%22color%3A%23fff%22%2C%22border%3A1px%20solid%20rgba(255%2C255%2C255%2C.12)%22%2C%22border-radius%3A14px%22%2C%22box-shadow%3A0%2012px%2032px%20rgba(0%2C0%2C0%2C.45)%22%2C%22overflow%3Ahidden%22%2C%22user-select%3Anone%22%5D.join(%22%3B%22)%3Bvar%20s%3Ddocument.createElement(%22div%22)%3Bs.style.cssText%3D%22display%3Aflex%3Balign-items%3Acenter%3Bjustify-content%3Aspace-between%3Bpadding%3A8px%2012px%3Bbackground%3Argba(255%2C255%2C255%2C.06)%3Bfont-size%3A12px%3Bfont-weight%3A600%3Bletter-spacing%3A.04em%3Btext-transform%3Auppercase%3Bcolor%3A%239fadbc%22%3Bvar%20u%3Ddocument.createElement(%22span%22)%3Bu.textContent%3D%22Stand-up%20%F0%9F%8E%B2%22%3Bvar%20p%3Ddocument.createElement(%22div%22)%3Bp.style.cssText%3D%22display%3Aflex%3Bgap%3A6px%22%3Bvar%20f%3Ddocument.createElement(%22button%22)%3Bf.textContent%3D%22%E2%86%BA%22%2Cf.title%3D%22Reset%22%3Bvar%20x%3Ddocument.createElement(%22button%22)%3Bx.textContent%3D%22%E2%9C%95%22%2Cx.title%3D%22Close%22%2C%5Bf%2Cx%5D.forEach(function(e)%7Be.style.cssText%3D%22all%3Aunset%3Bcursor%3Apointer%3Bcolor%3A%239fadbc%3Bfont-size%3A13px%3Bline-height%3A1%3Bpadding%3A2px%205px%3Bborder-radius%3A6px%22%2Ce.onmouseenter%3Dfunction()%7Be.style.color%3D%22%23fff%22%2Ce.style.background%3D%22rgba(255%2C255%2C255%2C.12)%22%7D%2Ce.onmouseleave%3Dfunction()%7Be.style.color%3D%22%239fadbc%22%2Ce.style.background%3D%22transparent%22%7D%7D)%2Cp.appendChild(f)%2Cp.appendChild(x)%2Cs.appendChild(u)%2Cs.appendChild(p)%3Bvar%20m%3Ddocument.createElement(%22button%22)%3Bm.style.cssText%3D%22all%3Aunset%3Bdisplay%3Ablock%3Bcursor%3Apointer%3Bwidth%3A100%25%3Bbox-sizing%3Aborder-box%3Bpadding%3A16px%2016px%2018px%22%3Bvar%20g%3Ddocument.createElement(%22div%22)%3Bg.style.cssText%3D%22font-size%3A24px%3Bfont-weight%3A700%3Bline-height%3A1.15%3Bword-break%3Abreak-word%22%3Bvar%20b%3Ddocument.createElement(%22div%22)%3Bfunction%20h()%7Bvar%20e%3Dl.length%3Bif(-1%3D%3D%3Da)g.textContent%3D%22%F0%9F%8E%B2%20Start%20stand-up%22%2Cg.style.color%3D%22%23fff%22%2Cb.textContent%3Di.length%2B(1%3D%3D%3Di.length%3F%22%20person%22%3A%22%20people%22)%2B%22%20%C2%B7%20click%20to%20begin%22%3Belse%20if(a%3E%3De)g.textContent%3D%22%E2%9C%85%20All%20done!%22%2Cg.style.color%3D%22%234bce97%22%2Cb.textContent%3D%22click%20to%20run%20again%22%3Belse%7Bvar%20t%3Dl%5Ba%5D%3Bg.textContent%3Dt.name%2Cg.style.color%3D%22%23669df1%22%3Bvar%20n%3Da%3D%3D%3De-1%3Bb.textContent%3Da%2B1%2B%22%20%2F%20%22%2Be%2B%22%20%C2%B7%20%22%2B(n%3F%22last%20%E2%80%94%20click%20to%20finish%22%3A%22click%20for%20next%22)%7D%7Db.style.cssText%3D%22margin-top%3A6px%3Bfont-size%3A12px%3Bcolor%3A%239fadbc%22%2Cm.appendChild(g)%2Cm.appendChild(b)%2Cm.onmouseenter%3Dfunction()%7Bm.style.background%3D%22rgba(255%2C255%2C255%2C.05)%22%7D%2Cm.onmouseleave%3Dfunction()%7Bm.style.background%3D%22transparent%22%7D%2Cd.appendChild(s)%2Cd.appendChild(m)%2Cdocument.body.appendChild(d)%2Cm.addEventListener(%22click%22%2Cfunction()%7Bif(-1%3D%3D%3Da%7C%7Ca%3E%3Dl.length)%7Bif(!(i%3Do()).length)return%20g.textContent%3D%22No%20people%20found%22%2Cg.style.color%3D%22%23f87168%22%2Cvoid(b.textContent%3D%22open%20the%20assignee%20filter%20bar%20first%22)%3Bl%3Dfunction(e)%7Bfor(var%20t%3De.length-1%3Bt%3E0%3Bt--)%7Bvar%20n%3DMath.floor(Math.random()*(t%2B1))%2Co%3De%5Bt%5D%3Be%5Bt%5D%3De%5Bn%5D%2Ce%5Bn%5D%3Do%7Dreturn%20e%7D(i.slice())%2Ca%3D0%2Cr(i%2Cl%5B0%5D)%7Delse%20a%3Cl.length-1%3F(a%2B%2B%2Cr(i%2Cl%5Ba%5D))%3A(a%3Dl.length%2Cr(i%2Cnull))%3Bh()%7D)%2Cf.addEventListener(%22click%22%2Cfunction(e)%7Be.stopPropagation()%2Ci%3Do()%2Cl%3D%5B%5D%2Ca%3D-1%2Cr(i%2Cnull)%2Ch()%7D)%2Cx.addEventListener(%22click%22%2Cfunction(e)%7Be.stopPropagation()%2Cd.remove()%7D)%2Ci%3Do()%2Ch()%7D()%3B">🎲 Stand-up Roller</a></p>
<ol>
<li>Make sure your bookmarks bar is visible (<code>⌘⇧B</code> / <code>Ctrl⇧B</code>).</li>
<li>Drag the blue button onto it. (Browsers don't allow one-click install — dragging is the only way.)</li>
<li>Open your Jira board, expand the assignee filter bar, then click the bookmark.</li>
</ol>
<p class="muted">Can't drag? Right-click the button → <em>Copy link</em>, then create a new bookmark and paste it into the URL/address field.</p>
</div>
</body>
</html>
/*
* Stand-up random-order roller — Jira assignee filter bar.
*
* Discovers the people in the Jira "filter selection bar" (the assignee
* avatar/name checkboxes), shuffles them into a random order, and gives you a
* floating button in the bottom-left. Each click rolls to the next person
* (without replacement): it unchecks the previous person and checks the next,
* so the board filters down to whoever is currently up. After the last person
* it shows a "done" state; click again to reshuffle and run another round.
*
* This is the readable source. The drag-to-bookmarks one-liner is in
* bookmarklet.txt (regenerate with: node build.js).
*/
(function () {
'use strict';
var WIDGET_ID = '__standup_roller__';
var FS_SELECTOR =
'fieldset[data-testid="software-filters.ui.filter-selection-bar.filter-selection-bar"]';
// Non-person quick-filters that live in the same bar. Compared case-insensitively
// against the trimmed label text, so they never end up in the roster.
var EXCLUDE = [
'unassigned',
'assigned to me',
'only my issues',
'recently updated',
'deployed to production'
];
function getFieldset() {
var fs = document.querySelector(FS_SELECTOR);
if (fs) return fs;
// Fallback: any fieldset that holds Jira filter checkboxes.
var cb = document.querySelector('input[id^="checkbox-id-"]');
return cb ? cb.closest('fieldset') : null;
}
function labelFor(fs, id) {
var sel = 'label[for="' + (window.CSS && CSS.escape ? CSS.escape(id) : id) + '"]';
var lb = fs.querySelector(sel);
return lb ? lb.textContent.trim() : '';
}
// Build the roster of people from the live DOM.
function discover() {
var fs = getFieldset();
if (!fs) return [];
var out = [];
var seen = {};
fs.querySelectorAll('input[type="checkbox"]').forEach(function (inp) {
if (!inp.id || seen[inp.id]) return;
var name = labelFor(fs, inp.id);
if (!name) return;
if (EXCLUDE.indexOf(name.toLowerCase()) !== -1) return;
seen[inp.id] = 1;
out.push({ id: inp.id, name: name });
});
return out;
}
// Re-find a person's live checkbox each time — Jira re-renders nodes, so a
// stored reference can go stale. Match by id first, then fall back to label text.
function resolve(entry) {
var el = document.getElementById(entry.id);
if (el && el.type === 'checkbox') return el;
var fs = getFieldset();
if (!fs) return null;
var labels = fs.querySelectorAll('label');
for (var i = 0; i < labels.length; i++) {
if (labels[i].textContent.trim() === entry.name) {
var fid = labels[i].getAttribute('for');
var inp = fid && document.getElementById(fid);
if (inp) return inp;
}
}
return null;
}
// Real click → toggles the box AND fires the event React listens for. Only
// click when the current state differs from what we want, so it's idempotent.
function setChecked(input, desired) {
if (input && input.checked !== desired) input.click();
}
// Ensure exactly `current` is checked among the roster (null = uncheck all).
function selectOnly(roster, current) {
roster.forEach(function (e) {
setChecked(resolve(e), e === current);
});
}
function shuffle(a) {
for (var i = a.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var t = a[i];
a[i] = a[j];
a[j] = t;
}
return a;
}
// ---- state ----
var roster = [];
var order = [];
var idx = -1; // -1 = idle, 0..N-1 = running, >=N = done
// ---- UI ----
var old = document.getElementById(WIDGET_ID);
if (old) old.remove();
var box = document.createElement('div');
box.id = WIDGET_ID;
box.style.cssText = [
'position:fixed', 'left:20px', 'bottom:20px', 'z-index:2147483647',
'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif',
'min-width:220px', 'max-width:320px', 'background:#1d2125', 'color:#fff',
'border:1px solid rgba(255,255,255,.12)', 'border-radius:14px',
'box-shadow:0 12px 32px rgba(0,0,0,.45)', 'overflow:hidden', 'user-select:none'
].join(';');
var header = document.createElement('div');
header.style.cssText =
'display:flex;align-items:center;justify-content:space-between;padding:8px 12px;' +
'background:rgba(255,255,255,.06);font-size:12px;font-weight:600;letter-spacing:.04em;' +
'text-transform:uppercase;color:#9fadbc';
var htitle = document.createElement('span');
htitle.textContent = 'Stand-up \uD83C\uDFB2';
var hbtns = document.createElement('div');
hbtns.style.cssText = 'display:flex;gap:6px';
var resetBtn = document.createElement('button');
resetBtn.textContent = '\u21BA';
resetBtn.title = 'Reset';
var closeBtn = document.createElement('button');
closeBtn.textContent = '\u2715';
closeBtn.title = 'Close';
[resetBtn, closeBtn].forEach(function (b) {
b.style.cssText =
'all:unset;cursor:pointer;color:#9fadbc;font-size:13px;line-height:1;padding:2px 5px;border-radius:6px';
b.onmouseenter = function () { b.style.color = '#fff'; b.style.background = 'rgba(255,255,255,.12)'; };
b.onmouseleave = function () { b.style.color = '#9fadbc'; b.style.background = 'transparent'; };
});
hbtns.appendChild(resetBtn);
hbtns.appendChild(closeBtn);
header.appendChild(htitle);
header.appendChild(hbtns);
var bodyBtn = document.createElement('button');
bodyBtn.style.cssText =
'all:unset;display:block;cursor:pointer;width:100%;box-sizing:border-box;padding:16px 16px 18px';
var nameEl = document.createElement('div');
nameEl.style.cssText = 'font-size:24px;font-weight:700;line-height:1.15;word-break:break-word';
var subEl = document.createElement('div');
subEl.style.cssText = 'margin-top:6px;font-size:12px;color:#9fadbc';
bodyBtn.appendChild(nameEl);
bodyBtn.appendChild(subEl);
bodyBtn.onmouseenter = function () { bodyBtn.style.background = 'rgba(255,255,255,.05)'; };
bodyBtn.onmouseleave = function () { bodyBtn.style.background = 'transparent'; };
box.appendChild(header);
box.appendChild(bodyBtn);
document.body.appendChild(box);
function render() {
var n = order.length;
if (idx === -1) {
nameEl.textContent = '\uD83C\uDFB2 Start stand-up';
nameEl.style.color = '#fff';
subEl.textContent =
roster.length + (roster.length === 1 ? ' person' : ' people') + ' \u00B7 click to begin';
} else if (idx >= n) {
nameEl.textContent = '\u2705 All done!';
nameEl.style.color = '#4bce97';
subEl.textContent = 'click to run again';
} else {
var cur = order[idx];
nameEl.textContent = cur.name;
nameEl.style.color = '#669df1';
var last = idx === n - 1;
subEl.textContent =
(idx + 1) + ' / ' + n + ' \u00B7 ' + (last ? 'last \u2014 click to finish' : 'click for next');
}
}
function advance() {
if (idx === -1 || idx >= order.length) {
// (re)start a round
roster = discover();
if (!roster.length) {
nameEl.textContent = 'No people found';
nameEl.style.color = '#f87168';
subEl.textContent = 'open the assignee filter bar first';
return;
}
order = shuffle(roster.slice());
idx = 0;
selectOnly(roster, order[0]);
} else if (idx < order.length - 1) {
idx++;
selectOnly(roster, order[idx]);
} else {
idx = order.length; // last -> done
selectOnly(roster, null);
}
render();
}
bodyBtn.addEventListener('click', advance);
resetBtn.addEventListener('click', function (ev) {
ev.stopPropagation();
roster = discover();
order = [];
idx = -1;
selectOnly(roster, null);
render();
});
closeBtn.addEventListener('click', function (ev) {
ev.stopPropagation();
box.remove();
});
roster = discover();
render();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment