Skip to content

Commit c06593e

Browse files
committed
Simplify onboarding around VIZ account login
1 parent b0a89af commit c06593e

5 files changed

Lines changed: 48 additions & 178 deletions

File tree

app/js/i18n/en.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ var LangEN = {
2727
login_button: 'Enter the Realm',
2828
login_connecting: 'Connecting to the Realm...',
2929
login_back: '← Back to main page',
30+
login_register_hint: 'If you do not have a VIZ account yet, register at start.viz.world',
31+
onboarding_existing_account_notice: 'VIZ account found: {account}. No character is awakened yet — choose your path of magic.',
3032
error_empty_account: 'Enter your mage name.',
3133
error_no_mage_name: 'Enter your mage name.',
3234
error_empty_key: 'Enter your Magic Word.',

app/js/i18n/ru.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ var LangRU = {
2727
login_button: 'Войти в Мир',
2828
login_connecting: 'Связь с Миром устанавливается...',
2929
login_back: '← Вернуться на главную',
30+
login_register_hint: 'Если аккаунта VIZ ещё нет, зарегистрируйтесь на start.viz.world',
31+
onboarding_existing_account_notice: 'Аккаунт VIZ найден: {account}. Персонаж ещё не пробуждён — выберите путь магии.',
3032
error_empty_account: 'Введи имя мага.',
3133
error_no_mage_name: 'Введи имя мага.',
3234
error_empty_key: 'Введи Магическое Слово.',

app/js/ui/screens/landing.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,6 @@ var LandingScreen = (function() {
2929
'<button class="btn btn-primary btn-glow btn-large" id="btn-begin">' +
3030
t('landing_cta') +
3131
'</button>' +
32-
'<p class="landing-login-link">' +
33-
'<a href="#" id="btn-login-link">' + t('landing_login') + ' \u2192</a>' +
34-
'</p>' +
3532
'</header>' +
3633
'<section class="landing-cards" aria-label="Features">' +
3734
'<div class="feature-card">' +
@@ -60,11 +57,6 @@ var LandingScreen = (function() {
6057
Helpers.$('btn-begin').addEventListener('click', function() {
6158
SoundManager.init(); // must be first — creates AudioContext inside user gesture
6259
SoundManager.play('tap');
63-
Helpers.EventBus.emit('navigate', 'onboarding');
64-
});
65-
Helpers.$('btn-login-link').addEventListener('click', function(e) {
66-
e.preventDefault();
67-
console.log('Login link clicked, navigating to login screen');
6860
Helpers.EventBus.emit('navigate', 'login');
6961
});
7062
var btnRu = Helpers.$('landing-lang-ru');

app/js/ui/screens/login.js

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ var LoginScreen = (function() {
3535
t('login_button') +
3636
'</button>' +
3737
'</form>' +
38+
'<p class="login-subtitle">' + t('login_register_hint') + ': <a href="https://start.viz.world" target="_blank" rel="noopener noreferrer">start.viz.world</a></p>' +
3839
'<p class="login-back">' +
3940
'<a href="#" id="btn-login-back">' + t('login_back') + '</a>' +
4041
'</p>' +
@@ -97,41 +98,35 @@ var LoginScreen = (function() {
9798
// Check if character exists (has Grimoire)
9899
var grimoire = VizAccount.parseGrimoire(accountData);
99100
if (grimoire && grimoire.class) {
100-
// Restore character in StateEngine from grimoire (checkpoint has priority)
101-
var state = StateEngine.getState();
102-
if (!state.characters[account]) {
103-
var character = CharacterSystem.createCharacter(account, grimoire.name || account, grimoire.class);
104-
if (character) {
105-
// Restore level/xp from Grimoire cache
106-
if (grimoire.level && grimoire.level > 1) {
107-
character.level = grimoire.level;
108-
character.xp = grimoire.xp || 0;
109-
character.hp = GameFormulas.calculateMaxHp(character.className, character.level, CharacterSystem.getTotalStat(character, 'res'));
110-
character.maxHp = character.hp;
111-
}
112-
113-
// Sync Magic Core from on-chain SHARES with heavy compression.
114-
// This keeps SHARES meaningful but prevents whales from becoming unbeatable.
115-
var effectiveShares = VizAccount.getEffectiveShares(accountData);
116-
var cappedShares = Math.min(effectiveShares, 1000000000000); // cap at 1,000,000 SHARES (6 decimals)
117-
CharacterSystem.updateCoreBonus(character, cappedShares);
118-
119-
state.characters[account] = character;
120-
state.inventories[account] = state.inventories[account] || [];
121-
state.quests[account] = state.quests[account] || (typeof QuestSystem !== 'undefined' ? QuestSystem.createPlayerQuestState() : {});
122-
console.log('Character restored from grimoire (login):', grimoire.name, grimoire.class, 'Lv' + character.level, 'XP:' + character.xp, 'Core:' + character.coreBonus);
123-
}
124-
} else {
125-
console.log('Character already in state from checkpoint, keeping checkpoint data');
126-
}
127-
// Existing character — go to home
101+
_restoreCharacterFromGrimoire(account, accountData, grimoire);
128102
Helpers.EventBus.emit('navigate', 'home');
129103
} else {
130-
// No character yet — go to onboarding (class selection)
104+
OnboardingScreen.startForAccount(account);
131105
Helpers.EventBus.emit('navigate', 'onboarding');
132106
}
133107
});
134108
}
135109

110+
function _restoreCharacterFromGrimoire(account, accountData, grimoire) {
111+
var state = StateEngine.getState();
112+
if (!state.characters[account]) {
113+
var character = CharacterSystem.createCharacter(account, grimoire.name || account, grimoire.class);
114+
if (character) {
115+
if (grimoire.level && grimoire.level > 1) {
116+
character.level = grimoire.level;
117+
character.xp = grimoire.xp || 0;
118+
character.hp = GameFormulas.calculateMaxHp(character.className, character.level, CharacterSystem.getTotalStat(character, 'res'));
119+
character.maxHp = character.hp;
120+
}
121+
var effectiveShares = VizAccount.getEffectiveShares(accountData);
122+
var cappedShares = Math.min(effectiveShares, 1000000000000);
123+
CharacterSystem.updateCoreBonus(character, cappedShares);
124+
state.characters[account] = character;
125+
}
126+
}
127+
state.inventories[account] = state.inventories[account] || [];
128+
state.quests[account] = state.quests[account] || (typeof QuestSystem !== 'undefined' ? QuestSystem.createPlayerQuestState() : {});
129+
}
130+
136131
return { render: render };
137132
})();

app/js/ui/screens/onboarding.js

Lines changed: 20 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,23 @@
11
/**
22
* Viz Magic — Onboarding / Character Creation Flow
3+
* New flow: after successful VIZ login without grimoire,
4+
* the user only chooses a class for the existing VIZ account.
35
*/
46
var OnboardingScreen = (function() {
57
'use strict';
68

7-
var step = 0;
89
var selectedClass = '';
910
var mageName = '';
1011

1112
function render() {
1213
var el = Helpers.$('screen-onboarding');
1314
if (!el) return;
14-
step = 0;
15-
_renderStep(el);
15+
_renderClassStep(el, Helpers.t);
1616
}
1717

18-
function _renderStep(el) {
19-
var t = Helpers.t;
20-
switch (step) {
21-
case 0: _renderNameStep(el, t); break;
22-
case 1: _renderClassStep(el, t); break;
23-
case 2: _renderLoginStep(el, t); break;
24-
}
25-
}
26-
27-
function _renderNameStep(el, t) {
28-
el.innerHTML =
29-
'<div class="onboarding-step">' +
30-
'<h1>' + t('onboarding_awaken_title') + '</h1>' +
31-
'<p class="onboarding-text">' + t('onboarding_awaken_text').replace(/\n/g, '<br>') + '</p>' +
32-
'<label for="mage-name-input" class="input-label">' + t('onboarding_name_prompt') + '</label>' +
33-
'<input type="text" id="mage-name-input" class="input-field" ' +
34-
'aria-label="' + t('onboarding_name_prompt') + '" ' +
35-
'autocomplete="off" maxlength="50" value="' + Helpers.escapeHtml(mageName) + '">' +
36-
'<div id="name-feedback" class="input-feedback" aria-live="polite"></div>' +
37-
'<button class="btn btn-primary btn-large" id="btn-name-next" disabled>' +
38-
t('onboarding_next') +
39-
'</button>' +
40-
'</div>';
41-
42-
var input = Helpers.$('mage-name-input');
43-
var feedback = Helpers.$('name-feedback');
44-
var btn = Helpers.$('btn-name-next');
45-
46-
input.addEventListener('input', Helpers.debounce(function() {
47-
mageName = input.value.trim();
48-
if (!mageName) {
49-
feedback.textContent = '';
50-
btn.disabled = true;
51-
return;
52-
}
53-
if (mageName.length < 2) {
54-
feedback.textContent = t('onboarding_name_invalid');
55-
feedback.className = 'input-feedback error';
56-
btn.disabled = true;
57-
return;
58-
}
59-
feedback.textContent = '\u2728 ' + t('onboarding_name_available');
60-
feedback.className = 'input-feedback success';
61-
btn.disabled = false;
62-
}, 300));
63-
64-
btn.addEventListener('click', function() {
65-
if (!mageName) return;
66-
SoundManager.play('transition');
67-
step = 1;
68-
_renderStep(el);
69-
});
70-
71-
// BUG 3 FIX: Re-validate on render when returning via Back button
72-
if (mageName && mageName.length >= 2) {
73-
feedback.textContent = '\u2728 ' + t('onboarding_name_available');
74-
feedback.className = 'input-feedback success';
75-
btn.disabled = false;
76-
}
77-
78-
input.focus();
18+
function startForAccount(account) {
19+
selectedClass = '';
20+
mageName = (account || '').trim();
7921
}
8022

8123
function _renderClassStep(el, t) {
@@ -89,6 +31,7 @@ var OnboardingScreen = (function() {
8931
var html = '<div class="onboarding-step">' +
9032
'<h1>' + t('onboarding_class_title') + '</h1>' +
9133
'<p class="onboarding-text">' + t('onboarding_class_text').replace(/\n/g, '<br>') + '</p>' +
34+
'<p class="onboarding-text">' + t('onboarding_existing_account_notice', { account: mageName || '' }) + '</p>' +
9235
'<div class="class-grid" role="radiogroup" aria-label="' + t('onboarding_class_title') + '">';
9336

9437
for (var i = 0; i < classes.length; i++) {
@@ -107,9 +50,7 @@ var OnboardingScreen = (function() {
10750
html += '</div>' +
10851
'<div class="onboarding-buttons">' +
10952
'<button class="btn btn-secondary" id="btn-class-back">' + t('onboarding_back') + '</button>' +
110-
'<button class="btn btn-primary" id="btn-class-next" ' + (selectedClass ? '' : 'disabled') + '>' +
111-
t('onboarding_next') +
112-
'</button>' +
53+
'<button class="btn btn-primary" id="btn-class-next" ' + (selectedClass ? '' : 'disabled') + '>' + t('onboarding_next') + '</button>' +
11354
'</div></div>';
11455

11556
el.innerHTML = html;
@@ -130,110 +71,48 @@ var OnboardingScreen = (function() {
13071
}
13172

13273
Helpers.$('btn-class-back').addEventListener('click', function() {
133-
step = 0;
134-
_renderStep(el);
74+
Helpers.EventBus.emit('navigate', 'login');
13575
});
13676

13777
Helpers.$('btn-class-next').addEventListener('click', function() {
13878
if (!selectedClass) return;
13979
SoundManager.play('transition');
140-
step = 2;
141-
_renderStep(el);
80+
_createCharacterAndFinish();
14281
});
14382
}
14483

145-
/**
146-
* Create character locally and on chain, then navigate home.
147-
* BUG 1 FIX: Also creates the character in StateEngine immediately
148-
* so Hunt and other screens can find it without waiting for chain.
149-
* BUG 2 FIX: Called directly when user is already logged in.
150-
*/
15184
function _createCharacterAndFinish() {
15285
var user = VizAccount.getCurrentUser();
86+
if (!user) {
87+
Helpers.EventBus.emit('navigate', 'login');
88+
return;
89+
}
15390

154-
// Create character locally in StateEngine so all screens see it immediately
91+
var displayName = mageName || user;
15592
var state = StateEngine.getState();
156-
var character = CharacterSystem.createCharacter(user, mageName, selectedClass);
93+
var character = CharacterSystem.createCharacter(user, displayName, selectedClass);
15794
if (character) {
15895
state.characters[user] = character;
15996
state.inventories[user] = state.inventories[user] || [];
97+
state.quests[user] = state.quests[user] || (typeof QuestSystem !== 'undefined' ? QuestSystem.createPlayerQuestState() : {});
16098
}
16199

162-
// Broadcast to chain
163-
var actionData = VMProtocol.createCharAttuneAction(selectedClass, mageName);
100+
var actionData = VMProtocol.createCharAttuneAction(selectedClass, displayName);
164101
VizBroadcast.gameAction(actionData, function(err2) {
165102
if (err2) {
166103
console.log('Char attune broadcast error (may already exist):', err2);
167104
}
168105
});
169106

170-
// Save grimoire to blockchain so character persists across page reloads
171-
VizAccount.updateGrimoire({ class: selectedClass, name: mageName }, function(err3) {
107+
VizAccount.updateGrimoire({ class: selectedClass, name: displayName }, function(err3) {
172108
if (err3) {
173109
console.log('Grimoire save error:', err3);
174-
} else {
175-
console.log('Grimoire saved to chain for', user);
176110
}
177111
});
178112

179113
SoundManager.play('success');
180114
Helpers.EventBus.emit('navigate', 'home');
181115
}
182116

183-
function _renderLoginStep(el, t) {
184-
// BUG 2 FIX: If user is already logged in, skip the login form
185-
var currentUser = VizAccount.getCurrentUser();
186-
if (currentUser) {
187-
_createCharacterAndFinish();
188-
return;
189-
}
190-
191-
el.innerHTML =
192-
'<div class="onboarding-step">' +
193-
'<h1>' + t('login_title') + '</h1>' +
194-
'<p class="onboarding-text">' +
195-
'Class: ' + Helpers.classIcon(selectedClass) + ' ' + t('class_' + selectedClass) +
196-
'<br>Name: ' + Helpers.escapeHtml(mageName) +
197-
'</p>' +
198-
'<label for="login-account" class="input-label">' + t('login_account') + '</label>' +
199-
'<input type="text" id="login-account" class="input-field" autocomplete="username">' +
200-
'<label for="login-key" class="input-label">' + t('login_key') + '</label>' +
201-
'<input type="password" id="login-key" class="input-field" autocomplete="current-password">' +
202-
'<div id="login-error" class="input-feedback error" aria-live="polite"></div>' +
203-
'<button class="btn btn-primary btn-large" id="btn-login-submit">' +
204-
t('login_submit') +
205-
'</button>' +
206-
'<button class="btn btn-secondary" id="btn-login-back">' + t('onboarding_back') + '</button>' +
207-
'</div>';
208-
209-
Helpers.$('btn-login-back').addEventListener('click', function() {
210-
step = 1;
211-
_renderStep(el);
212-
});
213-
214-
Helpers.$('btn-login-submit').addEventListener('click', function() {
215-
var account = Helpers.$('login-account').value.trim();
216-
var key = Helpers.$('login-key').value.trim();
217-
var errorEl = Helpers.$('login-error');
218-
219-
if (!account || !key) {
220-
errorEl.textContent = t('login_error_empty');
221-
return;
222-
}
223-
224-
errorEl.textContent = Helpers.t('loading');
225-
VizAccount.login(account, key, function(err) {
226-
if (err) {
227-
if (err.message === 'account_not_found') errorEl.textContent = t('login_error_not_found');
228-
else if (err.message === 'invalid_regular_key') errorEl.textContent = t('login_error_wrong_key');
229-
else errorEl.textContent = err.message;
230-
return;
231-
}
232-
233-
_createCharacterAndFinish();
234-
});
235-
});
236-
}
237-
238-
return { render: render };
117+
return { render: render, startForAccount: startForAccount };
239118
})();

0 commit comments

Comments
 (0)