From c3132f327904178f32593d272e3534919644117b Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Sat, 22 Aug 2026 18:23:57 +0200 Subject: [PATCH 1/3] fix: soften the first-run wizard backdrop NcModal's opaque backdrop (used on desktop via `dark`) is ~92% black, which dims the whole page quite heavily behind the first-run wizard. Ease it to ~75% so the modal still lifts off the page without darkening everything behind it. Targets `.modal-mask--opaque` (the current NcModal class) with `.modal-mask--dark` kept for older versions. Co-Authored-By: Claude Opus 4.8 --- src/views/App.vue | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/views/App.vue b/src/views/App.vue index 021cd2ee8..c9bfe8bd1 100644 --- a/src/views/App.vue +++ b/src/views/App.vue @@ -91,6 +91,17 @@ function close() { } } +/** + * Soften the backdrop. NcModal's opaque/dark backdrop is ~92% black, which dims + * the whole page quite heavily behind the wizard; ease it to a gentler tint that + * still lifts the modal off the page. (`--opaque` is the current class; `--dark` + * is kept for older NcModal versions.) + */ +.first-run-wizard.modal-mask--opaque, +.first-run-wizard.modal-mask--dark { + background-color: rgba(0, 0, 0, 0.75) !important; +} + @media only screen and (max-width: 512px) { .first-run-wizard { .modal-wrapper .modal-container { From ff88c3bf40ddbe6e8f9e6f6a4eba364a368bbf16 Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Sat, 22 Aug 2026 18:50:18 +0200 Subject: [PATCH 2/3] feat: allow dismissing the first-run wizard by clicking outside MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The wizard was opened with `noClose`, which blocks every close path (NcModal's close() returns early when noClose is set). Drop it and enable `closeOnClickOutside` so the wizard can be dismissed by clicking the backdrop โ€” as well as via the standard close button and Escape. All paths route through the existing close handler, so dismissing still records "do not show again". Co-Authored-By: Claude Opus 4.8 --- src/views/App.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/App.vue b/src/views/App.vue index c9bfe8bd1..8ed9bb395 100644 --- a/src/views/App.vue +++ b/src/views/App.vue @@ -57,7 +57,7 @@ function close() { id="firstrunwizard" class="first-run-wizard" size="normal" - noClose + closeOnClickOutside :dark="!isMobile" :setReturnFocus @close="close" From f79b6951d9aa2eceead0a94e33b9d787b07d980d Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Sun, 23 Aug 2026 16:40:23 +0200 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20livelier=20wizard=20=E2=80=94=20onb?= =?UTF-8?q?oarding=20checklist,=20motion=20polish,=20full=20flow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build on the backdrop/close-outside tweaks with a round of UX improvements that make the first-run wizard more useful and more pleasant to move through. Live onboarding checklist (replaces the static "devices" page) - New GetStarted page turns the device step into a personal checklist: add a profile picture, set a full name, add an email, install the desktop app, get the mobile app, connect calendar & contacts. Profile items tick off live from the user's real state, with a progress bar and a friendly greeting. - The step is platform-aware: the client matching the visitor's device is highlighted and marked "recommended for your device"; the mobile row links to both the App Store and Google Play, all action buttons a uniform width. - BeforeTemplateRenderedListener provides the onboarding initial state (display name, whether avatar/name/email are set, and whether the user may change avatar/name so we can hide steps they can't act on). Always show the full wizard - Drop the returning-user "changelog only" shortcut so opening "About & What's new" shows the complete wizard, not just a couple of pages. Motion polish - Spring-eased page transitions in the slideshow. - Staggered reveal of the key-aspects cards plus hover micro-interactions. - An animated appear/disappear for the modal itself: it springs up and sharpens from a soft blur into focus while the backdrop pulls focus, then drifts away slowly and smoothly. All motion is disabled under prefers-reduced-motion. Source only; compiled assets need a /compile. Co-Authored-By: Claude Opus 4.8 --- .../BeforeTemplateRenderedListener.php | 17 +- src/components/InfoCard.vue | 52 ++- src/components/SlideShow.vue | 25 +- src/components/pages/GetStarted.vue | 384 ++++++++++++++++++ src/components/pages/KeyNotes.vue | 4 + src/pages.ts | 6 +- src/views/App.vue | 87 +++- 7 files changed, 554 insertions(+), 21 deletions(-) create mode 100644 src/components/pages/GetStarted.vue diff --git a/lib/Listener/BeforeTemplateRenderedListener.php b/lib/Listener/BeforeTemplateRenderedListener.php index 84fbdd2ae..a3cf90547 100644 --- a/lib/Listener/BeforeTemplateRenderedListener.php +++ b/lib/Listener/BeforeTemplateRenderedListener.php @@ -61,12 +61,6 @@ public function handle(Event $event): void { if (version_compare(Constants::CHANGELOG_VERSION, $lastSeenVersion, '>')) { Util::addScript(Application::APP_ID, Application::APP_ID . '-activate'); } - - // If the user was already seen before (compatibility with older wizard versions where the value was 1) - // then we only show the changelog - if (version_compare($lastSeenVersion, '1', '>')) { - $this->initialState->provideInitialState('changelogOnly', true); - } } Util::addStyle(Application::APP_ID, Application::APP_ID . '-style'); @@ -86,5 +80,16 @@ public function handle(Event $event): void { 'ios', $this->systemConfig->getSystemValueString('customclient_ios', $this->theming->getiOSClientUrl()) ); + + // Live onboarding state for the "Get set up" checklist: which profile + // basics are already done, so the wizard can tick them off. + $this->initialState->provideInitialState('onboarding', [ + 'displayName' => $user->getDisplayName(), + 'hasAvatar' => $user->getAvatarImage(64) !== null, + 'hasName' => $user->getDisplayName() !== '' && $user->getDisplayName() !== $user->getUID(), + 'hasEmail' => ($user->getEMailAddress() ?? '') !== '', + 'canChangeAvatar' => $user->canChangeAvatar(), + 'canChangeName' => $user->canChangeDisplayName(), + ]); } } diff --git a/src/components/InfoCard.vue b/src/components/InfoCard.vue index 8daed32b9..4f05cbd70 100644 --- a/src/components/InfoCard.vue +++ b/src/components/InfoCard.vue @@ -9,16 +9,22 @@ const props = defineProps<{ title: string subtitle?: string href?: string + /** When set, the card fades/rises in with a stagger based on this index. */ + revealIndex?: number }>() const isLink = computed(() => !!props.href) +const revealStyle = computed(() => props.revealIndex === undefined + ? undefined + : { '--reveal-delay': `${props.revealIndex * 80}ms` })