From 2f5563299b7392915ee37a6b89c337b576ee5e34 Mon Sep 17 00:00:00 2001 From: Michael Scott Asato Cuthbert Date: Mon, 2 Feb 2026 14:15:09 -1000 Subject: [PATCH 1/7] copybutton - no jquery --- .../source/_themes/m21/static/copybutton.js | 199 ++++++++++++++---- 1 file changed, 153 insertions(+), 46 deletions(-) diff --git a/documentation/source/_themes/m21/static/copybutton.js b/documentation/source/_themes/m21/static/copybutton.js index c9f49770b1..68235d113f 100644 --- a/documentation/source/_themes/m21/static/copybutton.js +++ b/documentation/source/_themes/m21/static/copybutton.js @@ -1,64 +1,171 @@ // found in the _themes/m21/static folder -$(document).ready(function() { +document.addEventListener('DOMContentLoaded', function() { /* Add a [>>>] button on the top-right corner of code samples to hide * the >>> and ... prompts and the output and thus make the code * copyable. */ - var div = $('.highlight-python .highlight,' + - '.highlight-python3 .highlight,' + - '.highlight-default .highlight'); - var pre = div.find('pre'); + var div = document.querySelectorAll('.highlight-python .highlight,' + + '.highlight-python3 .highlight,' + + '.highlight-default .highlight'); + + // NOTE: in the old jQuery version, `pre` was a jQuery collection across all divs. + // Here we take the first
 we find (if any) to read theme styles.
+	var firstPre = null;
+	for (var i = 0; i < div.length; i++) {
+		var maybePre = div[i].querySelector('pre');
+		if (maybePre) {
+			firstPre = maybePre;
+			break;
+		}
+	}
 
 	// get the styles from the current theme
-	pre.parent().parent().css('position', 'relative');
+	if (firstPre && firstPre.parentElement && firstPre.parentElement.parentElement) {
+		firstPre.parentElement.parentElement.style.position = 'relative';
+	}
 	var hide_text = 'Hide the prompts and output';
 	var show_text = 'Show the prompts and output';
-	var border_width = pre.css('border-top-width');
-	var border_style = pre.css('border-top-style');
-	var border_color = pre.css('border-top-color');
+
+	var border_width = '';
+	var border_style = '';
+	var border_color = '';
+	if (firstPre) {
+		var cs = window.getComputedStyle(firstPre);
+		border_width = cs.borderTopWidth;
+		border_style = cs.borderTopStyle;
+		border_color = cs.borderTopColor;
+	}
+
 	var button_styles = {
-	    'cursor':'pointer', 'position': 'absolute', 'top': '0', 'right': '0',
-	    'border-color': border_color, 'border-style': border_style,
-	    'border-width': border_width, 'color': border_color, 'text-size': '75%',
-	    'font-family': 'monospace', 'padding-left': '0.2em', 'padding-right': '0.2em',
-	    'border-radius': '0 3px 0 0'
+		'cursor':'pointer', 'position': 'absolute', 'top': '0', 'right': '0',
+		'border-color': border_color, 'border-style': border_style,
+		'border-width': border_width, 'color': border_color, 'text-size': '75%',
+		'font-family': 'monospace', 'padding-left': '0.2em', 'padding-right': '0.2em',
+		'border-radius': '0 3px 0 0'
+	};
+
+	function apply_button_styles(button) {
+		// Keep the original style keys, but translate the ones that need JS-style names.
+		button.style.cursor = button_styles['cursor'];
+		button.style.position = button_styles['position'];
+		button.style.top = button_styles['top'];
+		button.style.right = button_styles['right'];
+		button.style.borderColor = button_styles['border-color'];
+		button.style.borderStyle = button_styles['border-style'];
+		button.style.borderWidth = button_styles['border-width'];
+		button.style.color = button_styles['color'];
+		// Old code had 'text-size' which is not a real CSS property; map it to font-size.
+		button.style.fontSize = '75%';
+		button.style.fontFamily = button_styles['font-family'];
+		button.style.paddingLeft = button_styles['padding-left'];
+		button.style.paddingRight = button_styles['padding-right'];
+		button.style.borderRadius = button_styles['border-radius'];
+	}
+
+	function hide_elements(parent, selector) {
+		var els = parent.querySelectorAll(selector);
+		for (var i = 0; i < els.length; i++) {
+			els[i].style.display = 'none';
+		}
+	}
+
+	function show_elements(parent, selector) {
+		var els = parent.querySelectorAll(selector);
+		for (var i = 0; i < els.length; i++) {
+			els[i].style.display = '';
+		}
+	}
+
+	function set_traceback_visibility(pre, visible) {
+		// Equivalent to: button.next('pre').find('.gt').nextUntil('.gp, .go')...
+		var gts = pre.querySelectorAll('.gt');
+		for (var i = 0; i < gts.length; i++) {
+			var n = gts[i].nextSibling;
+			while (n) {
+				if (n.nodeType === Node.ELEMENT_NODE) {
+					var el = n;
+					if (el.classList.contains('gp') || el.classList.contains('go')) {
+						break;
+					}
+					el.style.visibility = visible ? 'visible' : 'hidden';
+				}
+				n = n.nextSibling;
+			}
+		}
 	}
 
 	// create and add the button to all the code blocks that contain >>>
