From d0f47de0ef23ad311e984500db5da781ea5e6018 Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Mon, 1 Jun 2026 10:15:04 -0500 Subject: [PATCH] bmad-brainstorming: composer header polish + dark mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Center header content (.hwrap) so it aligns with the card column on wide screens. - Replace the text filter with jump-nav: category chips smooth-scroll to their section (offset for the sticky header); drop the category exclude-toggle, so Random/AI draw from the whole catalog. - Fix narrow-screen crowding between the chips and the Copy prompt button. - Move Copy prompt to the end of the Techniques row, anchored to the Total readout. - Add a per-mode hint line that explains the selected facilitation stance. - Dark mode: refactor all colors to CSS variables + a dark palette, with a header toggle (☾/☀) that defaults to system preference and persists in localStorage; an inline head script applies the theme before first paint to avoid a flash. Category hues are lifted toward white on dark surfaces to stay legible. Regenerated the snapshot-tested selection page; SKILL.md wording updated (chips are jump-nav, not a filter). 52 Python tests passing. --- src/core-skills/bmad-brainstorming/SKILL.md | 2 +- .../assets/brain-selector.html | 141 ++++++++++------- .../bmad-brainstorming/scripts/brain.py | 143 +++++++++++------- 3 files changed, 176 insertions(+), 110 deletions(-) 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 0 AI picks 0 Total 0 · 3–4 is the sweet spot +
- - + Jump to +
-
+

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 0 AI picks 0 Total 0 · 3–4 is the sweet spot +
- - + Jump to +
{{CHIPS}}
-
{{CHIPS}}
+
{{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'

{disp}{len(groups[cat])}

' f'
{"".join(cards)}
'