diff --git a/zddc/internal/apps/embedded/archive.html b/zddc/internal/apps/embedded/archive.html index 0a7cced..0ff3b08 100644 --- a/zddc/internal/apps/embedded/archive.html +++ b/zddc/internal/apps/embedded/archive.html @@ -269,7 +269,7 @@ a:hover { } /* Subdued / de-emphasized variant. - Used on the "Add Local Directory" button when a tool is operating + Used on the "Use Local Directory" button when a tool is operating in server (online) mode — the local-dir affordance is still available but visually quieter, since the typical user already has the directory loaded from the server. */ @@ -331,6 +331,11 @@ a:hover { background: var(--bg-secondary); border-bottom: 1px solid var(--border); flex-shrink: 0; + /* Let the left / right groups wrap to a second row at narrow + viewports rather than overflowing the viewport edge. row-gap + gives a small breathing strip when wrapped. */ + flex-wrap: wrap; + row-gap: 0.3rem; } /* Left and right groups inside .app-header. Both flex-row so their @@ -342,16 +347,35 @@ a:hover { display: flex; align-items: center; gap: 0.75rem; + /* Allow the title to shrink (and ellipsize) before the action + buttons get pushed off-screen at narrow viewports. */ + min-width: 0; + flex-wrap: wrap; + row-gap: 0.3rem; } .header-right { display: flex; align-items: center; gap: 0.5rem; + flex-shrink: 0; +} + +/* Title group (title + build label). Made shrinkable so narrow + viewports don't push the action buttons out of view; the title + itself ellipsizes via the rule below. */ +.header-title-group { + display: flex; + align-items: baseline; + gap: 0.5rem; + min-width: 0; + flex-shrink: 1; } /* Tool name inside the header. Renders in the display serif so the - tool's identity reads as a document title, not a UI label. */ + tool's identity reads as a document title, not a UI label. + overflow + ellipsis on min-width:0 lets the title compress + gracefully when there's no room. */ .app-header__title { font-family: var(--font-display); font-size: 18px; @@ -359,6 +383,9 @@ a:hover { color: var(--text); letter-spacing: 0; white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; } /* Brand logo — sits left of the title in every tool's app-header. @@ -809,61 +836,127 @@ body.help-open .app-header { to { transform: translateX(100%); opacity: 0; } } -/* shared/nav.css — lateral project-stage strip paired with shared/nav.js. - Sits as a sibling immediately under .app-header (mounted by JS). - Rendered only in online mode when a project segment is in the URL. */ +/* shared/elevation.css — admin-elevation toggle in the tool header. + Renders only for users with admin scope (handled by elevation.js; + the placeholder is `.hidden` by default). When visible, sits left + of the theme button — sudo-style affordance for opting into admin + powers. */ -.zddc-stage-strip { - display: flex; +.elevation-toggle { + display: inline-flex; align-items: center; - gap: 0.5rem; - padding: 0.3rem 1rem; - background: var(--bg); - border-bottom: 1px solid var(--border); - font-size: 0.8rem; - line-height: 1.3; - flex-shrink: 0; - overflow-x: auto; - white-space: nowrap; -} - -.zddc-stage-strip__project { - color: var(--text); - font-weight: 600; - margin-right: 0.15rem; -} - -.zddc-stage-strip__divider, -.zddc-stage-strip__sep { + gap: 0.3rem; + font-size: 0.78rem; color: var(--text-muted); user-select: none; -} - -.zddc-stage-strip__divider { - margin-right: 0.35rem; -} - -.zddc-stage { - color: var(--text-muted); - text-decoration: none; - padding: 0.1rem 0.25rem; + cursor: pointer; + padding: 0.15rem 0.45rem; + border: 1px solid var(--border); border-radius: var(--radius); - transition: color 0.15s, background 0.15s; + background: var(--bg); + transition: background 0.12s, border-color 0.12s, color 0.12s; } -.zddc-stage:hover { - color: var(--text); - background: var(--bg-secondary); - text-decoration: none; +.elevation-toggle:hover { + background: var(--bg-hover); + border-color: var(--border-dark); } -.zddc-stage--active { - color: var(--primary); +.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; } -.zddc-stage--active:hover { - color: var(--primary); +/* Page-wide chrome when admin mode is active. The toggle alone is + easy to miss; these add an inescapable visual cue: + 1. Thin red border around the entire viewport — peripheral- + vision reminder regardless of which tool / scroll position. + 2. Sticky banner across the top with a one-click "Drop admin" + button so the user can disarm without hunting for the toggle. + Both rendered ONLY when the zddc-elevate cookie is set; the + shared/elevation.js init() syncs the body class on every page + load and tears it down when elevation is cleared. + + Frame uses fixed positioning + pointer-events:none so it doesn't + reflow content or steal clicks. An inset outline on
was + tried first but overdrew content in tools whose root layout butts + right up to the viewport edge (browse split-pane, archive grid). */ +body.is-elevated::after { + content: ""; + position: fixed; + inset: 0; + border: 3px solid var(--danger, #dc3545); + pointer-events: none; + z-index: 9200; /* above banner (9100) so the frame paints on top */ +} + +.elevation-banner { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.4rem 0.9rem; + background: rgba(220, 53, 69, 0.95); + color: #fff; + font-size: 0.85rem; + font-weight: 500; + letter-spacing: 0.01em; + position: sticky; + top: 0; + z-index: 9100; /* above modal-overlay (9000) so it's never hidden */ + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.18); +} + +.elevation-banner__dot { + width: 0.5rem; + height: 0.5rem; + background: #fff; + border-radius: 50%; + box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7); + animation: elev-pulse 1.6s infinite; + flex-shrink: 0; +} + +@keyframes elev-pulse { + 0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7); } + 70% { box-shadow: 0 0 0 8px rgba(255, 255, 255, 0); } + 100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); } +} + +.elevation-banner__msg { + flex: 1 1 auto; +} + +.elevation-banner__off { + background: rgba(255, 255, 255, 0.18); + border: 1px solid rgba(255, 255, 255, 0.7); + color: #fff; + padding: 0.18rem 0.65rem; + border-radius: var(--radius, 4px); + font-size: 0.78rem; + font-weight: 600; + letter-spacing: 0.02em; + cursor: pointer; + flex-shrink: 0; +} +.elevation-banner__off:hover { + background: rgba(255, 255, 255, 0.3); } /* shared/logo.css — paired with shared/logo.js. The wrapping anchor @@ -2470,13 +2563,19 @@ td[data-field="trackingNumber"] {