From 7ff21621d91008c4430d8f9929bbab5dcd28c362 Mon Sep 17 00:00:00 2001 From: librelad Date: Thu, 28 May 2026 14:19:03 +0100 Subject: [PATCH] ux(setup): dedicated dev icon + richer reveal for the dev-mode strip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Swap the strip's shared 🛠️ emoji for the inline "tool" SVG used by the topbar Developer-mode banner — a real, dedicated icon that ties the two dev-mode surfaces together and no longer doubles the Advanced card's glyph. Enrich the entrance: the box grows in and settles, a one-shot accent glow pulses for the "unlocked" beat, a subtle shine sweeps across, the icon pops with a slight overshoot/wiggle, and the text slides in just behind it. All gated behind prefers-reduced-motion. Signed-off-by: librelad --- .../libreportal/frontend/css/setup-wizard.css | 60 +++++++++++++++++-- .../frontend/js/system/setup-wizard.js | 6 +- 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/containers/libreportal/frontend/css/setup-wizard.css b/containers/libreportal/frontend/css/setup-wizard.css index ec9fb96..6039270 100755 --- a/containers/libreportal/frontend/css/setup-wizard.css +++ b/containers/libreportal/frontend/css/setup-wizard.css @@ -1122,6 +1122,7 @@ body.setup-wizard-open { from the parent flex column) until JS clears the attribute, at which point the entrance keyframes play. */ .setup-dev-strip { + position: relative; display: flex; align-items: center; gap: 12px; @@ -1131,19 +1132,68 @@ body.setup-wizard-open { border: 1px solid rgba(var(--accent-rgb), 0.5); box-shadow: 0 6px 22px rgba(var(--accent-rgb), 0.16); overflow: hidden; - animation: swDevStripIn .4s cubic-bezier(.2, .7, .3, 1) both; + /* Grow-in settles the box; the glow pulse gives the "unlocked!" beat. */ + animation: + swDevStripIn .45s cubic-bezier(.2, .8, .25, 1) both, + swDevStripGlow 1.1s ease-out .12s both; } /* Author display:flex above outranks the UA [hidden] rule, so re-hide. */ .setup-dev-strip[hidden] { display: none; } -.setup-dev-strip-icon { font-size: 1.5rem; line-height: 1; } + +/* One-shot shine sweep across the strip on reveal. */ +.setup-dev-strip::after { + content: ""; + position: absolute; + inset: 0; + background: linear-gradient(115deg, transparent 35%, rgba(255, 255, 255, 0.18) 50%, transparent 65%); + transform: translateX(-120%); + pointer-events: none; +} +.setup-dev-strip:not([hidden])::after { animation: swDevShine .9s ease .28s both; } + +.setup-dev-strip-icon { + display: inline-flex; + color: rgb(var(--accent-rgb)); + flex: 0 0 auto; +} +.setup-dev-strip-icon svg { width: 22px; height: 22px; display: block; } +.setup-dev-strip:not([hidden]) .setup-dev-strip-icon { + animation: swDevIconPop .55s cubic-bezier(.2, 1.4, .4, 1) .12s both; +} + .setup-dev-strip-text { display: flex; flex-direction: column; gap: 2px; } .setup-dev-strip-text strong { font-size: 0.95rem; font-weight: 700; color: var(--text-primary); } .setup-dev-strip-text span { font-size: 0.8rem; color: rgba(var(--text-rgb), 0.65); } +.setup-dev-strip:not([hidden]) .setup-dev-strip-text { + animation: swDevTextIn .4s ease .18s both; +} @keyframes swDevStripIn { - from { opacity: 0; transform: translateY(-6px); max-height: 0; padding-top: 0; padding-bottom: 0; } - to { opacity: 1; transform: translateY(0); max-height: 160px; padding-top: 12px; padding-bottom: 12px; } + from { opacity: 0; transform: translateY(-8px) scale(.98); max-height: 0; padding-top: 0; padding-bottom: 0; } + 60% { opacity: 1; } + to { opacity: 1; transform: translateY(0) scale(1); max-height: 160px; padding-top: 12px; padding-bottom: 12px; } +} +@keyframes swDevStripGlow { + 0% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0); } + 35% { box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.22), 0 8px 26px rgba(var(--accent-rgb), 0.30); } + 100% { box-shadow: 0 6px 22px rgba(var(--accent-rgb), 0.16); } +} +@keyframes swDevShine { + from { transform: translateX(-120%); } + to { transform: translateX(120%); } +} +@keyframes swDevIconPop { + 0% { transform: scale(0) rotate(-25deg); opacity: 0; } + 60% { transform: scale(1.15) rotate(8deg); opacity: 1; } + 100% { transform: scale(1) rotate(0); opacity: 1; } +} +@keyframes swDevTextIn { + from { opacity: 0; transform: translateX(-6px); } + to { opacity: 1; transform: translateX(0); } } @media (prefers-reduced-motion: reduce) { - .setup-dev-strip { animation-duration: .01ms; } + .setup-dev-strip, + .setup-dev-strip::after, + .setup-dev-strip .setup-dev-strip-icon, + .setup-dev-strip .setup-dev-strip-text { animation-duration: .01ms; } } diff --git a/containers/libreportal/frontend/js/system/setup-wizard.js b/containers/libreportal/frontend/js/system/setup-wizard.js index cbc7716..6e67729 100755 --- a/containers/libreportal/frontend/js/system/setup-wizard.js +++ b/containers/libreportal/frontend/js/system/setup-wizard.js @@ -120,7 +120,11 @@ class SetupWizard {