Add menu enable toggle, user status, and mobile/UI polish

- Admin menu page: mobile item cards now put Actions in the top-right
  corner next to Status, matching the desktop row layout instead of
  stacking at the bottom; same treatment applied to announcements and
  users for consistency across all three admin lists
- Center button label text everywhere via .btn { justify-content: center }
  instead of one-off overrides
- Add a Disable/Enable Menu toggle to the admin menu toolbar; the public
  menu page shows its existing "menu being updated" message while disabled
- Add an Enabled/Disabled status to user accounts, with its own toggle
  badge, guardrails against disabling your own account or the last
  enabled account, and an immediate session kick for anyone disabled
  mid-session
- Fix the announcement banner / hero tagline "·" separators to hide only
  when their neighboring text actually wraps onto a different line,
  instead of a fixed breakpoint or always-on dot

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
David DyerandClaude Sonnet 5 committed 2026-09-01 14:10:08 -04:00
1 parent eab20899de
commit cd4c6c1969
13 files changed
+405 -50

No files matched your search

+53
View File
@@ -64,3 +64,56 @@
closeNav();
});
})();
// Wrap-aware separator dots (announcement banner, hero tagline row).
// Hides a "·" only when its two flanking elements wrap onto different
// visual lines. There's no CSS way to detect "same line as sibling," and
// a fixed viewport breakpoint can't substitute for it either, since
// wrapping depends on the announcement's own text length, not just
// screen width.
(() => {
const seps = document.querySelectorAll('.wrap-sep');
if (!seps.length) return;
function sameLine(a, b) {
const ra = a.getBoundingClientRect();
const rb = b.getBoundingClientRect();
return ra.top < rb.bottom && rb.top < ra.bottom;
}
// Hiding a separator removes it from flow (see .wrap-sep-hidden in
// styles.css — needs to, so a hidden dot never skews centering by still
// reserving its space), which means one separator's hide/show can shift
// where a *later* one's neighbors land — e.g. the tagline row's second
// dot needs to see the first dot's decision already applied, or it may
// judge its own neighbors' line-sharing from a layout that's since
// changed underneath it. So this deliberately processes separators one
// at a time, in DOM order, applying each decision before measuring the
// next: reset just this one to visible (so its own contribution is
// never argued away by whatever its own current hidden state already
// happens to be — otherwise "hidden" would be self-confirming, since a
// hidden dot's neighbors always look like they share a line), measure,
// decide, apply, then move on with that change already in effect.
function sync() {
seps.forEach((sep) => {
const prev = sep.previousElementSibling;
const next = sep.nextElementSibling;
if (!prev || !next) return;
sep.classList.remove('wrap-sep-hidden');
sep.classList.toggle('wrap-sep-hidden', !sameLine(prev, next));
});
}
sync();
let resizeTimer;
window.addEventListener('resize', () => {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(sync, 100);
});
if (document.fonts && document.fonts.ready) {
document.fonts.ready.then(sync);
}
})();