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

+116 -22
View File
@@ -34,6 +34,26 @@
}
}
/* ---------- wrap-aware separators (JS-toggled, see main.js) ----------
Shared hook used by the announcement banner and hero tagline row: a
"·" between two pieces of text that can independently wrap onto
separate lines. No CSS selector exists for "am I on the same line as
my sibling," so main.js measures and toggles this class instead of a
fixed breakpoint. Visible by default so a no-JS visitor always sees
the dot (today's behavior), never hidden by default.
display, not visibility: a hidden separator must take no space, or
it still gets counted when justify-content: center balances its flex
line — a lone badge sharing its line with a space-reserving invisible
dot renders off-center. Safe from the "text merges together" bug that
originally motivated reserving space: both wrap-sep contexts space
their siblings with a real flex `gap` (see .announcement-inner,
.tagline-row) rather than the separator's own margin, so removing it
from flow still leaves normal spacing behind, never a collision. */
.wrap-sep-hidden {
display: none;
}
body {
margin: 0;
background: var(--ground);
@@ -478,17 +498,28 @@
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 0.6rem 0;
/* column-gap carries the spacing now (was 0 — the dot's own margin
used to be the only thing separating the phrases). Needs to be a
real flex gap, not a margin on the dot, so removing a hidden dot
from flow (see .wrap-sep-hidden) still leaves normal spacing
between the two phrases instead of running them together. */
gap: 0.6rem 0.7rem;
font-family: var(--font-mono);
font-size: 0.82rem;
letter-spacing: 0.04em;
color: var(--ink-muted);
}
.tagline-row span:not(:last-child)::after {
content: "·";
/* Real element instead of a ::after pseudo-element — needs to be a
measurable, independently-toggleable DOM node for the wrap-aware
script in main.js to hide it when its neighbors wrap apart. No margin
of its own: the column-gap above already provides 0.7rem on each
side of it (matching the old margin-inline's total spacing exactly),
and still applies directly between the two phrases whenever the dot
itself is removed from flow. */
.tag-sep {
color: var(--gold-dim);
margin-inline: 0.7rem;
user-select: none;
}
.hero-ctas {
@@ -502,6 +533,11 @@
.btn {
display: inline-flex;
align-items: center;
/* Centers label text horizontally too — matters once a button is
stretched wider than its content (e.g. action-stack children filling
a fixed-width column) or wraps to multiple lines; a no-op otherwise,
so every button gets this instead of one-off overrides per case. */
justify-content: center;
gap: 0.5rem;
padding: 0.8rem 1.5rem;
font-family: var(--font-mono);
@@ -1330,6 +1366,14 @@
width: 7.25rem;
}
/* "○ Disabled" is the longer of the two user labels. Not scoped to
button.status-badge like the two above — your own row's badge is a
plain non-interactive span (see users.php), and it still needs to line
up in the same column. */
.user-row .status-badge {
width: 7rem;
}
/* Admin row actions: the button-group styling shared by every admin
list (menu items/categories, announcements, users). The actual
row/column layout for each list is its own CSS Grid, defined where
@@ -1576,7 +1620,7 @@
}
.user-grid-wrap {
grid-template-columns: minmax(0, 1fr) auto auto auto;
grid-template-columns: auto minmax(0, 1fr) auto auto auto;
}
.item-grid-wrap,
@@ -1640,12 +1684,13 @@
}
.user-grid-head, .user-row {
grid-template-areas: "username role created actions";
grid-template-areas: "status username role created actions";
}
.col-status,
.item-row [data-label="Status"],
.ann-row [data-label="Status"] {
.ann-row [data-label="Status"],
.user-row [data-label="Status"] {
grid-area: status;
}
@@ -1726,10 +1771,37 @@
columns: subgrid` from the desktop rule, so this one change is all
it takes to reflow the whole list (see grid-template-areas per row
further down for how each field then stacks within that column). */
/* Every list's cards get a second column on mobile so Actions can sit
in the top-right corner alongside that list's leading field, instead
of stacking at the very bottom of the card — overrides the shared
single-column rule that would otherwise apply here too.
minmax(0, auto), not plain auto, so this column can still shrink
below its content's natural width under pressure instead of forcing
the whole row to overflow the card. */
.item-grid-wrap,
.ann-grid-wrap,
.user-grid-wrap {
grid-template-columns: 1fr;
grid-template-columns: 1fr minmax(0, auto);
}
/* Users' actions column gets a fixed cap instead of auto: "Change
Password" is long enough that its max-content size (what auto would
size the column to) is wider than this corner has room for, which
pushed the button past the card edge. A firm cap plus the
wrap/stretch rules below keep it predictable instead. */
.user-grid-wrap {
grid-template-columns: minmax(0, 1fr) 6rem;
}
/* Every row's Actions cell holds one or two .action-stacks (move
up/down, edit/delete, etc. — each stacked vertically, see
.action-stack itself further up) side by side. Keeps those stacks
from wrapping onto their own lines in this narrow corner, which is
what the shared .row-actions wrap rule below would otherwise do. */
.item-row .row-actions,
.ann-row .row-actions,
.user-row .row-actions {
flex-wrap: nowrap;
}
.item-grid-head,
@@ -1770,28 +1842,50 @@
.item-row {
grid-template-areas:
"status"
"item"
"price"
"actions";
"status actions"
"item item"
"price price";
/* Overrides the shared align-items: center above — on desktop that
vertically centers Status against the taller Item cell in one wide
row, which is fine, but here it also centers Status against the
taller Actions block above, pushing its badge down instead of
keeping both flush with the card's top edge. */
align-items: start;
}
.ann-row {
grid-template-areas:
"status"
"headline"
"type"
"schedule"
"placements"
"actions";
"status actions"
"headline headline"
"type type"
"schedule schedule"
"placements placements";
align-items: start;
}
.user-row {
grid-template-areas:
"username"
"role"
"created"
"actions";
"status actions"
"username username"
"role role"
"created created";
align-items: start;
}
/* Stretches Actions' whole flex chain out to the fixed 6rem column
above (each is shrink-wrapped by default), then lets "Change
Password" — long enough on its own to blow out this corner next to a
username — wrap onto two lines within that width instead of forcing
the column wider. Every other action-stack button here (Edit,
Delete, ↑/↓) is short enough to stay on one line regardless. */
.user-row .row-actions,
.user-row .action-stack,
.user-row .row-actions .btn {
width: 100%;
}
.user-row .btn-change-password {
white-space: normal;
}
.category-panel .panel-head {