diff --git a/app/assets/javascripts/wigu/table_crisscross.js b/app/assets/javascripts/wigu/table_crisscross.js
new file mode 100644
index 0000000..e885b4d
--- /dev/null
+++ b/app/assets/javascripts/wigu/table_crisscross.js
@@ -0,0 +1,49 @@
+// Crisscross highlighting for ActiveAdmin index tables. Clicking a cell marks
+// its whole row and column with class "selected", which the stylesheet paints
+// with var(--aa-selected) — so the clicked cell sits at the centre of a
+// highlighted cross, making it easy to trace a value to its row and column in a
+// wide table.
+//
+// Uses jQuery, which ActiveAdmin loads before this file (its own
+// checkbox-toggler is jQuery too); the rest of the theme JS is dependency-free,
+// but this behaviour lives entirely in AA's jQuery-driven index table.
+//
+// AA's table-checkbox-toggler selects the row when a cell is clicked; we undo
+// that (re-click the checkbox) so crisscross highlighting never leaves rows
+// ticked for batch actions.
+$(document).ready(function () {
+ var marker = 'selected';
+
+ // "Select all" clears every row marker left by AA's toggler.
+ $('table.index_table .toggle_all').click(function () {
+ $(this).closest('table').find('tr.' + marker).removeClass(marker);
+ });
+
+ // Clicking the row checkbox itself must not bubble into the cell handler.
+ $('table.index_table').find(':checkbox').click(function (e) {
+ e.stopPropagation();
+ $(this).closest('tr.' + marker).removeClass(marker);
+ });
+
+ $('table.index_table').on('click', 'td', function (e) {
+ var tdIndex = e.currentTarget.cellIndex,
+ $cell = $(e.currentTarget),
+ $table = $cell.closest('table'),
+ $row = $cell.closest('tr'),
+ $columnTds = $table.find('td:nth-child(' + (tdIndex + 1) + ')'),
+ $rowTds = $row.find('td');
+
+ // AA ticks the row checkbox on a cell click; click it again to revert.
+ $row.find(':checkbox').click();
+
+ if (!$cell.hasClass('col-selectable')) {
+ $table.find('td.' + marker).removeClass(marker);
+ $table.find('td.selected-cell').removeClass('selected-cell');
+ $columnTds.addClass(marker);
+ $rowTds.addClass(marker);
+ // Mark the clicked cell itself so the stylesheet can set it apart as the
+ // centre of the cross.
+ $cell.addClass('selected-cell');
+ }
+ });
+});
diff --git a/app/assets/stylesheets/wigu/active_admin_theme.scss b/app/assets/stylesheets/wigu/active_admin_theme.scss
index dd4761d..c0562e2 100644
--- a/app/assets/stylesheets/wigu/active_admin_theme.scss
+++ b/app/assets/stylesheets/wigu/active_admin_theme.scss
@@ -1774,11 +1774,22 @@ input[type='radio'] {
// Selected cells: the zebra and hover rules above outrank ActiveAdmin's own
// `tr.selected td` / `td.selected`, so without this a ticked row (batch
// actions) or a click-highlighted row + column (crisscross) looks unselected.
- // The :hover variants keep the selection visible under the pointer.
+ // The `tr >` in the td.selected selector is load-bearing: the even-stripe
+ // rule is `...tbody tr:nth-child(even) > td` (specificity 0,0,2,4); a plain
+ // `tbody td.selected` is only (0,0,2,3) and loses on even rows, breaking the
+ // cross every other row. Matching the `tr >` ties the specificity, so source
+ // order wins. The :hover variants keep the selection visible under the pointer.
table.index_table tbody tr.selected > td,
table.index_table tbody tr.selected:hover > td,
- table.index_table tbody td.selected,
+ table.index_table tbody tr > td.selected,
table.index_table tbody tr:hover > td.selected { background: var(--aa-selected); }
+ // The clicked cell sits at the centre of the cross; an accent outline marks
+ // it apart from the row/column tint without a competing fill (outset inward
+ // so it adds no layout shift).
+ table.index_table tbody tr > td.selected-cell {
+ outline: 2px solid var(--aa-accent);
+ outline-offset: -2px;
+ }
// Plain content tables (e.g. the CDR show "Attempts" table) are striped by the
// base CSS with light fills; map the odd/even rows onto the dark surfaces too.
table tbody tr.odd > td, table tbody tr.odd > th { background-color: var(--aa-surface); }
diff --git a/img/crisscross-dark.png b/img/crisscross-dark.png
new file mode 100644
index 0000000..59312fd
Binary files /dev/null and b/img/crisscross-dark.png differ
diff --git a/img/crisscross-light.png b/img/crisscross-light.png
new file mode 100644
index 0000000..7afdb53
Binary files /dev/null and b/img/crisscross-light.png differ
diff --git a/test/crisscross_screenshot.rb b/test/crisscross_screenshot.rb
new file mode 100644
index 0000000..0d5fd38
--- /dev/null
+++ b/test/crisscross_screenshot.rb
@@ -0,0 +1,23 @@
+# Regenerates img/crisscross-light.png / img/crisscross-dark.png — the crisscross
+# cell highlighting (wigu/table_crisscross.js) in both palettes. Clicks a middle
+# cell so the screenshot shows the highlighted row + column crossing at it, then
+# crops to the demo panel.
+require_relative "capybara_boot"
+require "capybara/dsl"
+
+include Capybara::DSL
+
+visit "/admin/table"
+# Click a cell away from the edges so the cross is clearly centred (row 3, Email).
+find("table.index_table tbody tr:nth-child(3) td:nth-child(3)").click
+page.driver.resize_window_to(page.driver.window_handles.first, 1120, 640)
+
+{ "light" => "crisscross-light.png", "dark" => "crisscross-dark.png" }.each do |theme, file|
+ page.execute_script(%(document.documentElement.setAttribute('data-theme', '#{theme}')))
+ sleep 0.3
+ out = File.expand_path("../img/#{file}", __dir__)
+ page.driver.browser.screenshot(path: out, selector: ".panel")
+ puts "wrote #{out} (#{File.size(out)} bytes)"
+end
+
+Capybara.reset_sessions!
diff --git a/test/dummy/app/admin/table.rb b/test/dummy/app/admin/table.rb
new file mode 100644
index 0000000..1ef7c62
--- /dev/null
+++ b/test/dummy/app/admin/table.rb
@@ -0,0 +1,29 @@
+# Demo page with a plain index table so the crisscross highlighting (wigu/
+# table_crisscross.js) can be exercised and screenshotted: clicking a cell marks
+# its whole row and column with .selected, which the theme paints.
+ActiveAdmin.register_page "Table" do
+ menu priority: 3
+
+ ROWS = [
+ %w[101 Ada\ Lovelace ada@example.com UK Active],
+ %w[102 Alan\ Turing alan@example.com UK Active],
+ %w[103 Grace\ Hopper grace@example.com US Closed],
+ %w[104 Katherine\ Johnson katherine@example.com US Active],
+ %w[105 Edsger\ Dijkstra edsger@example.com NL Closed],
+ %w[106 Donald\ Knuth donald@example.com US Active]
+ ].freeze
+
+ content do
+ panel "Crisscross highlighting (click a cell)" do
+ head = " "
+ body = ROWS.map do |r|
+ "ID Name Email " \
+ "Country Status