diff --git a/archive/build.sh b/archive/build.sh index 266d19f..4c0cf1a 100755 --- a/archive/build.sh +++ b/archive/build.sh @@ -23,6 +23,7 @@ concat_files \ "../shared/base.css" \ "../shared/toast.css" \ "../shared/elevation.css" \ + "../shared/profile-menu.css" \ "../shared/logo.css" \ "css/base.css" \ "css/layout.css" \ @@ -64,6 +65,7 @@ concat_files \ "js/app.js" \ "../shared/help.js" \ "../shared/elevation.js" \ + "../shared/profile-menu.js" \ "../shared/cap.js" \ > "$js_raw" diff --git a/archive/template.html b/archive/template.html index 5711b47..19cf886 100644 --- a/archive/template.html +++ b/archive/template.html @@ -36,12 +36,6 @@
- -
diff --git a/browse/build.sh b/browse/build.sh index 41a3536..2a99797 100755 --- a/browse/build.sh +++ b/browse/build.sh @@ -29,6 +29,7 @@ concat_files \ "../shared/vendor/codemirror-yaml.min.css" \ "../shared/context-menu.css" \ "../shared/elevation.css" \ + "../shared/profile-menu.css" \ "css/base.css" \ "css/tree.css" \ "css/preview-yaml.css" \ @@ -59,6 +60,7 @@ concat_files \ "../shared/preview-lib.js" \ "../shared/context-menu.js" \ "../shared/elevation.js" \ + "../shared/profile-menu.js" \ "../shared/cap.js" \ "../shared/icons.js" \ "../shared/zddc-source.js" \ diff --git a/browse/template.html b/browse/template.html index 5abb1e7..1a13b14 100644 --- a/browse/template.html +++ b/browse/template.html @@ -28,12 +28,6 @@
- -
diff --git a/classifier/build.sh b/classifier/build.sh index 14b41c1..27bf0aa 100755 --- a/classifier/build.sh +++ b/classifier/build.sh @@ -23,6 +23,7 @@ concat_files \ "../shared/base.css" \ "../shared/toast.css" \ "../shared/elevation.css" \ + "../shared/profile-menu.css" \ "../shared/logo.css" \ "css/base.css" \ "css/layout.css" \ @@ -62,6 +63,7 @@ concat_files \ "js/excel.js" \ "../shared/help.js" \ "../shared/elevation.js" \ + "../shared/profile-menu.js" \ "../shared/cap.js" \ > "$js_raw" diff --git a/classifier/template.html b/classifier/template.html index ab15a9c..5991e0b 100644 --- a/classifier/template.html +++ b/classifier/template.html @@ -32,12 +32,6 @@
- -
diff --git a/form/build.sh b/form/build.sh index 4dcab94..b373844 100755 --- a/form/build.sh +++ b/form/build.sh @@ -22,6 +22,7 @@ concat_files \ "../shared/base.css" \ "../shared/toast.css" \ "../shared/elevation.css" \ + "../shared/profile-menu.css" \ "../shared/logo.css" \ "css/form.css" \ > "$css_temp" @@ -32,6 +33,7 @@ concat_files \ "../shared/logo.js" \ "../shared/help.js" \ "../shared/elevation.js" \ + "../shared/profile-menu.js" \ "../shared/cap.js" \ "js/app.js" \ "js/context.js" \ diff --git a/form/template.html b/form/template.html index 5dda43b..e5b167f 100644 --- a/form/template.html +++ b/form/template.html @@ -26,12 +26,6 @@
- -
diff --git a/landing/build.sh b/landing/build.sh index 72d32f7..d29803a 100755 --- a/landing/build.sh +++ b/landing/build.sh @@ -22,6 +22,7 @@ concat_files \ "../shared/base.css" \ "../shared/toast.css" \ "../shared/elevation.css" \ + "../shared/profile-menu.css" \ "../shared/logo.css" \ "css/landing.css" \ > "$css_temp" @@ -34,6 +35,7 @@ concat_files \ "../shared/logo.js" \ "../shared/help.js" \ "../shared/elevation.js" \ + "../shared/profile-menu.js" \ "../shared/cap.js" \ "js/landing.js" \ > "$js_raw" diff --git a/landing/template.html b/landing/template.html index 818e2b3..2e9618d 100644 --- a/landing/template.html +++ b/landing/template.html @@ -26,12 +26,6 @@
- -
diff --git a/shared/elevation.css b/shared/elevation.css index 0ef67ec..2f57126 100644 --- a/shared/elevation.css +++ b/shared/elevation.css @@ -1,56 +1,7 @@ -/* shared/elevation.css — on-page admin-elevation toggle. - elevation.js appends this control to ONLY for users the - server says can_elevate (sudo-style opt-in). It's a fixed bottom- - right switch so it works in any tool without a header slot and - stays clear of the top "admin mode is on" banner. Arming is - per-page: the cookie is session-scoped and cleared on pagehide. */ - -.elevation-toggle { - position: fixed; - right: 0.9rem; - bottom: 0.9rem; - z-index: 9300; /* above the is-elevated frame (9200) so it stays clickable */ - display: inline-flex; - align-items: center; - gap: 0.3rem; - font-size: 0.78rem; - color: var(--text-muted); - user-select: none; - cursor: pointer; - padding: 0.2rem 0.5rem; - border: 1px solid var(--border); - border-radius: var(--radius); - background: var(--bg); - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.14); - transition: background 0.12s, border-color 0.12s, color 0.12s; -} - -.elevation-toggle:hover { - background: var(--bg-hover); - border-color: var(--border-dark); -} - -.elevation-toggle input[type="checkbox"] { - margin: 0; - cursor: pointer; - accent-color: var(--danger); -} - -.elevation-toggle__label { - cursor: pointer; - letter-spacing: 0.02em; -} - -/* Active state — when elevation is ON, the toggle reads as "armed" - so the user can't miss that admin powers are currently live. - :has(:checked) lets us style the wrapper based on the inner - checkbox without JS. */ -.elevation-toggle:has(input:checked) { - background: rgba(220, 53, 69, 0.12); - border-color: var(--danger); - color: var(--danger); - font-weight: 600; -} +/* shared/elevation.css — page-wide armed chrome for admin mode. + The elevate CONTROL is the "Admin mode" item in the shared profile menu + (shared/profile-menu.{js,css}); this file only styles the unmistakable + "you are elevated" cues: the red viewport frame + the sticky banner. */ /* Page-wide chrome when admin mode is active. The toggle alone is easy to miss; these add an inescapable visual cue: diff --git a/shared/elevation.js b/shared/elevation.js index ff6a442..a29f35a 100644 --- a/shared/elevation.js +++ b/shared/elevation.js @@ -1,17 +1,18 @@ -// shared/elevation.js — admin elevation via an on-page toggle. +// shared/elevation.js — admin elevation state machine. // // Sudo-style model: admins behave as normal users by default; elevating -// the session turns on admin escape hatches (WORM bypass, .zddc edit -// authority, profile admin scaffolds). State is carried in a -// `zddc-elevate=1` cookie that the server reads via handler.ACLMiddleware -// → zddc.Principal{Elevated}. +// the session turns on admin escape hatches (WORM bypass, recursive +// delete, rearranging records, profile admin scaffolds — NOT config-edit, +// which is standing). State is carried in a `zddc-elevate=1` cookie that +// the server reads via handler.ACLMiddleware → zddc.Principal{Elevated}. // -// Two ways to arm, both gated on /.profile/access `can_elevate` so only -// real admins can flip it (a non-admin's attempt is a silent no-op): -// 1. The on-page toggle (renderToggle) — a small fixed control that we -// render ONLY for users who can_elevate. This is the primary path. -// 2. `?admin=true|false` typed into any URL — still honoured (handy for -// deep links / scripting), just normalised into the same state. +// This module owns the STATE (cookie, armed chrome/banner, ephemeral +// lifecycle, the change event) + exposes setOn/setOff/isElevated. The +// on-page elevate CONTROL lives in the shared profile menu +// (shared/profile-menu.js) — an "Admin mode" item shown only to +// can_elevate users — which calls setOn/setOff. `?admin=true|false` typed +// into any URL is also honoured (gated on can_elevate), for deep links / +// scripting. // // Admin mode is EPHEMERAL — scoped to the page you turned it on: // * the cookie is a SESSION cookie (no Max-Age), and @@ -63,19 +64,19 @@ } catch (_e) { /* CustomEvent unsupported — non-fatal */ } } - // setOn / setOff are the single funnel for every arm/drop path (toggle, - // URL param, banner button). Each flips the cookie, re-paints the armed - // chrome, syncs the toggle checkbox, and emits the change — no reload. + // setOn / setOff are the single funnel for every arm/drop path (the + // profile menu's Admin mode item, the ?admin= URL param, the banner's + // Drop button). Each flips the cookie, re-paints the armed chrome, and + // emits the change — no reload. The profile menu listens for the change + // event to keep its checkbox + armed indicator in sync. function setOn() { setElevated(true); applyArmedChrome(true); - syncToggle(); emitChange(); } function setOff() { setElevated(false); applyArmedChrome(false); - syncToggle(); emitChange(); } @@ -181,75 +182,30 @@ } } - // renderToggle mounts the on-page admin switch — but ONLY for users the - // server says can_elevate (i.e. who'd actually gain edit authority by - // arming). Everyone else never sees it. It's a fixed control so it works - // in any tool without that tool rendering a header slot for it. - function renderToggle() { - // Reuse the header placeholder the tool templates ship (an empty - // `