feat add info toggle with explainer to probe cards

Each probe card now has an ⓘ button that reveals a collapsible explanation of the measurement: what it means, optimal range, and what affects it. Same WCAG-compliant pattern as insights. Explainers per probe: - pH: acidity, nitrification/photosynthesis effects - DO: oxygen levels, night/temperature dependencies - ORP: water cleanliness, correlation with DO - EC: nutrient concentration, water change effects

Commit
a9ed1fe3eeb935b00b3d6b1f7aa02fc38a101719
Author
GPT-5 medium <codex@openai.com>
Author date
Committer
GPT-5 medium <codex@openai.com>
Committer date
roles/dashboard/templates/dashboard/probe-card.html.ep
index af13500f..357460a4 100644..100644
@@ -1,3 +1,10 @@
1 Added: % my %explainers = (
2 Added: % ph => 'Measures acidity/alkalinity of the water. Optimal range for aquaponics is 6.4–7.2 pH. Affected by nitrification (lowers pH) and photosynthesis (raises pH).',
3 Added: % do => 'Dissolved oxygen available to fish and bacteria. Should stay above 5.5 mg/L. Drops at night (no photosynthesis) and in warm water (lower O₂ solubility).',
4 Added: % orp => 'Oxidation-Reduction Potential indicates water cleanliness. 250–400 mV is healthy. Low ORP suggests organic buildup; correlates with DO.',
5 Added: % ec => 'Electrical conductivity reflects dissolved nutrient concentration. 300–1500 µS/cm is typical. Rising EC means nutrient accumulation; drops after water changes.',
6 Added: % );
7 Added: % my $info_id = "info-probe-$probe->{key}";
1 8 <div class="card">
2 9 <div class="card-header d-flex justify-content-between align-items-start gap-2">
3 10 <div class="probe-card-title min-w-0">
@@ -6,29 +13,48 @@
6 13 </h1>
7 14 <p class="small mb-0 mt-1" data-variation="<%= $probe->{key} %>"></p>
8 15 </div>
9 Removed: <div class="dropdown flex-shrink-0">
10 Removed: <button
11 Removed: class="btn btn-sm btn-outline-secondary dropdown-toggle"
12 Removed: type="button"
13 Removed: data-bs-toggle="dropdown"
14 Removed: aria-expanded="false"
15 Removed: aria-label="Actions for <%= $probe->{label} %>">
16 Added: <div class="d-flex align-items-center gap-2 flex-shrink-0">
17 Added: <button class="btn btn-sm btn-link text-body-secondary p-0 info-toggle"
18 Added: type="button"
19 Added: data-bs-toggle="collapse"
20 Added: data-bs-target="#<%= $info_id %>"
21 Added: aria-expanded="false"
22 Added: aria-controls="<%= $info_id %>"
23 Added: aria-label="About this probe">
24 Added: <svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
25 Added: <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
26 Added: <path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0"/>
27 Added: </svg>
16 28 </button>
17 Removed: <ul class="dropdown-menu dropdown-menu-end">
18 Removed: <li>
19 Removed: <a
20 Removed: class="dropdown-item graph-download-button"
21 Removed: href="/downloads/readings.csv?probe=<%= $probe->{key} %>&amp;timeframe=day&amp;range=1"
22 Removed: data-raw-download
23 Removed: data-probe="<%= $probe->{key} %>">Download raw data</a>
24 Removed: </li>
25 Removed: </ul>
29 Added: <div class="dropdown">
30 Added: <button
31 Added: class="btn btn-sm btn-outline-secondary dropdown-toggle"
32 Added: type="button"
33 Added: data-bs-toggle="dropdown"
34 Added: aria-expanded="false"
35 Added: aria-label="Actions for <%= $probe->{label} %>">
36 Added: </button>
37 Added: <ul class="dropdown-menu dropdown-menu-end">
38 Added: <li>
39 Added: <a
40 Added: class="dropdown-item graph-download-button"
41 Added: href="/downloads/readings.csv?probe=<%= $probe->{key} %>&amp;timeframe=day&amp;range=1"
42 Added: data-raw-download
43 Added: data-probe="<%= $probe->{key} %>">Download raw data</a>
44 Added: </li>
45 Added: </ul>
46 Added: </div>
26 47 </div>
27 48 </div>
49 Added: <div class="collapse" id="<%= $info_id %>">
50 Added: <div class="card-body border-bottom py-2">
51 Added: <p class="text-body-secondary small mb-0"><%= $explainers{$probe->{key}} // 'Trend for this measurement over time.' %></p>
52 Added: </div>
53 Added: </div>
28 54 <div class="card-body">
29 55 <section id="probe-<%= $probe->{key} %>" class="tab-panel is-active" data-panel="<%= $probe->{key} %>">
30 56 <div class="chart-wrap">
31 Removed: <canvas id="chart-<%= $probe->{key} %>" role="img" aria-label="<%= $probe->{label} %> readings chart"></canvas>
57 Added: <canvas id="chart-<%= $probe->{key} %>" role="img" aria-label="<%= $probe->{label} %> trend over time"></canvas>
32 58 </div>
33 59 % unless (stash('single_probe')) {
34 60 <p class="text-body-secondary small mt-2 mb-0 reading-status placeholder-glow" data-status="<%= $probe->{key} %>">