diff --git a/frontend/src/stores/phone.ts b/frontend/src/stores/phone.ts index be23e88..62dc5ad 100644 --- a/frontend/src/stores/phone.ts +++ b/frontend/src/stores/phone.ts @@ -421,6 +421,13 @@ const cryptoFallbackLocales = { subtitle: 'Fictional asset exchange', loading: 'Opening secure exchange…', logout: 'Sign out', + logoutConfirm: { + title: 'Sign out of VaultX?', + body: 'You will need your VaultX password to unlock this profile again.', + cancel: 'Cancel', + confirm: 'Sign out', + signingOut: 'Signing out…', + }, refresh: 'Refresh portfolio', navigation: 'VaultX navigation', auth: { @@ -434,6 +441,8 @@ const cryptoFallbackLocales = { characterBound: 'Character bound', login: 'Log in', register: 'Register', + accountEmail: 'Sky Cloud email', + accountMissing: 'No Sky Cloud account', loginPanelTitle: 'Secure account access', loginPanelBody: 'Your character identity is detected automatically.', registerPanelTitle: 'Set up your private vault', diff --git a/frontend/src/views/apps/CryptoApp.contract.test.ts b/frontend/src/views/apps/CryptoApp.contract.test.ts index 9b55972..5604819 100644 --- a/frontend/src/views/apps/CryptoApp.contract.test.ts +++ b/frontend/src/views/apps/CryptoApp.contract.test.ts @@ -77,6 +77,12 @@ describe('VaultX crypto app contracts', () => { it('supports secure profile editing and header sign out', () => { expect(source).toContain('class="profile-signout"') expect(source).toContain("{{ t('logout') }}") + expect(source).toContain('@click="logoutDialogOpen = true"') + expect(source).toContain(':opened="logoutDialogOpen"') + expect(source).toContain("t('logoutConfirm.title')") + expect(source).toContain("t('logoutConfirm.cancel')") + expect(source).toContain("'logoutConfirm.confirm'") + expect(source).toContain('@click="signOut"') expect(source).not.toMatch(/icon-only\s+class="profile-signout"/) expect(source).toContain('class="profile-edit-button"') expect(source).toContain("sheet.value = 'profile'") @@ -88,21 +94,39 @@ describe('VaultX crypto app contracts', () => { expect(server).toContain('CryptoHashPassword(new_password)') }) - it('provides detailed registration and server-authoritative crypto-key transfers', () => { + it('keeps registration focused and crypto-key transfers server-authoritative', () => { const transferServer = server.slice( server.indexOf('local function execute_transfer'), server.indexOf('Bridge.Callbacks.Register("sky_phone:crypto:quote"'), ) - expect(source).toContain('class="password-strength"') + expect(source).toContain('class="auth-account"') + expect(source).toContain('') + expect(source).not.toContain( + ' { expect(rule?.[1]).not.toContain('linear-gradient') expect(rule?.[1]).toContain('box-shadow: none') } - expect(source).toMatch( - /\.password-rules span\s*\{[^}]*font-size:\s*10\.5px;/s, - ) - expect(source).toMatch( - /\.password-strength i\.active\s*\{[^}]*background:\s*var\(--vault-mint\);/s, - ) - expect(source).not.toMatch( - /\.password-strength i\.active\s*\{[^}]*linear-gradient/s, - ) expect(testServer).toContain('let cryptoRegistered = true') expect(testServer).toContain('registered: cryptoRegistered') expect(testServer).toContain( "cryptoRegistered = testScenario !== 'crypto-register'", ) expect(testServer).toContain('cryptoPassword = password') - expect(source).toContain('v-model="confirmPassword"') - expect(source).toContain('(null) const side = ref('buy') const handle = ref('') const password = ref('') -const confirmPassword = ref('') -const acceptedTerms = ref(false) const amount = ref('') const financialPassword = ref('') const showPassword = ref(false) @@ -115,6 +117,8 @@ const transferQuantity = ref('') const transferPassword = ref('') const transferRecipient = ref(null) const walletKeyCopied = ref(false) +const logoutDialogOpen = ref(false) +const logoutSubmitting = ref(false) const locale = computed(() => phone.lang || 'de') const authenticated = computed(() => crypto.data?.authenticated === true) @@ -132,15 +136,6 @@ const activities = computed(() => )), ), ) -const passwordChecks = computed(() => ({ - length: password.value.length >= 8, - mixed: /[a-z]/.test(password.value) && /[A-Z]/.test(password.value), - number: /\d/.test(password.value), - special: /[^A-Za-z0-9]/.test(password.value), -})) -const passwordStrength = computed( - () => Object.values(passwordChecks.value).filter(Boolean).length, -) const transferMarket = computed(() => market(transferMarketId.value)) const transferHolding = computed(() => holdings.value.find((item) => item.assetId === transferMarketId.value), @@ -482,30 +477,12 @@ function closeSheet() { async function submitAuth() { formError.value = '' - if ( - authMode.value === 'register' && - (passwordStrength.value < 4 || - password.value !== confirmPassword.value || - !acceptedTerms.value) - ) { - formError.value = t( - passwordStrength.value < 4 - ? 'errors.invalid_password' - : password.value !== confirmPassword.value - ? 'errors.password_mismatch' - : 'errors.accept_terms', - ) - return - } const success = authMode.value === 'register' ? await crypto.register(handle.value.trim(), password.value) : await crypto.login(password.value) if (!success) formError.value = errorText(crypto.error) - else { - password.value = '' - confirmPassword.value = '' - } + else password.value = '' } async function resolveTransferRecipient() { formError.value = '' @@ -623,11 +600,30 @@ async function savePreferences() { else saved.value = true } async function signOut() { + if (logoutSubmitting.value) return + logoutSubmitting.value = true closeSheet() - await crypto.logout() - tab.value = 'portfolio' + try { + await crypto.logout() + if (!authenticated.value) { + logoutDialogOpen.value = false + tab.value = 'portfolio' + } + } finally { + logoutSubmitting.value = false + } } +watch( + () => account.email, + (email) => { + if (!email || handle.value) return + handle.value = (email.split('@')[0] ?? '') + .replace(/[^a-zA-Z0-9._]/g, '') + .slice(0, 20) + }, + { immediate: true }, +) watch( profile, (value) => { @@ -696,6 +692,7 @@ onUnmounted(() => { dark > { type="button" :aria-label="t('logout')" :title="t('logout')" - @click="signOut" + @click="logoutDialogOpen = true" > {{ t('logout') }} @@ -753,142 +750,92 @@ onUnmounted(() => { > - - - {{ t('auth.eyebrow') }} - - {{ - t(authMode === 'login' ? 'auth.loginTitle' : 'auth.registerTitle') - }} - - {{ t('auth.body') }} - - {{ t('auth.serverSecured') }} - {{ t('auth.personalKey') }} - {{ t('auth.characterBound') }} + + + + + + {{ + t( + authMode === 'login' + ? 'auth.loginTitle' + : 'auth.registerTitle', + ) + }} + + + + {{ t('auth.login') }}{{ t('auth.register') }} - - - - {{ t('auth.login') }}{{ t('auth.register') }} - - {{ authMode === 'login' ? '01' : '02' }} - - {{ t(`auth.${authMode}PanelTitle`) }} - {{ t(`auth.${authMode}PanelBody`) }} + + + + {{ t('auth.accountEmail') }} + {{ account.email || t('auth.accountMissing') }} + + - - - - - - + + + - - - - - {{ t(`auth.strength${passwordStrength}`) }} - - - {{ t('auth.ruleLength') }} - {{ t('auth.ruleMixed') }} - {{ t('auth.ruleNumber') }} - {{ t('auth.ruleSpecial') }} - - - {{ t('auth.acceptTitle') }}{{ t('auth.acceptBody') }} - - - - {{ t('auth.security') }} - - {{ formError }} - - - + {{ formError }} + {{ + t( + authMode === 'login' + ? 'auth.loginAction' + : 'auth.registerAction', + ) + }} + + + - - {{ - t(authMode === 'login' ? 'auth.loginAction' : 'auth.registerAction') - }} - { + + {{ t('logoutConfirm.title') }} + {{ t('logoutConfirm.body') }} + + + {{ t('logoutConfirm.cancel') }} + + + {{ + t( + logoutSubmitting + ? 'logoutConfirm.signingOut' + : 'logoutConfirm.confirm', + ) + }} + + + @@ -1998,6 +1976,10 @@ onUnmounted(() => { ), #05070b; } +.vault-logout-confirm { + color: #fff; + background: #c4475c; +} .state, .auth { display: grid; @@ -2009,9 +1991,8 @@ onUnmounted(() => { .auth-hero { display: grid; justify-items: center; - gap: 7px; + gap: 6px; } -.auth-hero__mark, .profile-head > span { display: grid; place-items: center; @@ -2022,200 +2003,181 @@ onUnmounted(() => { background: linear-gradient(145deg, #6ff5cd, #3d8fff); box-shadow: 0 18px 45px rgba(49, 214, 170, 0.18); } -.auth-hero p, .auth-hero h2, .auth-hero small { margin: 0; } -.auth-hero p { - color: #49e4b2; - font-size: 12px; - font-weight: 800; - letter-spacing: 0.12em; - text-transform: uppercase; +.auth-hero h2 { + font-size: 23px; + line-height: 1.15; } .auth-hero small { - max-width: 280px; + max-width: 260px; color: var(--muted); + font-size: 11px; line-height: 1.45; } .auth { - align-content: start; - gap: 14px; + align-content: center; + justify-items: center; min-height: 0; flex: 1 1 auto; - padding-top: 12px !important; - padding-bottom: 28px !important; + padding-top: 18px !important; + padding-bottom: 22px !important; } -.auth-benefits { +.auth-shell { display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 6px; + gap: 18px; width: 100%; - margin-top: 8px; -} -.auth-benefits span { - display: grid; - min-height: 57px; - place-content: center; - justify-items: center; - gap: 5px; - padding: 7px 4px; - color: rgba(255, 255, 255, 0.7); - font-size: 10px; - line-height: 1.15; - background: rgba(255, 255, 255, 0.035); - border: 1px solid rgba(255, 255, 255, 0.055); - border-radius: 13px; -} -.auth-benefits svg { - color: var(--vault-mint); + max-width: 338px; } .auth-panel { - padding: 9px; + padding: 14px; text-align: left; - background: linear-gradient(145deg, #111923, #080d13); + background: #0b1118; border: 1px solid rgba(255, 255, 255, 0.075); - border-radius: 25px; + border-radius: 22px; box-shadow: - 0 24px 54px rgba(0, 0, 0, 0.34), - inset 0 1px rgba(255, 255, 255, 0.045); + 0 20px 46px rgba(0, 0, 0, 0.3), + inset 0 1px rgba(255, 255, 255, 0.04); } .auth-mode { - min-height: 46px; - margin-bottom: 13px; + min-height: 48px; + margin-bottom: 14px; padding: 3px; background: rgba(255, 255, 255, 0.045); - border-radius: 15px; + border-radius: 14px; } .auth-mode :deep(.sky-segmented-button) { - min-height: 40px; + min-height: 42px; color: rgba(255, 255, 255, 0.52); - font-size: 12px; + font-size: 13px; font-weight: 800; } .auth-mode :deep(.sky-segmented-button--active) { color: #fff; } -.auth-panel__heading { - display: flex; - gap: 10px; - align-items: center; - margin: 0 4px 12px; -} -.auth-panel__heading > span { - display: grid; - place-items: center; - width: 34px; - height: 34px; - color: #07100e; - font-size: 11px; - font-weight: 900; - background: linear-gradient(145deg, var(--vault-mint), var(--vault-blue)); - border-radius: 11px; -} -.auth-panel__heading > div { - display: grid; - gap: 2px; -} -.auth-panel__heading b { - font-size: 13px; -} -.auth-panel__heading small { - color: var(--muted); - font-size: 11px; -} .auth-form { - padding: 0 4px 4px; + gap: 12px; } -.password-strength > div { +.auth-account { display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 4px; -} -.password-strength i { - height: 3px; - background: rgba(255, 255, 255, 0.08); - border-radius: 3px; -} -.password-strength i.active { - background: var(--vault-mint); -} -.password-strength span { - display: block; - margin-top: 5px; - color: var(--muted); - font-size: 11px; -} -.password-rules { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 8px 6px; -} -.password-rules span { - display: flex; - gap: 6px; + grid-template-columns: 40px minmax(0, 1fr) 18px; + gap: 11px; align-items: center; - color: rgba(255, 255, 255, 0.35); - font-size: 10.5px; + min-height: 64px; + margin-bottom: 12px; + padding: 10px 13px; + color: #f8fbfd; + background: rgba(101, 251, 210, 0.055); + border: 1px solid rgba(101, 251, 210, 0.15); + border-radius: 17px; +} +.auth-account > span { + display: grid; + width: 40px; + height: 40px; + place-items: center; + color: var(--vault-mint); + background: rgba(101, 251, 210, 0.09); + border-radius: 12px; +} +.auth-account > div { + display: grid; + gap: 3px; + min-width: 0; +} +.auth-account small { + color: rgba(255, 255, 255, 0.52); + font-size: 11px; + font-weight: 750; +} +.auth-account strong { + overflow: hidden; + font-size: 14px; line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; } -.password-rules svg { - width: 15px; - height: 15px; - flex: 0 0 15px; -} -.password-rules span.done { +.auth-account > svg { color: var(--vault-mint); } -.auth-consent { - display: flex; - gap: 9px; - align-items: flex-start; - padding: 10px; - background: rgba(255, 255, 255, 0.035); - border: 1px solid rgba(255, 255, 255, 0.055); - border-radius: 13px; +.auth-field { + min-height: 68px; + margin: 0; + padding: 0 14px; + color: #f8fbfd; + background: #080d13; + border-radius: 17px; + box-shadow: inset 0 1px rgba(255, 255, 255, 0.035); } -.auth-consent span { +.auth-field:focus-within { + background: #091119; + box-shadow: + 0 0 0 3px rgba(101, 251, 210, 0.06), + inset 0 1px rgba(255, 255, 255, 0.05); +} +.auth-field :deep(.sky-field__media) { display: grid; - gap: 2px; + width: 40px; + height: 40px; + place-items: center; + justify-content: center; + margin-right: 11px; + padding: 0; + color: var(--vault-mint); + background: rgba(101, 251, 210, 0.07); + border: 1px solid rgba(101, 251, 210, 0.12); + border-radius: 11px; } -.auth-consent b { - font-size: 10px; +.auth-field :deep(.sky-field__inner) { + padding: 11px 0 9px; } -.auth-consent small { - color: var(--muted); - font-size: 10px; - line-height: 1.35; +.auth-field :deep(.sky-field__label) { + margin-top: 0; + color: rgba(255, 255, 255, 0.56); + font-size: 11px; + font-weight: 750; } -.auth-action-dock { - flex: 0 0 auto; - padding: 9px calc(var(--sky-page-gutter) + var(--sky-safe-area-right)) - calc(var(--sky-safe-area-bottom) + 9px) - calc(var(--sky-page-gutter) + var(--sky-safe-area-left)); - border-top: 1px solid rgba(255, 255, 255, 0.07); - background: #05080d; +.auth-field :deep(.sky-field__label-text) { + top: 0; + margin: 0; + padding: 0; + background: transparent; +} +.auth-field :deep(.sky-field__control) { + margin: -2px 0 0; +} +.auth-field :deep(.sky-field__input) { + height: 33px; + min-height: 33px; + color: #fff; + font-size: 16px; + font-weight: 700; + line-height: 21px; +} +.auth-field :deep(.sky-field__input::placeholder) { + color: rgba(255, 255, 255, 0.28); +} +.auth-field :deep(.sky-field__border) { + inset: 0; + border-color: rgba(255, 255, 255, 0.12); + border-radius: 17px; +} +.auth-field:focus-within :deep(.sky-field__border) { + border-color: rgba(101, 251, 210, 0.56); } .auth-submit { - min-height: 50px; + min-height: 54px; + margin-top: 3px; color: #06110e; - font-size: 13px; + font-size: 14px; font-weight: 900; border: 1px solid rgba(101, 251, 210, 0.48); border-radius: 12px; background: var(--vault-mint); box-shadow: none; } -.auth-footer { - display: flex; - gap: 6px; - align-items: center; - justify-content: center; - padding: 9px 4px 2px; - color: rgba(255, 255, 255, 0.36); - font-size: 10px; -} .form { display: grid; gap: 13px; @@ -2880,28 +2842,17 @@ onUnmounted(() => { color: #fff; } .auth-hero__mark { - position: relative; - width: 76px; - height: 76px; - border: 1px solid rgba(255, 255, 255, 0.24); - border-radius: 26px; - background: - linear-gradient(145deg, rgba(255, 255, 255, 0.8), transparent 42%), - linear-gradient(145deg, #7dffe0, #3977ff); - box-shadow: - 0 22px 70px rgba(60, 224, 184, 0.22), - inset 0 0 24px rgba(255, 255, 255, 0.3); + display: grid; + width: 66px; + height: 66px; + place-items: center; } -.auth-hero__mark::after { - position: absolute; - right: -5px; - bottom: -5px; - width: 18px; - height: 18px; - border: 4px solid #070b11; - border-radius: 50%; - content: ''; - background: var(--vault-mint); +.auth-hero__mark img { + display: block; + width: 66px; + height: 66px; + object-fit: contain; + filter: drop-shadow(0 12px 22px rgba(49, 214, 170, 0.18)); } .portfolio-shell { position: relative; diff --git a/sky_phone/config/locales/de.lua b/sky_phone/config/locales/de.lua index e341391..bf37588 100644 --- a/sky_phone/config/locales/de.lua +++ b/sky_phone/config/locales/de.lua @@ -1050,9 +1050,9 @@ Locales["de"] = { }, }, crypto = { - name = "VaultX", subtitle = "Börse für fiktive Assets", loading = "Sichere Börse wird geöffnet…", logout = "Abmelden", refresh = "Portfolio aktualisieren", navigation = "VaultX Navigation", + name = "VaultX", subtitle = "Börse für fiktive Assets", loading = "Sichere Börse wird geöffnet…", logout = "Abmelden", logoutConfirm = { title = "Bei VaultX abmelden?", body = "Du brauchst dein VaultX-Passwort, um dieses Profil erneut zu entsperren.", cancel = "Abbrechen", confirm = "Abmelden", signingOut = "Wird abgemeldet…" }, refresh = "Portfolio aktualisieren", navigation = "VaultX Navigation", auth = { - eyebrow = "Charaktergebundene Börse", loginTitle = "Willkommen zurück", registerTitle = "Erstelle dein VaultX-Profil", body = "Handle und übertrage fiktive digitale Assets über ein geschütztes, servergesteuertes Konto.", network = "VAULT-NETZWERK ONLINE", serverSecured = "Servergeschützt", personalKey = "Eigener Crypto-Key", characterBound = "Charaktergebunden", login = "Einloggen", register = "Registrieren", loginPanelTitle = "Sicherer Kontozugang", loginPanelBody = "Deine Charakteridentität wird automatisch erkannt.", registerPanelTitle = "Richte deinen privaten Vault ein", registerPanelBody = "Wähle deine Profilidentität und sichere Zugangsphrase.", handle = "VaultX-Benutzername", handlePlaceholder = "3–20 Buchstaben oder Zahlen", password = "Passwort", passwordPlaceholder = "8–72 Zeichen", confirmPassword = "Passwort bestätigen", confirmPasswordPlaceholder = "Dasselbe Passwort erneut eingeben", showPassword = "Passwort anzeigen", hidePassword = "Passwort ausblenden", security = "Nutze ein In-Character-Passwort. Verwende niemals ein echtes Passwort erneut.", ruleLength = "Mindestens 8 Zeichen", ruleMixed = "Groß- & Kleinbuchstaben", ruleNumber = "Enthält eine Zahl", ruleSpecial = "Sonderzeichen", strength0 = "Beginne mit einem sicheren Passwort", strength1 = "Schwacher Schutz", strength2 = "Grundlegender Schutz", strength3 = "Starker Schutz", strength4 = "Sehr starker Schutz", acceptTitle = "Fiktives Wallet erstellen", acceptBody = "Ich verstehe, dass VaultX-Assets keinen realen Wert besitzen.", footer = "Verschlüsselte Sitzung · Begrenzte Zugriffe · Speicherintensiver Passwortschutz", loginAction = "VaultX entsperren", registerAction = "Sicheres Profil erstellen", + eyebrow = "Charaktergebundene Börse", loginTitle = "Willkommen zurück", registerTitle = "Erstelle dein VaultX-Profil", body = "Handle und übertrage fiktive digitale Assets über ein geschütztes, servergesteuertes Konto.", network = "VAULT-NETZWERK ONLINE", serverSecured = "Servergeschützt", personalKey = "Eigener Crypto-Key", characterBound = "Charaktergebunden", login = "Einloggen", register = "Registrieren", accountEmail = "Sky Cloud E-Mail", accountMissing = "Kein Sky Cloud Konto", loginPanelTitle = "Sicherer Kontozugang", loginPanelBody = "Deine Charakteridentität wird automatisch erkannt.", registerPanelTitle = "Richte deinen privaten Vault ein", registerPanelBody = "Wähle deine Profilidentität und sichere Zugangsphrase.", handle = "VaultX-Benutzername", handlePlaceholder = "3–20 Buchstaben oder Zahlen", password = "Passwort", passwordPlaceholder = "8–72 Zeichen", confirmPassword = "Passwort bestätigen", confirmPasswordPlaceholder = "Dasselbe Passwort erneut eingeben", showPassword = "Passwort anzeigen", hidePassword = "Passwort ausblenden", security = "Nutze ein In-Character-Passwort. Verwende niemals ein echtes Passwort erneut.", ruleLength = "Mindestens 8 Zeichen", ruleMixed = "Groß- & Kleinbuchstaben", ruleNumber = "Enthält eine Zahl", ruleSpecial = "Sonderzeichen", strength0 = "Beginne mit einem sicheren Passwort", strength1 = "Schwacher Schutz", strength2 = "Grundlegender Schutz", strength3 = "Starker Schutz", strength4 = "Sehr starker Schutz", acceptTitle = "Fiktives Wallet erstellen", acceptBody = "Ich verstehe, dass VaultX-Assets keinen realen Wert besitzen.", footer = "Verschlüsselte Sitzung · Begrenzte Zugriffe · Speicherintensiver Passwortschutz", loginAction = "VaultX entsperren", registerAction = "Sicheres Profil erstellen", }, tabs = { portfolio = "Portfolio", markets = "Märkte", activity = "Aktivität", profile = "Profil" }, portfolio = { total = "Gesamtes Portfolio", performance = "Gewinn / Verlust", return = "Gesamtrendite", current = "Jetziger Stand", history = "Entwicklung", forecast = "Prognose", chartLabel = "Gewinn- und Verlustentwicklung mit gestrichelter Prognose", cash = "Verfügbares Guthaben", invested = "Investierte Assets", allocation = "Portfolio-Verteilung", inMarket = "in Märkten", assets = "Assets", holdings = "Deine Assets", empty = "Noch keine Assets", emptyBody = "Öffne Märkte und fordere ein geschütztes Angebot an.", avg = "Ø", insightTitle = "Portfolio-Einblick", insightBody = "Deine Bestände werden mit den neuesten synthetischen Serverkursen bewertet." }, diff --git a/sky_phone/config/locales/en.lua b/sky_phone/config/locales/en.lua index 65700f0..9b9519a 100644 --- a/sky_phone/config/locales/en.lua +++ b/sky_phone/config/locales/en.lua @@ -1050,9 +1050,9 @@ Locales["en"] = { }, }, crypto = { - name = "VaultX", subtitle = "Fictional asset exchange", loading = "Opening secure exchange…", logout = "Sign out", refresh = "Refresh portfolio", navigation = "VaultX navigation", + name = "VaultX", subtitle = "Fictional asset exchange", loading = "Opening secure exchange…", logout = "Sign out", logoutConfirm = { title = "Sign out of VaultX?", body = "You will need your VaultX password to unlock this profile again.", cancel = "Cancel", confirm = "Sign out", signingOut = "Signing out…" }, refresh = "Refresh portfolio", navigation = "VaultX navigation", auth = { - eyebrow = "Character-bound exchange", loginTitle = "Welcome back", registerTitle = "Create your VaultX profile", body = "Trade and transfer fictional digital assets through a protected, server-controlled account.", network = "VAULT NETWORK ONLINE", serverSecured = "Server secured", personalKey = "Personal crypto key", characterBound = "Character bound", login = "Log in", register = "Register", loginPanelTitle = "Secure account access", loginPanelBody = "Your character identity is detected automatically.", registerPanelTitle = "Set up your private vault", registerPanelBody = "Choose a profile identity and secure access phrase.", handle = "VaultX handle", handlePlaceholder = "3–20 letters or numbers", password = "Password", passwordPlaceholder = "8–72 characters", confirmPassword = "Confirm password", confirmPasswordPlaceholder = "Enter the same password again", showPassword = "Show password", hidePassword = "Hide password", security = "Use an in-character password. Never reuse a real password.", ruleLength = "8+ characters", ruleMixed = "Upper & lowercase", ruleNumber = "Contains a number", ruleSpecial = "Special character", strength0 = "Start entering a secure password", strength1 = "Weak protection", strength2 = "Basic protection", strength3 = "Strong protection", strength4 = "Excellent protection", acceptTitle = "Create a fictional wallet", acceptBody = "I understand that VaultX assets have no real-world value.", footer = "Encrypted session · Rate-limited access · Memory-hard password", loginAction = "Unlock VaultX", registerAction = "Create secure profile", + eyebrow = "Character-bound exchange", loginTitle = "Welcome back", registerTitle = "Create your VaultX profile", body = "Trade and transfer fictional digital assets through a protected, server-controlled account.", network = "VAULT NETWORK ONLINE", serverSecured = "Server secured", personalKey = "Personal crypto key", characterBound = "Character bound", login = "Log in", register = "Register", accountEmail = "Sky Cloud email", accountMissing = "No Sky Cloud account", loginPanelTitle = "Secure account access", loginPanelBody = "Your character identity is detected automatically.", registerPanelTitle = "Set up your private vault", registerPanelBody = "Choose a profile identity and secure access phrase.", handle = "VaultX handle", handlePlaceholder = "3–20 letters or numbers", password = "Password", passwordPlaceholder = "8–72 characters", confirmPassword = "Confirm password", confirmPasswordPlaceholder = "Enter the same password again", showPassword = "Show password", hidePassword = "Hide password", security = "Use an in-character password. Never reuse a real password.", ruleLength = "8+ characters", ruleMixed = "Upper & lowercase", ruleNumber = "Contains a number", ruleSpecial = "Special character", strength0 = "Start entering a secure password", strength1 = "Weak protection", strength2 = "Basic protection", strength3 = "Strong protection", strength4 = "Excellent protection", acceptTitle = "Create a fictional wallet", acceptBody = "I understand that VaultX assets have no real-world value.", footer = "Encrypted session · Rate-limited access · Memory-hard password", loginAction = "Unlock VaultX", registerAction = "Create secure profile", }, tabs = { portfolio = "Portfolio", markets = "Markets", activity = "Activity", profile = "Profile" }, portfolio = { total = "Total portfolio", performance = "Profit / loss", return = "Total return", current = "Current position", history = "Performance", forecast = "Forecast", chartLabel = "Profit and loss performance with dashed forecast", cash = "Available cash", invested = "Invested assets", allocation = "Portfolio allocation", inMarket = "in markets", assets = "assets", holdings = "Your assets", empty = "No assets yet", emptyBody = "Open Markets to request a protected quote.", avg = "Avg.", insightTitle = "Portfolio insight", insightBody = "Your holdings are valued against the latest synthetic server prices." },
{{ t('auth.eyebrow') }}
- {{ t('auth.security') }} -
{{ formError }}
{{ t('logoutConfirm.body') }}