@hezzlgames/sdk 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +415 -0
- package/bin/hezzl-sdk.mjs +150 -0
- package/demo/centre.html +481 -0
- package/demo/economy.json +8 -0
- package/demo/game.html +380 -0
- package/demo/hezzl.json +18 -0
- package/hezzl-sdk.js +767 -0
- package/index.cjs +7 -0
- package/index.d.ts +180 -0
- package/index.mjs +7 -0
- package/package.json +56 -0
package/demo/game.html
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="ru">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
6
|
+
<title>Пример игры на Hezzl SDK</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root{ --accent:#7d75ff; --bg:#f4f5fb; --card:#fff; --ink:#12142b; --muted:#6b7089; --line:#e6e8f2; --coin:#d98b00 }
|
|
9
|
+
:root[data-theme="dark"]{ --bg:#0e1018; --card:#171a27; --ink:#eef0fa; --muted:#9aa0bd; --line:#262a3d; --coin:#ffb020 }
|
|
10
|
+
*{box-sizing:border-box}
|
|
11
|
+
body{
|
|
12
|
+
margin:0; min-height:100vh; min-height:100dvh;
|
|
13
|
+
display:flex; flex-direction:column; gap:14px;
|
|
14
|
+
/* верхние 56 пунктов принадлежат центру — раздел 4.1 */
|
|
15
|
+
padding:calc(56px + env(safe-area-inset-top)) 16px calc(16px + env(safe-area-inset-bottom));
|
|
16
|
+
background:var(--bg); color:var(--ink);
|
|
17
|
+
font:15px/1.4 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
|
|
18
|
+
}
|
|
19
|
+
h1{margin:0;font-size:19px;letter-spacing:-.01em}
|
|
20
|
+
h2{margin:0 0 8px;font-size:15px}
|
|
21
|
+
.card{background:var(--card);border-radius:14px;padding:14px;box-shadow:0 1px 3px rgba(16,20,50,.08);position:relative}
|
|
22
|
+
.row{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px;align-items:center}
|
|
23
|
+
button{
|
|
24
|
+
font:inherit;font-weight:600;cursor:pointer;
|
|
25
|
+
padding:9px 14px;border:0;border-radius:10px;
|
|
26
|
+
background:var(--accent);color:#fff;
|
|
27
|
+
}
|
|
28
|
+
button:disabled{opacity:.5;cursor:default}
|
|
29
|
+
button.ghost{background:transparent;color:var(--ink);border:1px solid var(--line)}
|
|
30
|
+
dl{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;margin:0;font-size:13.5px}
|
|
31
|
+
dt{color:var(--muted)}
|
|
32
|
+
dd{margin:0;font-variant-numeric:tabular-nums}
|
|
33
|
+
.score{font-size:34px;font-weight:700;letter-spacing:-.02em;margin:2px 0 0}
|
|
34
|
+
#log{
|
|
35
|
+
font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;
|
|
36
|
+
white-space:pre-wrap; color:var(--muted);
|
|
37
|
+
max-height:190px; overflow:auto; margin:10px 0 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* ── магазин ─────────────────────────────────────────────────────── */
|
|
41
|
+
.purse{display:flex;justify-content:space-between;align-items:center;gap:10px}
|
|
42
|
+
.coins{font-weight:700;color:var(--coin);font-variant-numeric:tabular-nums;font-size:17px}
|
|
43
|
+
.coins.bump{animation:bump .5s ease-in-out}
|
|
44
|
+
@keyframes bump{40%{transform:scale(1.25)}}
|
|
45
|
+
.bag{display:flex;gap:12px;flex-wrap:wrap;margin-top:8px;font-size:13.5px;color:var(--muted)}
|
|
46
|
+
.bag b{color:var(--ink);font-variant-numeric:tabular-nums}
|
|
47
|
+
.good{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:9px 0;border-top:1px solid var(--line)}
|
|
48
|
+
.good:first-child{border-top:0}
|
|
49
|
+
.good small{display:block;color:var(--muted)}
|
|
50
|
+
.good button{white-space:nowrap}
|
|
51
|
+
.note{margin:8px 0 0;font-size:13px;color:var(--muted);min-height:1.3em}
|
|
52
|
+
.note.warn{color:#c7401d}
|
|
53
|
+
/* Прилёт награды в инвентарь: одна летящая плашка, 500 мс по дуге
|
|
54
|
+
(17.1, таблица длительностей). Не окно и не «забрать». */
|
|
55
|
+
.fly{
|
|
56
|
+
position:fixed;z-index:9;pointer-events:none;
|
|
57
|
+
padding:6px 10px;border-radius:999px;background:var(--accent);color:#fff;font-weight:700;font-size:13px;
|
|
58
|
+
transition:transform .5s cubic-bezier(.4,0,.2,1),opacity .2s .35s;
|
|
59
|
+
}
|
|
60
|
+
@media (prefers-reduced-motion:reduce){ .fly{transition:none} .coins.bump{animation:none} }
|
|
61
|
+
</style>
|
|
62
|
+
</head>
|
|
63
|
+
<body>
|
|
64
|
+
|
|
65
|
+
<h1>Пример игры на Hezzl SDK</h1>
|
|
66
|
+
|
|
67
|
+
<div class="card">
|
|
68
|
+
<p class="score" id="score">0</p>
|
|
69
|
+
<div class="row">
|
|
70
|
+
<button id="tap">Заработать очки</button>
|
|
71
|
+
<button class="ghost" id="over">Закончить партию</button>
|
|
72
|
+
<button class="ghost" id="again">Заново</button>
|
|
73
|
+
<button class="ghost" id="hint" title="списание валюты центра, кейс без сервера">Подсказка · 10 <span id="hint-icon">⬡</span></button>
|
|
74
|
+
</div>
|
|
75
|
+
<p class="note" id="hint-note"></p>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div class="card" id="shop" hidden>
|
|
79
|
+
<div class="purse">
|
|
80
|
+
<h2 style="margin:0">Магазин</h2>
|
|
81
|
+
<div class="row" style="margin:0">
|
|
82
|
+
<span class="coins" id="coins">—</span>
|
|
83
|
+
<button class="ghost" id="topup">Пополнить</button>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="bag">
|
|
87
|
+
<span>бустеры <b id="bag-boosters">0</b></span>
|
|
88
|
+
<span>жизни <b id="bag-lives">3</b></span>
|
|
89
|
+
<span>подсказки <b id="bag-hints">0</b></span>
|
|
90
|
+
<span>скин <b id="bag-skin">обычный</b></span>
|
|
91
|
+
</div>
|
|
92
|
+
<div id="goods" style="margin-top:8px"></div>
|
|
93
|
+
<p class="note" id="shop-note"></p>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="card">
|
|
97
|
+
<dl>
|
|
98
|
+
<dt>режим</dt><dd id="mode">…</dd>
|
|
99
|
+
<dt>центр умеет</dt><dd id="can">…</dd>
|
|
100
|
+
<dt>язык</dt><dd id="lang">—</dd>
|
|
101
|
+
<dt>тема</dt><dd id="theme">—</dd>
|
|
102
|
+
<dt>цвет</dt><dd id="accent">—</dd>
|
|
103
|
+
<dt>звук</dt><dd id="sound">—</dd>
|
|
104
|
+
<dt>вибрация</dt><dd id="haptics">—</dd>
|
|
105
|
+
<dt>метка</dt><dd id="ref">—</dd>
|
|
106
|
+
<dt>сохранено</dt><dd id="saved">—</dd>
|
|
107
|
+
</dl>
|
|
108
|
+
<div class="row">
|
|
109
|
+
<button class="ghost" id="share">Поделиться</button>
|
|
110
|
+
<button class="ghost" id="support">Поддержка</button>
|
|
111
|
+
<button class="ghost" id="quit">Выйти</button>
|
|
112
|
+
<button class="ghost" id="who">Кто я</button>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<pre id="log"></pre>
|
|
117
|
+
|
|
118
|
+
<script src="../hezzl-sdk.js"></script>
|
|
119
|
+
<script>
|
|
120
|
+
(function () {
|
|
121
|
+
var score = 0, muted = false, haptics = true, paused = false;
|
|
122
|
+
var bag = { boosters: 0, lives: 3, hints: 0, skin: 'обычный' };
|
|
123
|
+
var el = function (id) { return document.getElementById(id); };
|
|
124
|
+
function log(line) { el('log').textContent = line + '\n' + el('log').textContent; }
|
|
125
|
+
function show(id, value) { el(id).textContent = value; }
|
|
126
|
+
|
|
127
|
+
/* ── что игра умеет: обработчики и есть объявление умений ────────── */
|
|
128
|
+
Hezzl.init({
|
|
129
|
+
id: 'sdk-demo',
|
|
130
|
+
version: '1.1.0',
|
|
131
|
+
modes: ['quick'],
|
|
132
|
+
orientation: 'any',
|
|
133
|
+
debug: true,
|
|
134
|
+
on: {
|
|
135
|
+
mute: function (flag) { muted = flag; show('sound', flag ? 'выключен' : 'включён'); log('команда mute: ' + flag); },
|
|
136
|
+
haptics: function (flag) { haptics = flag; show('haptics', flag ? 'включена' : 'выключена'); log('команда haptics: ' + flag); },
|
|
137
|
+
pause: function () { paused = true; log('команда pause'); },
|
|
138
|
+
resume: function () { paused = false; log('команда resume'); },
|
|
139
|
+
restart: function () { score = 0; render(); log('команда restart'); },
|
|
140
|
+
setlang: function (lang) { show('lang', lang); log('команда setlang: ' + lang); },
|
|
141
|
+
settheme: function (theme) { paint(theme); log('команда settheme: ' + theme); },
|
|
142
|
+
setaccent: function (hex) { document.documentElement.style.setProperty('--accent', '#' + hex); show('accent', '#' + hex); log('команда setaccent: ' + hex); },
|
|
143
|
+
signedin: function (id) { log('вошёл: ' + id); refreshShop(); },
|
|
144
|
+
signedout: function () { log('вышел'); coins(0); },
|
|
145
|
+
profile: function (p) { log('профиль: ' + (p && p.name || '—')); },
|
|
146
|
+
/* Валюта центра (7.9). balance приходит и в ответ, и сам по себе:
|
|
147
|
+
награда за задание, покупка в кошельке — счётчик обновляется
|
|
148
|
+
сразу, игра ничего не опрашивает. */
|
|
149
|
+
balance: function (amount) { coins(amount); log('баланс: ' + amount); },
|
|
150
|
+
walletclosed: function (amount, d) { log('кошелёк закрыт, баланс ' + amount + (d.bought ? ', пополнил' : '')); }
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
function paint(theme) {
|
|
155
|
+
if (theme) document.documentElement.setAttribute('data-theme', theme);
|
|
156
|
+
else document.documentElement.removeAttribute('data-theme');
|
|
157
|
+
show('theme', theme || 'система');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* ── стартовые настройки: адрес → своё → браузер ─────────────────── */
|
|
161
|
+
var s = Hezzl.settings;
|
|
162
|
+
show('lang', Hezzl.resolve('lang', '', function () {
|
|
163
|
+
return (navigator.language || 'ru').slice(0, 2) === 'en' ? 'en' : 'ru';
|
|
164
|
+
}));
|
|
165
|
+
paint(s.theme);
|
|
166
|
+
if (s.accent) { document.documentElement.style.setProperty('--accent', '#' + s.accent); show('accent', '#' + s.accent); }
|
|
167
|
+
muted = s.muted; show('sound', muted ? 'выключен' : 'включён');
|
|
168
|
+
haptics = s.haptics; show('haptics', haptics ? 'включена' : 'выключена');
|
|
169
|
+
show('ref', s.ref || '—');
|
|
170
|
+
|
|
171
|
+
/* ── прогресс: через центр, а без него — своё хранилище ──────────── */
|
|
172
|
+
Hezzl.storage.get('best').then(function (best) {
|
|
173
|
+
show('saved', best === null ? 'записи нет' : 'рекорд ' + best);
|
|
174
|
+
}, function (e) {
|
|
175
|
+
/* Отказ типизирован — ветвимся по reason, а не по тексту (7.6) */
|
|
176
|
+
show('saved', 'не прочитать: ' + e.reason);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
function render() { show('score', score); }
|
|
180
|
+
|
|
181
|
+
/* ── партия ──────────────────────────────────────────────────────── */
|
|
182
|
+
el('tap').onclick = function () {
|
|
183
|
+
if (paused) { log('на паузе'); return; }
|
|
184
|
+
if (score === 0) Hezzl.gameStart({ mode: 'quick' });
|
|
185
|
+
score += 10;
|
|
186
|
+
render();
|
|
187
|
+
Hezzl.score(score); /* лишнее SDK придержит сам */
|
|
188
|
+
if (score % 100 === 0) Hezzl.milestone('combo', score / 100);
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
el('over').onclick = function () {
|
|
192
|
+
Hezzl.gameOver({ score: score }); /* финальный счёт уходит мимо лимита */
|
|
193
|
+
Hezzl.storage.set('best', score, { sync: true, schema: 1 }).then(function () {
|
|
194
|
+
show('saved', 'рекорд ' + score);
|
|
195
|
+
log('записано');
|
|
196
|
+
}, function (e) {
|
|
197
|
+
show('saved', 'не записать: ' + e.reason);
|
|
198
|
+
log('запись отказала: ' + e.reason);
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
el('again').onclick = function () { score = 0; render(); log('заново'); };
|
|
203
|
+
|
|
204
|
+
el('share').onclick = function () { Hezzl.share({ text: 'Набрал ' + score + ' очков' }); };
|
|
205
|
+
el('support').onclick = function () { Hezzl.support({ message: 'Пример обращения', state: { score: score } }); };
|
|
206
|
+
el('quit').onclick = function () { Hezzl.exit(); };
|
|
207
|
+
el('who').onclick = function () {
|
|
208
|
+
Hezzl.profile(['name', 'avatar']).then(function (answer) {
|
|
209
|
+
log('профиль: ' + JSON.stringify(answer.profile || null));
|
|
210
|
+
}, function (e) { log('профиль не пришёл: ' + e.reason); });
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/* ── магазин: валюта центра ──────────────────────────────────────── */
|
|
214
|
+
function icon() { var c = Hezzl.currency(); return c && c.icon ? c.icon : '⬡'; }
|
|
215
|
+
|
|
216
|
+
function coins(amount) {
|
|
217
|
+
var node = el('coins');
|
|
218
|
+
node.textContent = (amount === null || amount === undefined ? '—' : amount) + ' ' + icon();
|
|
219
|
+
node.classList.remove('bump'); void node.offsetWidth; node.classList.add('bump');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function renderBag() {
|
|
223
|
+
show('bag-boosters', bag.boosters); show('bag-lives', bag.lives);
|
|
224
|
+
show('bag-hints', bag.hints); show('bag-skin', bag.skin);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* Прилёт награды: плашка стартует от кнопки и садится на строку
|
|
228
|
+
инвентаря. Счётчик меняется в момент посадки, не раньше (17.1). */
|
|
229
|
+
function fly(from, text, toId, then) {
|
|
230
|
+
var chip = document.createElement('span');
|
|
231
|
+
chip.className = 'fly';
|
|
232
|
+
chip.textContent = text;
|
|
233
|
+
var a = from.getBoundingClientRect(), b = el(toId).getBoundingClientRect();
|
|
234
|
+
chip.style.left = a.left + 'px'; chip.style.top = a.top + 'px';
|
|
235
|
+
document.body.appendChild(chip);
|
|
236
|
+
requestAnimationFrame(function () {
|
|
237
|
+
chip.style.transform = 'translate(' + (b.left - a.left) + 'px,' + (b.top - a.top) + 'px) scale(.8)';
|
|
238
|
+
chip.style.opacity = '0';
|
|
239
|
+
});
|
|
240
|
+
setTimeout(function () { chip.remove(); then(); }, 520);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Что сказать человеку на отказ. Ветвимся по reason (7.9): текст
|
|
244
|
+
центра — не для показа. cancelled молчит: человек сам передумал. */
|
|
245
|
+
function excuse(e) {
|
|
246
|
+
if (e.reason === 'insufficient') return 'Не хватает ' + e.need + ' ' + icon();
|
|
247
|
+
if (e.reason === 'cancelled') return '';
|
|
248
|
+
if (e.reason === 'signin') return 'Войдите в центр, чтобы покупать';
|
|
249
|
+
if (e.reason === 'notfound' || e.reason === 'disabled') return 'Товара больше нет';
|
|
250
|
+
if (e.reason === 'unavailable') return 'Магазин недоступен';
|
|
251
|
+
return 'Не получилось, попробуйте ещё раз';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function say(id, text, warn) {
|
|
255
|
+
var node = el(id);
|
|
256
|
+
node.textContent = text;
|
|
257
|
+
node.className = 'note' + (warn && text ? ' warn' : '');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function grant(award, from) {
|
|
261
|
+
/* Награда — из ответа центра, а не из своих ожиданий: цену
|
|
262
|
+
и состав товара знает наш каталог, игра их не хранит. */
|
|
263
|
+
var parts = [];
|
|
264
|
+
for (var key in award) if (award.hasOwnProperty(key)) parts.push(key);
|
|
265
|
+
(function next(i) {
|
|
266
|
+
if (i >= parts.length) return;
|
|
267
|
+
var key = parts[i], value = award[key];
|
|
268
|
+
var label = key === 'boosters' ? '+' + value + ' бустер' : key === 'lives' ? '+' + value + ' жизней'
|
|
269
|
+
: key === 'skin' ? 'скин «' + value + '»' : key + ' ' + value;
|
|
270
|
+
var to = key === 'boosters' ? 'bag-boosters' : key === 'lives' ? 'bag-lives' : key === 'skin' ? 'bag-skin' : 'bag-boosters';
|
|
271
|
+
fly(from, label, to, function () {
|
|
272
|
+
if (key === 'skin') bag.skin = value;
|
|
273
|
+
else if (typeof bag[key] === 'number') bag[key] += value;
|
|
274
|
+
renderBag();
|
|
275
|
+
next(i + 1);
|
|
276
|
+
});
|
|
277
|
+
})(0);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/* Кейс 1 — товар из нашего каталога. Центр сам спросит подтверждение
|
|
281
|
+
и сам покажет кошелёк, если не хватает; нам остаётся ждать. */
|
|
282
|
+
function buy(good, button) {
|
|
283
|
+
button.disabled = true;
|
|
284
|
+
say('shop-note', '');
|
|
285
|
+
Hezzl.buy(good.id).then(function (done) {
|
|
286
|
+
coins(done.amount);
|
|
287
|
+
grant(done.award || {}, button);
|
|
288
|
+
log('куплено #' + done.goodId + ', выдано ' + JSON.stringify(done.award) + ', осталось ' + done.amount);
|
|
289
|
+
}, function (e) {
|
|
290
|
+
say('shop-note', excuse(e), true);
|
|
291
|
+
log('покупка #' + good.id + ' не прошла: ' + e.reason + (e.need ? ', не хватает ' + e.need : ''));
|
|
292
|
+
}).then(function () { button.disabled = false; });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function renderGoods(goods) {
|
|
296
|
+
var root = el('goods');
|
|
297
|
+
root.innerHTML = '';
|
|
298
|
+
goods.forEach(function (g) {
|
|
299
|
+
var row = document.createElement('div');
|
|
300
|
+
row.className = 'good';
|
|
301
|
+
var text = document.createElement('div');
|
|
302
|
+
text.innerHTML = '<b></b><small></small>';
|
|
303
|
+
text.querySelector('b').textContent = g.title;
|
|
304
|
+
text.querySelector('small').textContent = describe(g.award);
|
|
305
|
+
var button = document.createElement('button');
|
|
306
|
+
button.textContent = g.price + ' ' + icon();
|
|
307
|
+
button.onclick = function () { buy(g, button); };
|
|
308
|
+
row.appendChild(text); row.appendChild(button);
|
|
309
|
+
root.appendChild(row);
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function describe(award) {
|
|
314
|
+
var out = [];
|
|
315
|
+
for (var k in award) if (award.hasOwnProperty(k)) out.push(k + ': ' + award[k]);
|
|
316
|
+
return out.join(', ') || '—';
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function refreshShop() {
|
|
320
|
+
if (!Hezzl.can('balance')) return;
|
|
321
|
+
Hezzl.balance().then(function (a) {
|
|
322
|
+
coins(a.signedIn ? a.amount : null);
|
|
323
|
+
if (!a.signedIn) say('shop-note', 'Войдите, чтобы видеть баланс');
|
|
324
|
+
}, function (e) { log('баланс не пришёл: ' + e.reason); });
|
|
325
|
+
Hezzl.goods().then(function (a) { renderGoods(a.goods || []); },
|
|
326
|
+
function (e) { log('товары не пришли: ' + e.reason); });
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
el('topup').onclick = function () {
|
|
330
|
+
Hezzl.wallet({ reason: 'Магазин' }).then(function (w) { coins(w.amount); },
|
|
331
|
+
function (e) { log('кошелёк не открылся: ' + e.reason); });
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
/* Кейс 2 — товар свой, цену назначает игра: подсказка за 10.
|
|
335
|
+
Списание идёт через центр, он и подтверждает. Выдаём только
|
|
336
|
+
после ответа ok, а не по нажатию. */
|
|
337
|
+
el('hint').onclick = function () {
|
|
338
|
+
var button = this;
|
|
339
|
+
if (!Hezzl.can('balance')) { say('hint-note', 'Подсказки продаются только в центре', true); return; }
|
|
340
|
+
button.disabled = true;
|
|
341
|
+
say('hint-note', '');
|
|
342
|
+
Hezzl.spend(10, { item: 'Подсказка' }).then(function (done) {
|
|
343
|
+
coins(done.amount);
|
|
344
|
+
fly(button, '+1 подсказка', 'bag-hints', function () { bag.hints++; renderBag(); });
|
|
345
|
+
log('списано ' + done.spent + ', осталось ' + done.amount);
|
|
346
|
+
}, function (e) {
|
|
347
|
+
say('hint-note', excuse(e), true);
|
|
348
|
+
log('подсказка не куплена: ' + e.reason);
|
|
349
|
+
}).then(function () { button.disabled = false; });
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
/* ── готовы принимать ввод ───────────────────────────────────────── */
|
|
353
|
+
render();
|
|
354
|
+
renderBag();
|
|
355
|
+
Hezzl.ready();
|
|
356
|
+
|
|
357
|
+
/* Режим известен не сразу: ждём рукопожатия или его отсутствия. */
|
|
358
|
+
var tick = setInterval(function () {
|
|
359
|
+
show('mode', Hezzl.mode());
|
|
360
|
+
if (Hezzl.mode() === 'unknown') return;
|
|
361
|
+
clearInterval(tick);
|
|
362
|
+
var can = ['share', 'support', 'exit', 'auth', 'profile', 'storage', 'balance'].filter(function (x) { return Hezzl.can(x); });
|
|
363
|
+
show('can', can.length ? can.join(', ') : '—');
|
|
364
|
+
/* Кнопки, зависящие от центра, показываем только после ack (3.8) */
|
|
365
|
+
['share', 'support', 'quit', 'who'].forEach(function (id) {
|
|
366
|
+
el(id).style.display = can.length ? '' : 'none';
|
|
367
|
+
});
|
|
368
|
+
/* Магазин — только если у центра есть валюта. Иначе его нет вовсе:
|
|
369
|
+
кнопка «купить» без магазина за ней хуже отсутствующей. */
|
|
370
|
+
el('shop').hidden = !Hezzl.can('balance');
|
|
371
|
+
el('hint').style.display = Hezzl.can('balance') ? '' : 'none';
|
|
372
|
+
show('hint-icon', icon());
|
|
373
|
+
refreshShop();
|
|
374
|
+
log('режим: ' + Hezzl.mode());
|
|
375
|
+
}, 100);
|
|
376
|
+
})();
|
|
377
|
+
</script>
|
|
378
|
+
|
|
379
|
+
</body>
|
|
380
|
+
</html>
|
package/demo/hezzl.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "sdk-demo",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"sdk": 1,
|
|
5
|
+
"title": { "ru": "Пример игры на Hezzl SDK", "en": "Hezzl SDK sample" },
|
|
6
|
+
"entry": "game.html",
|
|
7
|
+
"modes": ["quick"],
|
|
8
|
+
"orientation": "any",
|
|
9
|
+
"supports": ["mute", "haptics", "pause", "resume", "restart", "setlang", "settheme", "setaccent"],
|
|
10
|
+
"schema": 1,
|
|
11
|
+
"offline": true,
|
|
12
|
+
"purchases": false,
|
|
13
|
+
"currency": true,
|
|
14
|
+
"profile": ["name", "avatar"],
|
|
15
|
+
"hosts": [],
|
|
16
|
+
"engine": "dom",
|
|
17
|
+
"field": "dom"
|
|
18
|
+
}
|