mobile: fix edge beam (transform-rotate) + ONU master-detail layout

Edge glow: iOS Safari won't animate an @property-driven conic-gradient
angle, so the beam sat static / off the border on mobile. Reworked to the
robust technique: a .beam child masked to the border ring contains an
oversized conic-gradient ::before spun with transform: rotate 0→360deg; the
glow halo is a drop-shadow on the parent (survives the child mask). Works on
iOS/Safari; honors prefers-reduced-motion.

ONU info on narrow screens: was stacking the picker above the detail, so
selecting an ONU meant a long scroll to find it. Now master-detail — show
the picker OR the detail (not both), toggled by .show-detail on
#view-onu-info via a shared @media(max-width:860px) rule; selecting shows the
detail with a "← ONU list" back button, tab/sub-tab nav resets to the
picker. (Earlier commit already de-compacted the list: extra filters +
two-line rows.)

Docs: CLAUDE.md §15 (edge glow technique) + §17 (master-detail).

Verified offscreen at 414px: beam element present; panel-left/​panel-main
display toggles correctly on select/back; back button visible on mobile;
rendered both states.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Jon Vanvik 2026-06-23 16:33:07 +02:00
parent 2046fad0f8
commit 6f4a315947
4 changed files with 81 additions and 31 deletions

View file

@ -261,6 +261,7 @@ for (const tab of $$('.tab')) {
showView(view);
// Dashboard loads itself on first visit; other tabs load on demand.
if (view === '#view-dashboard' && !state.dashboard) loadDashboard();
if (view === '#view-onu-info') $('#view-onu-info').classList.remove('show-detail');
});
}
@ -1857,10 +1858,22 @@ for (const st of $$('.subtab')) {
st.addEventListener('click', () => {
const view = st.dataset.subview;
showView(view);
if (view === '#view-onu-info' && state.fleet.length) renderOnuInfoList();
if (view === '#view-onu-info') {
$('#view-onu-info').classList.remove('show-detail'); // land on the picker
if (state.fleet.length) renderOnuInfoList();
}
});
}
// "Back to list" in the narrow master-detail layout (delegated — the detail
// re-renders several times).
$('#onu-info-detail').addEventListener('click', (e) => {
if (e.target.closest('.onu-detail-back')) {
e.preventDefault();
$('#view-onu-info').classList.remove('show-detail');
}
});
// -------- ONU info / stats page --------
$('#onu-info-load').addEventListener('click', async () => {
@ -1937,6 +1950,8 @@ function renderOnuInfoList() {
function selectOnuInfo(onuId) {
state.onuInfoSelected = onuId;
// Narrow layout: switch from the picker to the detail (master-detail).
$('#view-onu-info').classList.add('show-detail');
for (const row of $$('#onu-info-list .onu-info-row')) {
row.classList.toggle('selected', row.dataset.id === onuId);
}
@ -1994,7 +2009,7 @@ async function renderOnuDetail(onuId) {
const cfg = state.fleet.find((c) => c._id === onuId);
if (!cfg) { el.innerHTML = '<p class="muted">ONU not found in the loaded fleet — reload the fleet.</p>'; return; }
const head = `<h2>${escapeHtml(cfg._id)}</h2>`;
const head = `<button class="ghost onu-detail-back" type="button">← ONU list</button><h2>${escapeHtml(cfg._id)}</h2>`;
// Identity + link render instantly from the loaded fleet; the rest streams
// in from one getOnuDetail call.
el.innerHTML = `${head}<div class="detail-grid">