diff --git a/src/core-skills/bmad-brainstorming/SKILL.md b/src/core-skills/bmad-brainstorming/SKILL.md
index 1d8bf780f..9552d1ee5 100644
--- a/src/core-skills/bmad-brainstorming/SKILL.md
+++ b/src/core-skills/bmad-brainstorming/SKILL.md
@@ -67,7 +67,7 @@ Two things get set before ideating: the **facilitation mode** (your stance) and
- Default catalog → open `{skill-root}/assets/brain-selector.html`.
- Customized catalog (overridden `{workflow.brain_methods}` or any `{workflow.additional_techniques}`) → regenerate first, then open it. If there are `{workflow.additional_techniques}`, write them to a JSON file (a list of `{category, technique_name, description}` objects) and pass it as `--extra` so the page includes them too: `python3 {skill-root}/scripts/brain.py --file {workflow.brain_methods} [--extra {doc_workspace}/extra-techniques.json] html --out {doc_workspace}/brain-selector.html`.
-There they choose a facilitation mode, build a technique batch (tick cards, **+Random**, **+Invent**, **AI picks**), filter by category if they want, click **Copy prompt**, and paste it back. Read that pasted block:
+There they choose a facilitation mode, build a technique batch (tick cards, **+Random**, **+Invent**, **AI picks**), jump to a category via the chips if they want, click **Copy prompt**, and paste it back. Read that pasted block:
- the **`Facilitation mode:`** line → the mode;
- the **listed techniques** — full category, name, and description are included, some tagged `(random pick)` → that is the batch; run them as given, no `list`/`show` needed;
diff --git a/src/core-skills/bmad-brainstorming/assets/brain-selector.html b/src/core-skills/bmad-brainstorming/assets/brain-selector.html
index 2ae6b5221..e7d508a76 100644
--- a/src/core-skills/bmad-brainstorming/assets/brain-selector.html
+++ b/src/core-skills/bmad-brainstorming/assets/brain-selector.html
@@ -4,58 +4,88 @@
BMad Method Brainstorming Selection
+
-
BMad Method Brainstorming Selection
+
+
+
BMad Method Brainstorming Selection
+
+
Compose your session, hit Copy prompt, and paste it back into the chat to begin. 100 techniques across 13 categories.
@@ -66,6 +96,7 @@
+
Techniques
@@ -74,16 +105,17 @@
Invent 0AI picks 0Total 0 · 3–4 is the sweet spot
+
-
-
+ Jump to
+
-
✓ Copied! Now paste it into the chat to start your session.
+
Absurdist6
@@ -106,15 +138,31 @@
var $ = function(id){ return document.getElementById(id); };
var all = Array.prototype.slice;
var boxes = all.call(document.querySelectorAll('input[type=checkbox]'));
- var q = $('q');
+ var header = document.querySelector('header');
+ var sections = all.call(document.querySelectorAll('section'));
var state = { mode: 'Facilitator', rand: 0, inv: 0, ai: 0 };
- var offCats = {};
+ var MODE_HINTS = {
+ 'Facilitator': 'A forcing function for your ideas — I prompt and push, but never supply them.',
+ 'Creative Partner': 'We riff together — I facilitate and add ideas too, each logged as yours or mine.',
+ 'Ideate for me': 'I run the whole session myself, then show you the result and offer to keep going.'
+ };
+ function setHint(){ $('modehint').textContent = MODE_HINTS[state.mode] || ''; }
+
+ var themeBtn = $('theme');
+ function setThemeIcon(){ themeBtn.textContent = document.documentElement.getAttribute('data-theme') === 'dark' ? '☀' : '☾'; }
+ themeBtn.addEventListener('click', function(){
+ var next = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
+ document.documentElement.setAttribute('data-theme', next);
+ try { localStorage.setItem('bmad-theme', next); } catch(e){}
+ setThemeIcon();
+ });
all.call(document.querySelectorAll('.mode')).forEach(function(b){
b.addEventListener('click', function(){
all.call(document.querySelectorAll('.mode')).forEach(function(m){ m.classList.remove('on'); });
b.classList.add('on');
state.mode = b.dataset.mode;
+ setHint();
});
});
@@ -126,25 +174,21 @@
});
});
+ // Category chips are jump-nav: click one to smooth-scroll its section into view,
+ // offsetting by the sticky header's height so the heading isn't hidden beneath it.
all.call(document.querySelectorAll('.chip')).forEach(function(chip){
chip.addEventListener('click', function(){
- var on = !chip.classList.contains('on');
- chip.classList.toggle('on', on);
- if (on){ delete offCats[chip.dataset.cat]; } else { offCats[chip.dataset.cat] = true; }
- applyFilter();
- update(); // a toggled-off category leaves the session, so counts must refresh too
+ var sec = null;
+ for (var i = 0; i < sections.length; i++){ if (sections[i].dataset.cat === chip.dataset.cat){ sec = sections[i]; break; } }
+ if (!sec){ return; }
+ var top = sec.getBoundingClientRect().top + window.pageYOffset - header.offsetHeight - 8;
+ window.scrollTo({ top: top, behavior: 'smooth' });
});
});
boxes.forEach(function(b){ b.addEventListener('change', update); });
- q.addEventListener('input', applyFilter);
- // A category toggled off (offCats) leaves the session entirely; the text filter is a
- // transient browse aid that never changes what's selected. So both manual picks and the
- // random pool key off offCats — never the search box — keeping the copied prompt in step
- // with what the user sees, and never starving a random draw because of a stray filter term.
- function inScope(b){ return !offCats[b.dataset.cat]; }
- function checked(){ return boxes.filter(function(b){ return b.checked && inScope(b); }); }
+ function checked(){ return boxes.filter(function(b){ return b.checked; }); }
function update(){
$('pickN').textContent = checked().length;
@@ -157,20 +201,7 @@
t.classList.toggle('warn', total > 5);
}
- function applyFilter(){
- var s = q.value.trim().toLowerCase();
- all.call(document.querySelectorAll('label.tech')).forEach(function(l){
- var cat = l.querySelector('input').dataset.cat;
- var hay = (l.textContent + ' ' + cat).toLowerCase();
- l.style.display = (!offCats[cat] && (!s || hay.indexOf(s) > -1)) ? '' : 'none';
- });
- all.call(document.querySelectorAll('section')).forEach(function(sec){
- var any = all.call(sec.querySelectorAll('label.tech')).some(function(l){ return l.style.display !== 'none'; });
- sec.style.display = (!offCats[sec.dataset.cat] && any) ? '' : 'none';
- });
- }
-
- function randomPool(){ return boxes.filter(function(b){ return !b.checked && inScope(b); }); }
+ function randomPool(){ return boxes.filter(function(b){ return !b.checked; }); }
function sample(arr, n){
var a = arr.slice(), out = [];
@@ -234,6 +265,8 @@
} else { flash(fallbackCopy(text), text); }
});
+ setHint();
+ setThemeIcon();
update();
})();
diff --git a/src/core-skills/bmad-brainstorming/scripts/brain.py b/src/core-skills/bmad-brainstorming/scripts/brain.py
index 02d428c04..3f4bf13e5 100644
--- a/src/core-skills/bmad-brainstorming/scripts/brain.py
+++ b/src/core-skills/bmad-brainstorming/scripts/brain.py
@@ -433,58 +433,88 @@ SELECTOR_TEMPLATE = r"""
BMad Method Brainstorming Selection
+
-
BMad Method Brainstorming Selection
+
+
+
BMad Method Brainstorming Selection
+
+
Compose your session, hit Copy prompt, and paste it back into the chat to begin. {{TOTAL}}
@@ -495,6 +525,7 @@ SELECTOR_TEMPLATE = r"""
+
Techniques
@@ -503,16 +534,17 @@ SELECTOR_TEMPLATE = r"""
Invent 0AI picks 0Total 0 · 3–4 is the sweet spot
+
-
-
+ Jump to
+
{{CHIPS}}
-
{{CHIPS}}
✓ Copied! Now paste it into the chat to start your session.
+
{{BODY}}
@@ -523,15 +555,31 @@ SELECTOR_TEMPLATE = r"""
var $ = function(id){ return document.getElementById(id); };
var all = Array.prototype.slice;
var boxes = all.call(document.querySelectorAll('input[type=checkbox]'));
- var q = $('q');
+ var header = document.querySelector('header');
+ var sections = all.call(document.querySelectorAll('section'));
var state = { mode: 'Facilitator', rand: 0, inv: 0, ai: 0 };
- var offCats = {};
+ var MODE_HINTS = {
+ 'Facilitator': 'A forcing function for your ideas — I prompt and push, but never supply them.',
+ 'Creative Partner': 'We riff together — I facilitate and add ideas too, each logged as yours or mine.',
+ 'Ideate for me': 'I run the whole session myself, then show you the result and offer to keep going.'
+ };
+ function setHint(){ $('modehint').textContent = MODE_HINTS[state.mode] || ''; }
+
+ var themeBtn = $('theme');
+ function setThemeIcon(){ themeBtn.textContent = document.documentElement.getAttribute('data-theme') === 'dark' ? '☀' : '☾'; }
+ themeBtn.addEventListener('click', function(){
+ var next = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
+ document.documentElement.setAttribute('data-theme', next);
+ try { localStorage.setItem('bmad-theme', next); } catch(e){}
+ setThemeIcon();
+ });
all.call(document.querySelectorAll('.mode')).forEach(function(b){
b.addEventListener('click', function(){
all.call(document.querySelectorAll('.mode')).forEach(function(m){ m.classList.remove('on'); });
b.classList.add('on');
state.mode = b.dataset.mode;
+ setHint();
});
});
@@ -543,25 +591,21 @@ SELECTOR_TEMPLATE = r"""
});
});
+ // Category chips are jump-nav: click one to smooth-scroll its section into view,
+ // offsetting by the sticky header's height so the heading isn't hidden beneath it.
all.call(document.querySelectorAll('.chip')).forEach(function(chip){
chip.addEventListener('click', function(){
- var on = !chip.classList.contains('on');
- chip.classList.toggle('on', on);
- if (on){ delete offCats[chip.dataset.cat]; } else { offCats[chip.dataset.cat] = true; }
- applyFilter();
- update(); // a toggled-off category leaves the session, so counts must refresh too
+ var sec = null;
+ for (var i = 0; i < sections.length; i++){ if (sections[i].dataset.cat === chip.dataset.cat){ sec = sections[i]; break; } }
+ if (!sec){ return; }
+ var top = sec.getBoundingClientRect().top + window.pageYOffset - header.offsetHeight - 8;
+ window.scrollTo({ top: top, behavior: 'smooth' });
});
});
boxes.forEach(function(b){ b.addEventListener('change', update); });
- q.addEventListener('input', applyFilter);
- // A category toggled off (offCats) leaves the session entirely; the text filter is a
- // transient browse aid that never changes what's selected. So both manual picks and the
- // random pool key off offCats — never the search box — keeping the copied prompt in step
- // with what the user sees, and never starving a random draw because of a stray filter term.
- function inScope(b){ return !offCats[b.dataset.cat]; }
- function checked(){ return boxes.filter(function(b){ return b.checked && inScope(b); }); }
+ function checked(){ return boxes.filter(function(b){ return b.checked; }); }
function update(){
$('pickN').textContent = checked().length;
@@ -574,20 +618,7 @@ SELECTOR_TEMPLATE = r"""
t.classList.toggle('warn', total > 5);
}
- function applyFilter(){
- var s = q.value.trim().toLowerCase();
- all.call(document.querySelectorAll('label.tech')).forEach(function(l){
- var cat = l.querySelector('input').dataset.cat;
- var hay = (l.textContent + ' ' + cat).toLowerCase();
- l.style.display = (!offCats[cat] && (!s || hay.indexOf(s) > -1)) ? '' : 'none';
- });
- all.call(document.querySelectorAll('section')).forEach(function(sec){
- var any = all.call(sec.querySelectorAll('label.tech')).some(function(l){ return l.style.display !== 'none'; });
- sec.style.display = (!offCats[sec.dataset.cat] && any) ? '' : 'none';
- });
- }
-
- function randomPool(){ return boxes.filter(function(b){ return !b.checked && inScope(b); }); }
+ function randomPool(){ return boxes.filter(function(b){ return !b.checked; }); }
function sample(arr, n){
var a = arr.slice(), out = [];
@@ -651,6 +682,8 @@ SELECTOR_TEMPLATE = r"""
} else { flash(fallbackCopy(text), text); }
});
+ setHint();
+ setThemeIcon();
update();
})();
@@ -690,7 +723,7 @@ def html_doc(rows: list[dict]) -> str:
f'{cat_icon}{t_icon}'
f'{name}{desc}'
)
- chips.append(f'')
+ chips.append(f'')
sections.append(
f'