feat(frontend): restore Hello/Welcome headline cycle on login

Earlier I deferred the legacy headline word-cycling animation as
"purely aesthetic". Restored it: the title now alternates between
'Hello' and 'Welcome' every 2 seconds, matching the legacy panel.

The legacy implementation toggled .is-visible / .is-hidden classes on
two <b> elements via setTimeout chains and DOM querying. Replaced
with a reactive ref + Vue 3 <Transition mode="out-in"> so the fade
between words is declarative — no manual DOM manipulation, and the
interval is properly cleaned up in onBeforeUnmount.

The earlier "Welcome to 3x-ui" string was wrong on two counts: it
should be just "Welcome", and it should be one of two cycling words
with "Hello" preceding it.

Ultra-dark palette already matched legacy after the prior wave timing
fix; no additional changes needed there beyond the animation speeds
that now also apply to ultra-dark via the shared CSS rules.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
MHSanaei 2026-05-08 12:18:33 +02:00
parent 35e54f2e9a
commit 651aea1ca8
No known key found for this signature in database
GPG key ID: 7E4060F2FBE5AB7A

View file

@ -1,5 +1,5 @@
<script setup>
import { computed, onMounted, reactive, ref } from 'vue';
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import { UserOutlined, LockOutlined, KeyOutlined, SettingOutlined } from '@ant-design/icons-vue';
import { theme as antdTheme } from 'ant-design-vue';
@ -14,6 +14,24 @@ const antdThemeConfig = computed(() => ({
algorithm: themeState.isDark ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm,
}));
// Cycle the title between "Hello" and "Welcome" matches the legacy
// panel's Vue 2 .is-visible / .is-hidden DOM-class swap, but driven
// reactively + with a Vue 3 <Transition> for the fade.
const HEADLINE_WORDS = ['Hello', 'Welcome'];
const HEADLINE_INTERVAL_MS = 2000;
const headlineIndex = ref(0);
let headlineTimer = null;
onMounted(() => {
headlineTimer = window.setInterval(() => {
headlineIndex.value = (headlineIndex.value + 1) % HEADLINE_WORDS.length;
}, HEADLINE_INTERVAL_MS);
});
onBeforeUnmount(() => {
if (headlineTimer != null) window.clearInterval(headlineTimer);
});
// Phase 4 ships this page in English only. Translations come back in
// Phase 7 (vue-i18n) once we decide how the new build pipeline reads
// the existing TOML translation files.
@ -94,7 +112,11 @@ async function login() {
<a-row justify="center">
<a-col :span="24">
<h2 class="login-title">Welcome to 3x-ui</h2>
<h2 class="login-title">
<Transition name="headline" mode="out-in">
<b :key="headlineIndex">{{ HEADLINE_WORDS[headlineIndex] }}</b>
</Transition>
</h2>
</a-col>
</a-row>
@ -250,6 +272,27 @@ async function login() {
margin-bottom: 32px;
font-size: 2rem;
font-weight: 500;
min-height: 2.5rem;
}
.login-title b {
display: inline-block;
}
/* Cycle word fade — analogous to the legacy .is-visible / .is-hidden
* classes, but using Vue 3's <Transition> so we don't have to manage
* the DOM by hand. */
.headline-enter-active,
.headline-leave-active {
transition: opacity 0.4s ease, transform 0.4s ease;
}
.headline-enter-from {
opacity: 0;
transform: translateY(-12px);
}
.headline-leave-to {
opacity: 0;
transform: translateY(12px);
}
.waves-header {