-	div.each(function(index) {
-		var jthis = $(this);
-		if (jthis.find('.gp').length > 0) {
-		    var button = $('>>>');
-		    button.css(button_styles)
-			button.attr('title', hide_text);
-		    button.data('hidden', 'false');
-		    jthis.prepend(button);
+	for (var index = 0; index < div.length; index++) {
+		var jthis = div[index];
+		var pre = jthis.querySelector('pre');
+
+		// get the styles from the current theme (per-block positioning like before)
+		if (pre && pre.parentElement && pre.parentElement.parentElement) {
+			pre.parentElement.parentElement.style.position = 'relative';
+		}
+
+		if (jthis.querySelectorAll('.gp').length > 0) {
+			var button = document.createElement('span');
+			button.className = 'copybutton';
+			button.textContent = '>>>';
+			apply_button_styles(button);
+			button.setAttribute('title', hide_text);
+			button.setAttribute('data-hidden', 'false');
+			jthis.insertBefore(button, jthis.firstChild);
 		}
+
 		// tracebacks (.gt) contain bare text elements that need to be
 		// wrapped in a span to work with .nextUntil() (see later)
-		jthis.find('pre:has(.gt)').contents().filter(function() {
-			return ((this.nodeType == 3) && (this.data.trim().length > 0));
-		    }).wrap('');
-	    });
+		var preWithGt = jthis.querySelectorAll('pre');
+		for (var p = 0; p < preWithGt.length; p++) {
+			if (preWithGt[p].querySelector('.gt')) {
+				var contents = Array.prototype.slice.call(preWithGt[p].childNodes);
+				for (var c = 0; c < contents.length; c++) {
+					var node = contents[c];
+					if ((node.nodeType === 3) && (node.data.trim().length > 0)) {
+						var span = document.createElement('span');
+						span.textContent = node.data;
+						preWithGt[p].replaceChild(span, node);
+					}
+				}
+			}
+		}
+	}
 
 	// define the behavior of the button when it's clicked
-	$('.copybutton').click(function(e){
-		e.preventDefault();
-		var button = $(this);
-		if (button.data('hidden') === 'false') {
-		    // hide the code output
-		    button.parent().find('.go, .gp, .gt').hide();
-		    button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'hidden');
-		    button.css('text-decoration', 'line-through');
-		    button.attr('title', show_text);
-		    button.data('hidden', 'true');
-		} else {
-		    // show the code output
-		    button.parent().find('.go, .gp, .gt').show();
-		    button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'visible');
-		    button.css('text-decoration', 'none');
-		    button.attr('title', hide_text);
-		    button.data('hidden', 'false');
-		}
-	    });
-    });
+	var buttons = document.querySelectorAll('.copybutton');
+	for (var b = 0; b < buttons.length; b++) {
+		buttons[b].addEventListener('click', function(e){
+			e.preventDefault();
+			var button = this;
+			if (button.getAttribute('data-hidden') === 'false') {
+				// hide the code output
+				hide_elements(button.parentNode, '.go, .gp, .gt');
+                var next = button.nextElementSibling;
+                while (next && next.tagName.toLowerCase() !== 'pre') {
+                    next = next.nextElementSibling;
+                }
+                if (next) {
+                    set_traceback_visibility(next, false);
+				}
+				button.style.textDecoration = 'line-through';
+				button.setAttribute('title', show_text);
+				button.setAttribute('data-hidden', 'true');
+			} else {
+				// show the code output
+				show_elements(button.parentNode, '.go, .gp, .gt');
+                var next2 = button.nextElementSibling;
+                while (next2 && next2.tagName.toLowerCase() !== 'pre') {
+                    next2 = next2.nextElementSibling;
+                }
+                if (next2) {
+                    // next2 is the same thing jQuery would return for .next('pre')
+					set_traceback_visibility(next2, true);
+				}
+				button.style.textDecoration = 'none';
+				button.setAttribute('title', hide_text);
+				button.setAttribute('data-hidden', 'false');
+			}
+		});
+	}
+});

From 32bd1f38507d703f6d7261efafcc3a43b80a9569 Mon Sep 17 00:00:00 2001
From: Michael Scott Asato Cuthbert 
Date: Mon, 2 Feb 2026 14:19:29 -1000
Subject: [PATCH 2/7] fix indentation style

---
 .../source/_themes/m21/static/copybutton.js   | 296 +++++++++---------
 1 file changed, 154 insertions(+), 142 deletions(-)

diff --git a/documentation/source/_themes/m21/static/copybutton.js b/documentation/source/_themes/m21/static/copybutton.js
index 68235d113f..1cea188de2 100644
--- a/documentation/source/_themes/m21/static/copybutton.js
+++ b/documentation/source/_themes/m21/static/copybutton.js
@@ -1,171 +1,183 @@
 // found in the _themes/m21/static folder
+// MSAC: I can't remember where this came from, but in 2026 rewritten
+// to use modern JS and no jQuery
 
+/** Add a [>>>] button on the top-right corner of code samples to hide
+ * the >>> and ... prompts and the output and thus make the code
+ * copyable. */
 document.addEventListener('DOMContentLoaded', function() {
-	/* Add a [>>>] button on the top-right corner of code samples to hide
-	 * the >>> and ... prompts and the output and thus make the code
-	 * copyable. */
-	var div = document.querySelectorAll('.highlight-python .highlight,' +
-			'.highlight-python3 .highlight,' +
-			'.highlight-default .highlight');
+    var div = document.querySelectorAll(
+        '.highlight-python .highlight,'
+        + '.highlight-python3 .highlight,'
+        + '.highlight-default .highlight'
+    );
 
-	// NOTE: in the old jQuery version, `pre` was a jQuery collection across all divs.
-	// Here we take the first 
 we find (if any) to read theme styles.
-	var firstPre = null;
-	for (var i = 0; i < div.length; i++) {
-		var maybePre = div[i].querySelector('pre');
-		if (maybePre) {
-			firstPre = maybePre;
-			break;
-		}
-	}
+    // NOTE: in the old jQuery version, `pre` was a jQuery collection across all divs.
+    // Here we take the first 
 we find (if any) to read theme styles.
+    var firstPre = null;
+    for (var i = 0; i < div.length; i++) {
+        var maybePre = div[i].querySelector('pre');
+        if (maybePre) {
+            firstPre = maybePre;
+            break;
+        }
+    }
 
-	// get the styles from the current theme
-	if (firstPre && firstPre.parentElement && firstPre.parentElement.parentElement) {
-		firstPre.parentElement.parentElement.style.position = 'relative';
-	}
-	var hide_text = 'Hide the prompts and output';
-	var show_text = 'Show the prompts and output';
+    // get the styles from the current theme
+    if (firstPre && firstPre.parentElement && firstPre.parentElement.parentElement) {
+        firstPre.parentElement.parentElement.style.position = 'relative';
+    }
+    var hide_text = 'Hide the prompts and output';
+    var show_text = 'Show the prompts and output';
 
-	var border_width = '';
-	var border_style = '';
-	var border_color = '';
-	if (firstPre) {
-		var cs = window.getComputedStyle(firstPre);
-		border_width = cs.borderTopWidth;
-		border_style = cs.borderTopStyle;
-		border_color = cs.borderTopColor;
-	}
+    var border_width = '';
+    var border_style = '';
+    var border_color = '';
+    if (firstPre) {
+        var cs = window.getComputedStyle(firstPre);
+        border_width = cs.borderTopWidth;
+        border_style = cs.borderTopStyle;
+        border_color = cs.borderTopColor;
+    }
 
-	var button_styles = {
-		'cursor':'pointer', 'position': 'absolute', 'top': '0', 'right': '0',
-		'border-color': border_color, 'border-style': border_style,
-		'border-width': border_width, 'color': border_color, 'text-size': '75%',
-		'font-family': 'monospace', 'padding-left': '0.2em', 'padding-right': '0.2em',
-		'border-radius': '0 3px 0 0'
-	};
+    var button_styles = {
+        'cursor': 'pointer',
+        'position': 'absolute',
+        'top': '0',
+        'right': '0',
+        'border-color': border_color,
+        'border-style': border_style,
+        'border-width': border_width,
+        'color': border_color,
+        'text-size': '75%',
+        'font-family': 'monospace',
+        'padding-left': '0.2em',
+        'padding-right': '0.2em',
+        'border-radius': '0 3px 0 0'
+    };
 
-	function apply_button_styles(button) {
-		// Keep the original style keys, but translate the ones that need JS-style names.
-		button.style.cursor = button_styles['cursor'];
-		button.style.position = button_styles['position'];
-		button.style.top = button_styles['top'];
-		button.style.right = button_styles['right'];
-		button.style.borderColor = button_styles['border-color'];
-		button.style.borderStyle = button_styles['border-style'];
-		button.style.borderWidth = button_styles['border-width'];
-		button.style.color = button_styles['color'];
-		// Old code had 'text-size' which is not a real CSS property; map it to font-size.
-		button.style.fontSize = '75%';
-		button.style.fontFamily = button_styles['font-family'];
-		button.style.paddingLeft = button_styles['padding-left'];
-		button.style.paddingRight = button_styles['padding-right'];
-		button.style.borderRadius = button_styles['border-radius'];
-	}
+    function apply_button_styles(button) {
+        // Keep the original style keys, but translate the ones that need JS-style names.
+        button.style.cursor = button_styles['cursor'];
+        button.style.position = button_styles['position'];
+        button.style.top = button_styles['top'];
+        button.style.right = button_styles['right'];
+        button.style.borderColor = button_styles['border-color'];
+        button.style.borderStyle = button_styles['border-style'];
+        button.style.borderWidth = button_styles['border-width'];
+        button.style.color = button_styles['color'];
+        // Old code had 'text-size' which is not a real CSS property; map it to font-size.
+        button.style.fontSize = '75%';
+        button.style.fontFamily = button_styles['font-family'];
+        button.style.paddingLeft = button_styles['padding-left'];
+        button.style.paddingRight = button_styles['padding-right'];
+        button.style.borderRadius = button_styles['border-radius'];
+    }
 
-	function hide_elements(parent, selector) {
-		var els = parent.querySelectorAll(selector);
-		for (var i = 0; i < els.length; i++) {
-			els[i].style.display = 'none';
-		}
-	}
+    function hide_elements(parent, selector) {
+        var els = parent.querySelectorAll(selector);
+        for (var i = 0; i < els.length; i++) {
+            els[i].style.display = 'none';
+        }
+    }
 
-	function show_elements(parent, selector) {
-		var els = parent.querySelectorAll(selector);
-		for (var i = 0; i < els.length; i++) {
-			els[i].style.display = '';
-		}
-	}
+    function show_elements(parent, selector) {
+        var els = parent.querySelectorAll(selector);
+        for (var i = 0; i < els.length; i++) {
+            els[i].style.display = '';
+        }
+    }
 
-	function set_traceback_visibility(pre, visible) {
-		// Equivalent to: button.next('pre').find('.gt').nextUntil('.gp, .go')...
-		var gts = pre.querySelectorAll('.gt');
-		for (var i = 0; i < gts.length; i++) {
-			var n = gts[i].nextSibling;
-			while (n) {
-				if (n.nodeType === Node.ELEMENT_NODE) {
-					var el = n;
-					if (el.classList.contains('gp') || el.classList.contains('go')) {
-						break;
-					}
-					el.style.visibility = visible ? 'visible' : 'hidden';
-				}
-				n = n.nextSibling;
-			}
-		}
-	}
+    function set_traceback_visibility(pre, visible) {
+        // Equivalent to: button.next('pre').find('.gt').nextUntil('.gp, .go')...
+        var gts = pre.querySelectorAll('.gt');
+        for (var i = 0; i < gts.length; i++) {
+            var n = gts[i].nextSibling;
+            while (n) {
+                if (n.nodeType === Node.ELEMENT_NODE) {
+                    var el = n;
+                    if (el.classList.contains('gp') || el.classList.contains('go')) {
+                        break;
+                    }
+                    el.style.visibility = visible ? 'visible' : 'hidden';
+                }
+                n = n.nextSibling;
+            }
+        }
+    }
 
-	// create and add the button to all the code blocks that contain >>>
-	for (var index = 0; index < div.length; index++) {
-		var jthis = div[index];
-		var pre = jthis.querySelector('pre');
+    // create and add the button to all the code blocks that contain >>>
+    for (var index = 0; index < div.length; index++) {
+        var this_div = div[index];
+        var pre = this_div.querySelector('pre');
 
-		// get the styles from the current theme (per-block positioning like before)
-		if (pre && pre.parentElement && pre.parentElement.parentElement) {
-			pre.parentElement.parentElement.style.position = 'relative';
-		}
+        // get the styles from the current theme (per-block positioning like before)
+        if (pre && pre.parentElement && pre.parentElement.parentElement) {
+            pre.parentElement.parentElement.style.position = 'relative';
+        }
 
-		if (jthis.querySelectorAll('.gp').length > 0) {
-			var button = document.createElement('span');
-			button.className = 'copybutton';
-			button.textContent = '>>>';
-			apply_button_styles(button);
-			button.setAttribute('title', hide_text);
-			button.setAttribute('data-hidden', 'false');
-			jthis.insertBefore(button, jthis.firstChild);
-		}
+        if (this_div.querySelectorAll('.gp').length > 0) {
+            var button = document.createElement('span');
+            button.className = 'copybutton';
+            button.textContent = '>>>';
+            apply_button_styles(button);
+            button.setAttribute('title', hide_text);
+            button.setAttribute('data-hidden', 'false');
+            this_div.insertBefore(button, this_div.firstChild);
+        }
 
-		// tracebacks (.gt) contain bare text elements that need to be
-		// wrapped in a span to work with .nextUntil() (see later)
-		var preWithGt = jthis.querySelectorAll('pre');
-		for (var p = 0; p < preWithGt.length; p++) {
-			if (preWithGt[p].querySelector('.gt')) {
-				var contents = Array.prototype.slice.call(preWithGt[p].childNodes);
-				for (var c = 0; c < contents.length; c++) {
-					var node = contents[c];
-					if ((node.nodeType === 3) && (node.data.trim().length > 0)) {
-						var span = document.createElement('span');
-						span.textContent = node.data;
-						preWithGt[p].replaceChild(span, node);
-					}
-				}
-			}
-		}
-	}
+        // tracebacks (.gt) contain bare text elements that need to be
+        // wrapped in a span to work with .nextUntil() (see later)
+        var preWithGt = this_div.querySelectorAll('pre');
+        for (var p = 0; p < preWithGt.length; p++) {
+            if (preWithGt[p].querySelector('.gt')) {
+                var contents = Array.prototype.slice.call(preWithGt[p].childNodes);
+                for (var c = 0; c < contents.length; c++) {
+                    var node = contents[c];
+                    if ((node.nodeType === 3) && (node.data.trim().length > 0)) {
+                        var span = document.createElement('span');
+                        span.textContent = node.data;
+                        preWithGt[p].replaceChild(span, node);
+                    }
+                }
+            }
+        }
+    }
 
-	// define the behavior of the button when it's clicked
-	var buttons = document.querySelectorAll('.copybutton');
-	for (var b = 0; b < buttons.length; b++) {
-		buttons[b].addEventListener('click', function(e){
-			e.preventDefault();
-			var button = this;
-			if (button.getAttribute('data-hidden') === 'false') {
-				// hide the code output
-				hide_elements(button.parentNode, '.go, .gp, .gt');
+    // define the behavior of the button when it's clicked
+    var buttons = document.querySelectorAll('.copybutton');
+    for (var b = 0; b < buttons.length; b++) {
+        buttons[b].addEventListener('click', function(e){
+            e.preventDefault();
+            var button = this;
+            if (button.getAttribute('data-hidden') === 'false') {
+                // hide the code output
+                hide_elements(button.parentNode, '.go, .gp, .gt');
                 var next = button.nextElementSibling;
                 while (next && next.tagName.toLowerCase() !== 'pre') {
                     next = next.nextElementSibling;
                 }
                 if (next) {
                     set_traceback_visibility(next, false);
-				}
-				button.style.textDecoration = 'line-through';
-				button.setAttribute('title', show_text);
-				button.setAttribute('data-hidden', 'true');
-			} else {
-				// show the code output
-				show_elements(button.parentNode, '.go, .gp, .gt');
+                }
+                button.style.textDecoration = 'line-through';
+                button.setAttribute('title', show_text);
+                button.setAttribute('data-hidden', 'true');
+            } else {
+                // show the code output
+                show_elements(button.parentNode, '.go, .gp, .gt');
                 var next2 = button.nextElementSibling;
                 while (next2 && next2.tagName.toLowerCase() !== 'pre') {
                     next2 = next2.nextElementSibling;
                 }
                 if (next2) {
                     // next2 is the same thing jQuery would return for .next('pre')
-					set_traceback_visibility(next2, true);
-				}
-				button.style.textDecoration = 'none';
-				button.setAttribute('title', hide_text);
-				button.setAttribute('data-hidden', 'false');
-			}
-		});
-	}
+                    set_traceback_visibility(next2, true);
+                }
+                button.style.textDecoration = 'none';
+                button.setAttribute('title', hide_text);
+                button.setAttribute('data-hidden', 'false');
+            }
+        });
+    }
 });

From e525ed93bbd4e7c61feb995229614862eb7c4bcd Mon Sep 17 00:00:00 2001
From: Michael Scott Asato Cuthbert 
Date: Mon, 2 Feb 2026 14:25:00 -1000
Subject: [PATCH 3/7] var to let/const

---
 .../source/_themes/m21/static/copybutton.js   | 68 +++++++++----------
 1 file changed, 34 insertions(+), 34 deletions(-)

diff --git a/documentation/source/_themes/m21/static/copybutton.js b/documentation/source/_themes/m21/static/copybutton.js
index 1cea188de2..88ca08a068 100644
--- a/documentation/source/_themes/m21/static/copybutton.js
+++ b/documentation/source/_themes/m21/static/copybutton.js
@@ -6,7 +6,7 @@
  * the >>> and ... prompts and the output and thus make the code
  * copyable. */
 document.addEventListener('DOMContentLoaded', function() {
-    var div = document.querySelectorAll(
+    const div = document.querySelectorAll(
         '.highlight-python .highlight,'
         + '.highlight-python3 .highlight,'
         + '.highlight-default .highlight'
@@ -14,9 +14,9 @@ document.addEventListener('DOMContentLoaded', function() {
 
     // NOTE: in the old jQuery version, `pre` was a jQuery collection across all divs.
     // Here we take the first 
 we find (if any) to read theme styles.
-    var firstPre = null;
-    for (var i = 0; i < div.length; i++) {
-        var maybePre = div[i].querySelector('pre');
+    let firstPre = null;
+    for (let i = 0; i < div.length; i++) {
+        const maybePre = div[i].querySelector('pre');
         if (maybePre) {
             firstPre = maybePre;
             break;
@@ -27,20 +27,20 @@ document.addEventListener('DOMContentLoaded', function() {
     if (firstPre && firstPre.parentElement && firstPre.parentElement.parentElement) {
         firstPre.parentElement.parentElement.style.position = 'relative';
     }
-    var hide_text = 'Hide the prompts and output';
-    var show_text = 'Show the prompts and output';
+    const hide_text = 'Hide the prompts and output';
+    const show_text = 'Show the prompts and output';
 
-    var border_width = '';
-    var border_style = '';
-    var border_color = '';
+    let border_width = '';
+    let border_style = '';
+    let border_color = '';
     if (firstPre) {
-        var cs = window.getComputedStyle(firstPre);
+        const cs = window.getComputedStyle(firstPre);
         border_width = cs.borderTopWidth;
         border_style = cs.borderTopStyle;
         border_color = cs.borderTopColor;
     }
 
-    var button_styles = {
+    const button_styles = {
         'cursor': 'pointer',
         'position': 'absolute',
         'top': '0',
@@ -75,27 +75,27 @@ document.addEventListener('DOMContentLoaded', function() {
     }
 
     function hide_elements(parent, selector) {
-        var els = parent.querySelectorAll(selector);
-        for (var i = 0; i < els.length; i++) {
+        const els = parent.querySelectorAll(selector);
+        for (let i = 0; i < els.length; i++) {
             els[i].style.display = 'none';
         }
     }
 
     function show_elements(parent, selector) {
-        var els = parent.querySelectorAll(selector);
-        for (var i = 0; i < els.length; i++) {
+        const els = parent.querySelectorAll(selector);
+        for (let i = 0; i < els.length; i++) {
             els[i].style.display = '';
         }
     }
 
     function set_traceback_visibility(pre, visible) {
         // Equivalent to: button.next('pre').find('.gt').nextUntil('.gp, .go')...
-        var gts = pre.querySelectorAll('.gt');
-        for (var i = 0; i < gts.length; i++) {
-            var n = gts[i].nextSibling;
+        const gts = pre.querySelectorAll('.gt');
+        for (let i = 0; i < gts.length; i++) {
+            let n = gts[i].nextSibling;
             while (n) {
                 if (n.nodeType === Node.ELEMENT_NODE) {
-                    var el = n;
+                    const el = n;
                     if (el.classList.contains('gp') || el.classList.contains('go')) {
                         break;
                     }
@@ -107,9 +107,9 @@ document.addEventListener('DOMContentLoaded', function() {
     }
 
     // create and add the button to all the code blocks that contain >>>
-    for (var index = 0; index < div.length; index++) {
-        var this_div = div[index];
-        var pre = this_div.querySelector('pre');
+    for (let index = 0; index < div.length; index++) {
+        const this_div = div[index];
+        const pre = this_div.querySelector('pre');
 
         // get the styles from the current theme (per-block positioning like before)
         if (pre && pre.parentElement && pre.parentElement.parentElement) {
@@ -117,7 +117,7 @@ document.addEventListener('DOMContentLoaded', function() {
         }
 
         if (this_div.querySelectorAll('.gp').length > 0) {
-            var button = document.createElement('span');
+            const button = document.createElement('span');
             button.className = 'copybutton';
             button.textContent = '>>>';
             apply_button_styles(button);
@@ -128,14 +128,14 @@ document.addEventListener('DOMContentLoaded', function() {
 
         // tracebacks (.gt) contain bare text elements that need to be
         // wrapped in a span to work with .nextUntil() (see later)
-        var preWithGt = this_div.querySelectorAll('pre');
-        for (var p = 0; p < preWithGt.length; p++) {
+        const preWithGt = this_div.querySelectorAll('pre');
+        for (let p = 0; p < preWithGt.length; p++) {
             if (preWithGt[p].querySelector('.gt')) {
-                var contents = Array.prototype.slice.call(preWithGt[p].childNodes);
-                for (var c = 0; c < contents.length; c++) {
-                    var node = contents[c];
+                const contents = Array.prototype.slice.call(preWithGt[p].childNodes);
+                for (let c = 0; c < contents.length; c++) {
+                    const node = contents[c];
                     if ((node.nodeType === 3) && (node.data.trim().length > 0)) {
-                        var span = document.createElement('span');
+                        const span = document.createElement('span');
                         span.textContent = node.data;
                         preWithGt[p].replaceChild(span, node);
                     }
@@ -145,15 +145,15 @@ document.addEventListener('DOMContentLoaded', function() {
     }
 
     // define the behavior of the button when it's clicked
-    var buttons = document.querySelectorAll('.copybutton');
-    for (var b = 0; b < buttons.length; b++) {
+    const buttons = document.querySelectorAll('.copybutton');
+    for (let b = 0; b < buttons.length; b++) {
         buttons[b].addEventListener('click', function(e){
             e.preventDefault();
-            var button = this;
+            const button = this;
             if (button.getAttribute('data-hidden') === 'false') {
                 // hide the code output
                 hide_elements(button.parentNode, '.go, .gp, .gt');
-                var next = button.nextElementSibling;
+                let next = button.nextElementSibling;
                 while (next && next.tagName.toLowerCase() !== 'pre') {
                     next = next.nextElementSibling;
                 }
@@ -166,7 +166,7 @@ document.addEventListener('DOMContentLoaded', function() {
             } else {
                 // show the code output
                 show_elements(button.parentNode, '.go, .gp, .gt');
-                var next2 = button.nextElementSibling;
+                let next2 = button.nextElementSibling;
                 while (next2 && next2.tagName.toLowerCase() !== 'pre') {
                     next2 = next2.nextElementSibling;
                 }

From f1e904f77ba3877953d8b919c4495049c2cc0891 Mon Sep 17 00:00:00 2001
From: Michael Scott Asato Cuthbert 
Date: Mon, 2 Feb 2026 14:25:17 -1000
Subject: [PATCH 4/7] of iteration

---
 .../source/_themes/m21/static/copybutton.js   | 41 +++++++++----------
 1 file changed, 19 insertions(+), 22 deletions(-)

diff --git a/documentation/source/_themes/m21/static/copybutton.js b/documentation/source/_themes/m21/static/copybutton.js
index 88ca08a068..7876aac12a 100644
--- a/documentation/source/_themes/m21/static/copybutton.js
+++ b/documentation/source/_themes/m21/static/copybutton.js
@@ -5,7 +5,7 @@
 /** Add a [>>>] button on the top-right corner of code samples to hide
  * the >>> and ... prompts and the output and thus make the code
  * copyable. */
-document.addEventListener('DOMContentLoaded', function() {
+document.addEventListener('DOMContentLoaded', () => {
     const div = document.querySelectorAll(
         '.highlight-python .highlight,'
         + '.highlight-python3 .highlight,'
@@ -15,8 +15,8 @@ document.addEventListener('DOMContentLoaded', function() {
     // NOTE: in the old jQuery version, `pre` was a jQuery collection across all divs.
     // Here we take the first 
 we find (if any) to read theme styles.
     let firstPre = null;
-    for (let i = 0; i < div.length; i++) {
-        const maybePre = div[i].querySelector('pre');
+    for (const this_div of div) {
+        const maybePre = this_div.querySelector('pre');
         if (maybePre) {
             firstPre = maybePre;
             break;
@@ -76,23 +76,23 @@ document.addEventListener('DOMContentLoaded', function() {
 
     function hide_elements(parent, selector) {
         const els = parent.querySelectorAll(selector);
-        for (let i = 0; i < els.length; i++) {
-            els[i].style.display = 'none';
+        for (const el of els) {
+            el.style.display = 'none';
         }
     }
 
     function show_elements(parent, selector) {
         const els = parent.querySelectorAll(selector);
-        for (let i = 0; i < els.length; i++) {
-            els[i].style.display = '';
+        for (const el of els) {
+            el.style.display = '';
         }
     }
 
     function set_traceback_visibility(pre, visible) {
         // Equivalent to: button.next('pre').find('.gt').nextUntil('.gp, .go')...
         const gts = pre.querySelectorAll('.gt');
-        for (let i = 0; i < gts.length; i++) {
-            let n = gts[i].nextSibling;
+        for (const gt of gts) {
+            let n = gt.nextSibling;
             while (n) {
                 if (n.nodeType === Node.ELEMENT_NODE) {
                     const el = n;
@@ -107,11 +107,9 @@ document.addEventListener('DOMContentLoaded', function() {
     }
 
     // create and add the button to all the code blocks that contain >>>
-    for (let index = 0; index < div.length; index++) {
-        const this_div = div[index];
-        const pre = this_div.querySelector('pre');
-
+    for (const this_div of div) {
         // get the styles from the current theme (per-block positioning like before)
+        const pre = this_div.querySelector('pre');
         if (pre && pre.parentElement && pre.parentElement.parentElement) {
             pre.parentElement.parentElement.style.position = 'relative';
         }
@@ -129,15 +127,14 @@ document.addEventListener('DOMContentLoaded', function() {
         // tracebacks (.gt) contain bare text elements that need to be
         // wrapped in a span to work with .nextUntil() (see later)
         const preWithGt = this_div.querySelectorAll('pre');
-        for (let p = 0; p < preWithGt.length; p++) {
-            if (preWithGt[p].querySelector('.gt')) {
-                const contents = Array.prototype.slice.call(preWithGt[p].childNodes);
-                for (let c = 0; c < contents.length; c++) {
-                    const node = contents[c];
+        for (const preNode of preWithGt) {
+            if (preNode.querySelector('.gt')) {
+                const contents = Array.prototype.slice.call(preNode.childNodes);
+                for (const node of contents) {
                     if ((node.nodeType === 3) && (node.data.trim().length > 0)) {
                         const span = document.createElement('span');
                         span.textContent = node.data;
-                        preWithGt[p].replaceChild(span, node);
+                        preNode.replaceChild(span, node);
                     }
                 }
             }
@@ -146,10 +143,10 @@ document.addEventListener('DOMContentLoaded', function() {
 
     // define the behavior of the button when it's clicked
     const buttons = document.querySelectorAll('.copybutton');
-    for (let b = 0; b < buttons.length; b++) {
-        buttons[b].addEventListener('click', function(e){
+    for (const buttonEl of buttons) {
+        buttonEl.addEventListener('click', e => {
             e.preventDefault();
-            const button = this;
+            const button = e.currentTarget;
             if (button.getAttribute('data-hidden') === 'false') {
                 // hide the code output
                 hide_elements(button.parentNode, '.go, .gp, .gt');

From ed571cee9f973e1e5b8d7eb8b33fb17db5b6008f Mon Sep 17 00:00:00 2001
From: Michael Scott Asato Cuthbert 
Date: Mon, 2 Feb 2026 14:32:42 -1000
Subject: [PATCH 5/7] refactor redundant parts

---
 .../source/_themes/m21/static/copybutton.js   | 48 ++++++++++---------
 1 file changed, 26 insertions(+), 22 deletions(-)

diff --git a/documentation/source/_themes/m21/static/copybutton.js b/documentation/source/_themes/m21/static/copybutton.js
index 7876aac12a..49f5860998 100644
--- a/documentation/source/_themes/m21/static/copybutton.js
+++ b/documentation/source/_themes/m21/static/copybutton.js
@@ -106,6 +106,17 @@ document.addEventListener('DOMContentLoaded', () => {
         }
     }
 
+    /**
+     * find the next sibling that is a 
 tag.
+     */
+    function next_pre_sibling(startEl) {
+        let next = startEl.nextElementSibling;
+        while (next && next.tagName.toLowerCase() !== 'pre') {
+            next = next.nextElementSibling;
+        }
+        return next;
+    }
+
     // create and add the button to all the code blocks that contain >>>
     for (const this_div of div) {
         // get the styles from the current theme (per-block positioning like before)
@@ -116,7 +127,7 @@ document.addEventListener('DOMContentLoaded', () => {
 
         if (this_div.querySelectorAll('.gp').length > 0) {
             const button = document.createElement('span');
-            button.className = 'copybutton';
+            button.className = 'copy_button';
             button.textContent = '>>>';
             apply_button_styles(button);
             button.setAttribute('title', hide_text);
@@ -129,9 +140,9 @@ document.addEventListener('DOMContentLoaded', () => {
         const preWithGt = this_div.querySelectorAll('pre');
         for (const preNode of preWithGt) {
             if (preNode.querySelector('.gt')) {
-                const contents = Array.prototype.slice.call(preNode.childNodes);
+                const contents = Array.from(preNode.childNodes);
                 for (const node of contents) {
-                    if ((node.nodeType === 3) && (node.data.trim().length > 0)) {
+                    if ((node.nodeType === Node.TEXT_NODE) && node.data.trim()) {
                         const span = document.createElement('span');
                         span.textContent = node.data;
                         preNode.replaceChild(span, node);
@@ -142,34 +153,27 @@ document.addEventListener('DOMContentLoaded', () => {
     }
 
     // define the behavior of the button when it's clicked
-    const buttons = document.querySelectorAll('.copybutton');
-    for (const buttonEl of buttons) {
-        buttonEl.addEventListener('click', e => {
+    const buttons = document.querySelectorAll('.copy_button');
+    for (const button of buttons) {
+        button.addEventListener('click', e => {
             e.preventDefault();
-            const button = e.currentTarget;
+            const parent = button.parentNode;
+            const pre = next_pre_sibling(button);
             if (button.getAttribute('data-hidden') === 'false') {
                 // hide the code output
-                hide_elements(button.parentNode, '.go, .gp, .gt');
-                let next = button.nextElementSibling;
-                while (next && next.tagName.toLowerCase() !== 'pre') {
-                    next = next.nextElementSibling;
-                }
-                if (next) {
-                    set_traceback_visibility(next, false);
+                hide_elements(parent, '.go, .gp, .gt');
+                if (pre) {
+                    set_traceback_visibility(pre, false);
                 }
                 button.style.textDecoration = 'line-through';
                 button.setAttribute('title', show_text);
                 button.setAttribute('data-hidden', 'true');
             } else {
                 // show the code output
-                show_elements(button.parentNode, '.go, .gp, .gt');
-                let next2 = button.nextElementSibling;
-                while (next2 && next2.tagName.toLowerCase() !== 'pre') {
-                    next2 = next2.nextElementSibling;
-                }
-                if (next2) {
-                    // next2 is the same thing jQuery would return for .next('pre')
-                    set_traceback_visibility(next2, true);
+                show_elements(parent, '.go, .gp, .gt');
+                if (pre) {
+                    // pre is the same thing jQuery would return for .next('pre')
+                    set_traceback_visibility(pre, true);
                 }
                 button.style.textDecoration = 'none';
                 button.setAttribute('title', hide_text);

From 96ad636e50709f8a21fc5b1894880aa3f5a8cb45 Mon Sep 17 00:00:00 2001
From: Michael Scott Asato Cuthbert 
Date: Mon, 2 Feb 2026 14:33:58 -1000
Subject: [PATCH 6/7] pluralize divs

---
 documentation/source/_themes/m21/static/copybutton.js | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/documentation/source/_themes/m21/static/copybutton.js b/documentation/source/_themes/m21/static/copybutton.js
index 49f5860998..1e469dd5d7 100644
--- a/documentation/source/_themes/m21/static/copybutton.js
+++ b/documentation/source/_themes/m21/static/copybutton.js
@@ -6,16 +6,16 @@
  * the >>> and ... prompts and the output and thus make the code
  * copyable. */
 document.addEventListener('DOMContentLoaded', () => {
-    const div = document.querySelectorAll(
+    const divs = document.querySelectorAll(
         '.highlight-python .highlight,'
         + '.highlight-python3 .highlight,'
         + '.highlight-default .highlight'
     );
 
-    // NOTE: in the old jQuery version, `pre` was a jQuery collection across all divs.
-    // Here we take the first 
 we find (if any) to read theme styles.
+    // We take the first 
 we find (if any) to read theme styles
+    // and apply them to the buttons
     let firstPre = null;
-    for (const this_div of div) {
+    for (const this_div of divs) {
         const maybePre = this_div.querySelector('pre');
         if (maybePre) {
             firstPre = maybePre;
@@ -118,7 +118,7 @@ document.addEventListener('DOMContentLoaded', () => {
     }
 
     // create and add the button to all the code blocks that contain >>>
-    for (const this_div of div) {
+    for (const this_div of divs) {
         // get the styles from the current theme (per-block positioning like before)
         const pre = this_div.querySelector('pre');
         if (pre && pre.parentElement && pre.parentElement.parentElement) {

From 5fc644c542b3a950723cd8216a0d48b535c060a0 Mon Sep 17 00:00:00 2001
From: Michael Scott Asato Cuthbert 
Date: Mon, 2 Feb 2026 14:40:12 -1000
Subject: [PATCH 7/7] aria accessibility

---
 .../source/_themes/m21/static/copybutton.js   | 58 ++++++++-----------
 1 file changed, 24 insertions(+), 34 deletions(-)

diff --git a/documentation/source/_themes/m21/static/copybutton.js b/documentation/source/_themes/m21/static/copybutton.js
index 1e469dd5d7..71d8e99147 100644
--- a/documentation/source/_themes/m21/static/copybutton.js
+++ b/documentation/source/_themes/m21/static/copybutton.js
@@ -40,38 +40,20 @@ document.addEventListener('DOMContentLoaded', () => {
         border_color = cs.borderTopColor;
     }
 
-    const button_styles = {
-        'cursor': 'pointer',
-        'position': 'absolute',
-        'top': '0',
-        'right': '0',
-        'border-color': border_color,
-        'border-style': border_style,
-        'border-width': border_width,
-        'color': border_color,
-        'text-size': '75%',
-        'font-family': 'monospace',
-        'padding-left': '0.2em',
-        'padding-right': '0.2em',
-        'border-radius': '0 3px 0 0'
-    };
-
     function apply_button_styles(button) {
-        // Keep the original style keys, but translate the ones that need JS-style names.
-        button.style.cursor = button_styles['cursor'];
-        button.style.position = button_styles['position'];
-        button.style.top = button_styles['top'];
-        button.style.right = button_styles['right'];
-        button.style.borderColor = button_styles['border-color'];
-        button.style.borderStyle = button_styles['border-style'];
-        button.style.borderWidth = button_styles['border-width'];
-        button.style.color = button_styles['color'];
-        // Old code had 'text-size' which is not a real CSS property; map it to font-size.
+        button.style.cursor = 'pointer';
+        button.style.position = 'absolute';
+        button.style.top = '0';
+        button.style.right = '0';
+        button.style.borderColor = border_color;
+        button.style.borderStyle = border_style;
+        button.style.borderWidth = border_width;
+        button.style.color = border_color;
         button.style.fontSize = '75%';
-        button.style.fontFamily = button_styles['font-family'];
-        button.style.paddingLeft = button_styles['padding-left'];
-        button.style.paddingRight = button_styles['padding-right'];
-        button.style.borderRadius = button_styles['border-radius'];
+        button.style.fontFamily = 'monospace';
+        button.style.paddingLeft = '0.2em';
+        button.style.paddingRight = '0.2em';
+        button.style.borderRadius = '0 3px 0 0';
     }
 
     function hide_elements(parent, selector) {
@@ -129,9 +111,11 @@ document.addEventListener('DOMContentLoaded', () => {
             const button = document.createElement('span');
             button.className = 'copy_button';
             button.textContent = '>>>';
+            button.setAttribute('role', 'button');
+            button.setAttribute('tabindex', '0');
             apply_button_styles(button);
             button.setAttribute('title', hide_text);
-            button.setAttribute('data-hidden', 'false');
+            button.setAttribute('aria-pressed', 'false');
             this_div.insertBefore(button, this_div.firstChild);
         }
 
@@ -159,7 +143,7 @@ document.addEventListener('DOMContentLoaded', () => {
             e.preventDefault();
             const parent = button.parentNode;
             const pre = next_pre_sibling(button);
-            if (button.getAttribute('data-hidden') === 'false') {
+            if (button.getAttribute('aria-pressed') === 'false') {
                 // hide the code output
                 hide_elements(parent, '.go, .gp, .gt');
                 if (pre) {
@@ -167,7 +151,7 @@ document.addEventListener('DOMContentLoaded', () => {
                 }
                 button.style.textDecoration = 'line-through';
                 button.setAttribute('title', show_text);
-                button.setAttribute('data-hidden', 'true');
+                button.setAttribute('aria-pressed', 'true');
             } else {
                 // show the code output
                 show_elements(parent, '.go, .gp, .gt');
@@ -177,7 +161,13 @@ document.addEventListener('DOMContentLoaded', () => {
                 }
                 button.style.textDecoration = 'none';
                 button.setAttribute('title', hide_text);
-                button.setAttribute('data-hidden', 'false');
+                button.setAttribute('aria-pressed', 'false');
+            }
+        });
+        button.addEventListener('keydown', e => {
+            if (e.key === 'Enter' || e.key === ' ') {
+                e.preventDefault();
+                button.click();
             }
         });
     }