Skip to content

Commit 3165808

Browse files
author
web3blind
committed
Harden mobile layout and PWA audio
1 parent 91abdc6 commit 3165808

10 files changed

Lines changed: 248 additions & 29 deletions

File tree

app/assets/icons/icon-192.png

20.4 KB
Loading

app/assets/icons/icon-512.png

86.8 KB
Loading

app/css/main.css

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1433,3 +1433,162 @@ body {
14331433
vertical-align: middle;
14341434
line-height: 1.4;
14351435
}
1436+
1437+
1438+
/* ===== Mobile viewport containment and bottom tray hardening ===== */
1439+
html,
1440+
body,
1441+
.app-shell,
1442+
#app-main {
1443+
width: 100%;
1444+
max-width: 100%;
1445+
overflow-x: hidden;
1446+
}
1447+
1448+
.screen {
1449+
min-height: 100dvh;
1450+
max-width: 100vw;
1451+
overflow-x: hidden;
1452+
padding-left: max(12px, env(safe-area-inset-left));
1453+
padding-right: max(12px, env(safe-area-inset-right));
1454+
padding-bottom: calc(180px + env(safe-area-inset-bottom));
1455+
}
1456+
1457+
#bottom-nav.show {
1458+
display: grid;
1459+
grid-template-columns: repeat(5, minmax(0, 1fr));
1460+
align-items: stretch;
1461+
width: 100vw;
1462+
max-width: 100vw;
1463+
overflow: hidden;
1464+
}
1465+
1466+
.nav-tab {
1467+
min-width: 0;
1468+
max-width: 100%;
1469+
padding: 6px 2px;
1470+
gap: 1px;
1471+
line-height: 1.05;
1472+
}
1473+
1474+
.nav-icon {
1475+
font-size: 1.05rem;
1476+
margin-bottom: 1px;
1477+
}
1478+
1479+
.nav-label {
1480+
display: block;
1481+
max-width: 100%;
1482+
overflow: hidden;
1483+
text-overflow: ellipsis;
1484+
white-space: nowrap;
1485+
font-size: 0.62rem;
1486+
}
1487+
1488+
.chronicle-tabs,
1489+
.market-tabs,
1490+
.craft-tabs,
1491+
.arena-tabs,
1492+
.settings-toggle-group,
1493+
.region-travel-options,
1494+
.quest-actions,
1495+
.duel-actions,
1496+
.guild-actions,
1497+
.sell-item-controls,
1498+
.chronicle-compose-footer,
1499+
.modal-actions {
1500+
max-width: 100%;
1501+
}
1502+
1503+
@media (max-width: 480px) {
1504+
.chronicle-tabs,
1505+
.market-tabs,
1506+
.craft-tabs,
1507+
.arena-tabs {
1508+
display: grid;
1509+
grid-template-columns: 1fr;
1510+
gap: 8px;
1511+
}
1512+
1513+
.chronicle-tabs .btn,
1514+
.market-tabs .btn,
1515+
.craft-tabs .btn,
1516+
.arena-tabs .btn,
1517+
.quest-actions .btn,
1518+
.duel-actions .btn,
1519+
.guild-actions .btn {
1520+
width: 100%;
1521+
min-width: 0;
1522+
min-height: var(--touch-target);
1523+
white-space: normal;
1524+
overflow-wrap: anywhere;
1525+
}
1526+
1527+
.settings-toggle-group,
1528+
.region-travel-options,
1529+
.quest-actions,
1530+
.duel-actions,
1531+
.guild-actions,
1532+
.sell-item-controls,
1533+
.chronicle-compose-footer,
1534+
.modal-actions,
1535+
.listing-footer,
1536+
.region-header,
1537+
.char-header,
1538+
.arena-player-card,
1539+
.market-my-listing,
1540+
.consumable-item {
1541+
flex-wrap: wrap;
1542+
}
1543+
1544+
.btn-sm {
1545+
min-height: 44px;
1546+
}
1547+
1548+
.screen h1 {
1549+
overflow-wrap: anywhere;
1550+
}
1551+
}
1552+
1553+
@media (max-width: 360px) {
1554+
#bottom-nav.show {
1555+
grid-template-columns: repeat(3, minmax(0, 1fr));
1556+
}
1557+
1558+
.screen {
1559+
padding-bottom: calc(260px + env(safe-area-inset-bottom));
1560+
}
1561+
1562+
.nav-label {
1563+
font-size: 0.58rem;
1564+
}
1565+
}
1566+
1567+
1568+
/* Keep recipe rows inside narrow mobile screens. */
1569+
.recipe-card {
1570+
max-width: 100%;
1571+
flex-wrap: wrap;
1572+
}
1573+
1574+
.recipe-info {
1575+
min-width: 0;
1576+
}
1577+
1578+
.recipe-status {
1579+
min-width: 0;
1580+
overflow-wrap: anywhere;
1581+
}
1582+
1583+
.recipe-locked-text,
1584+
.recipe-ready {
1585+
overflow-wrap: anywhere;
1586+
}
1587+
1588+
@media (max-width: 480px) {
1589+
.recipe-status {
1590+
flex: 1 1 100%;
1591+
text-align: left;
1592+
padding-left: 44px;
1593+
}
1594+
}

app/index.html

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
<meta name="apple-mobile-web-app-capable" content="yes">
99
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
1010
<title>Viz Magic</title>
11-
<link rel="manifest" href="manifest.json">
12-
<link rel="icon" type="image/png" sizes="192x192" href="assets/icons/icon-192.png">
13-
<link rel="apple-touch-icon" href="assets/icons/icon-192.png">
14-
<link rel="stylesheet" href="css/main.css?v=20260711c">
11+
<link rel="manifest" href="manifest.json?v=20260712b">
12+
<link rel="icon" type="image/png" sizes="192x192" href="assets/icons/icon-192.png?v=20260712b">
13+
<link rel="apple-touch-icon" href="assets/icons/icon-192.png?v=20260712b">
14+
<link rel="stylesheet" href="css/main.css?v=20260712b">
1515
<link rel="stylesheet" href="css/themes.css">
1616
<link rel="stylesheet" href="css/accessibility.css">
1717
</head>
@@ -174,13 +174,13 @@
174174
<script src="js/ui/components/toast.js?v=20260711b"></script>
175175
<script src="js/ui/components/modal.js"></script>
176176
<script src="js/ui/components/nav.js"></script>
177-
<script src="js/ui/components/battle-narrator.js?v=20260711d"></script>
177+
<script src="js/ui/components/battle-narrator.js?v=20260712b"></script>
178178

179179
<!-- UI Screens -->
180180
<script src="js/ui/screens/landing.js"></script>
181181
<script src="js/ui/screens/login.js?v=20260711a"></script>
182182
<script src="js/ui/screens/onboarding.js"></script>
183-
<script src="js/ui/screens/home.js?v=20260712a"></script>
183+
<script src="js/ui/screens/home.js?v=20260712b"></script>
184184
<script src="js/ui/screens/character.js?v=20260621l"></script>
185185
<script src="js/ui/screens/hunt.js"></script>
186186
<script src="js/ui/screens/inventory.js?v=20260621l"></script>
@@ -193,7 +193,7 @@
193193
<script src="js/ui/screens/crafting.js?v=20260621j"></script>
194194
<script src="js/ui/screens/quests.js?v=20260621n"></script>
195195
<script src="js/ui/screens/world-boss.js"></script>
196-
<script src="js/ui/screens/settings.js?v=20260711d"></script>
196+
<script src="js/ui/screens/settings.js?v=20260712b"></script>
197197
<script src="js/ui/screens/help.js?v=20260711d"></script>
198198
<script src="js/ui/screens/leaderboard.js"></script>
199199

app/js/ui/components/battle-narrator.js

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ var BattleNarrator = (function() {
2323
enabled = true;
2424
}
2525

26-
// Create narrator live region
26+
// Create narrator live region. Keep it as one persistent status region:
27+
// mobile screen readers announce text replacement more reliably than
28+
// appended children inside role=log.
2729
if (!narratorEl) {
2830
narratorEl = document.createElement('div');
2931
narratorEl.id = 'battle-narrator';
30-
narratorEl.setAttribute('role', 'log');
32+
narratorEl.setAttribute('role', 'status');
3133
narratorEl.setAttribute('aria-live', 'polite');
32-
narratorEl.setAttribute('aria-atomic', 'false');
33-
narratorEl.setAttribute('aria-relevant', 'additions');
34+
narratorEl.setAttribute('aria-atomic', 'true');
3435
narratorEl.className = 'sr-only';
3536
narratorEl.setAttribute('aria-label', Helpers.t('narrator_label'));
3637
document.body.appendChild(narratorEl);
@@ -66,17 +67,32 @@ var BattleNarrator = (function() {
6667
if (!enabled || !message) return;
6768
if (!narratorEl) init();
6869

69-
narratorEl.setAttribute('aria-live', priority || 'polite');
70+
var mode = priority || 'polite';
71+
narratorEl.setAttribute('aria-live', mode);
72+
narratorEl.setAttribute('role', mode === 'assertive' ? 'alert' : 'status');
7073

71-
// Append new message as paragraph
72-
var p = document.createElement('p');
73-
p.textContent = message;
74-
narratorEl.appendChild(p);
74+
// Force a DOM text change so TalkBack/VoiceOver do not ignore repeated
75+
// announcements after navigation or re-render.
76+
narratorEl.textContent = '';
77+
setTimeout(function() {
78+
if (narratorEl) narratorEl.textContent = message;
79+
}, 25);
7580

76-
// Keep only last 10 messages
77-
while (narratorEl.children.length > 10) {
78-
narratorEl.removeChild(narratorEl.firstChild);
79-
}
81+
_speak(message);
82+
}
83+
84+
function _speak(message) {
85+
if (!message || typeof window === 'undefined' || !window.speechSynthesis || !window.SpeechSynthesisUtterance) return;
86+
try {
87+
var utterance = new SpeechSynthesisUtterance(message);
88+
var lang = (Helpers.getCurrentLang && Helpers.getCurrentLang() === 'en') ? 'en-US' : 'ru-RU';
89+
utterance.lang = lang;
90+
utterance.rate = 1;
91+
utterance.pitch = 1;
92+
utterance.volume = 1;
93+
window.speechSynthesis.cancel();
94+
window.speechSynthesis.speak(utterance);
95+
} catch (e) {}
8096
}
8197

8298
/**

app/js/ui/screens/home.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ var HomeScreen = (function() {
107107
VizAccount.getAccount(user, function(err, accountData) {
108108
if (!err && accountData) {
109109
var currentEnergy = VizAccount.calculateCurrentEnergy(accountData);
110-
ProgressBar.update('mana-bar', currentEnergy / 100, 100, Math.floor(currentEnergy / 100), 100);
110+
ProgressBar.update('mana-bar', currentEnergy / 100, 100);
111111
}
112112
});
113113
}

app/js/ui/screens/settings.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ var SettingsScreen = (function() {
3333
} catch (e) {}
3434
}
3535

36+
function _setStoredNumber(key, value) {
37+
try {
38+
localStorage.setItem(STORAGE_PREFIX + key, String(value));
39+
} catch (e) {}
40+
}
41+
3642
function render() {
3743
var t = Helpers.t;
3844
var el = Helpers.$('screen-settings');
@@ -43,6 +49,7 @@ var SettingsScreen = (function() {
4349
var highContrast = _getStoredBool('high_contrast', false);
4450
var reducedMotion = _getStoredBool('reduced_motion', false);
4551
var sfxVolume = Math.round(_getStoredNumber('sfx_volume', 0.5) * 100);
52+
var musicVolume = Math.round(_getStoredNumber('music_volume', 0.5) * 100);
4653
var narratorEnabled = (typeof BattleNarrator !== 'undefined' && BattleNarrator.isEnabled) ? BattleNarrator.isEnabled() : _getStoredBool('battle_narrator', false);
4754
if (typeof SoundManager !== 'undefined') SoundManager.setVolume(sfxVolume / 100);
4855

@@ -63,7 +70,7 @@ var SettingsScreen = (function() {
6370
'<section class="settings-section" aria-label="' + t('settings_sound') + '">' +
6471
'<h2>' + t('settings_sound') + '</h2>' +
6572
_renderSlider('sfx-volume', t('settings_sfx'), sfxVolume) +
66-
_renderSlider('music-volume', t('settings_music'), 50) +
73+
_renderSlider('music-volume', t('settings_music'), musicVolume) +
6774
_renderToggle('narrator-toggle', t('narrator_toggle'), narratorEnabled) +
6875
'<button type="button" class="btn btn-secondary btn-sm" id="btn-test-narrator">' + t('narrator_test') + '</button>' +
6976
'<div class="settings-field">' +
@@ -165,6 +172,11 @@ var SettingsScreen = (function() {
165172
SoundManager.setVolume(this.value / 100);
166173
});
167174

175+
var musicSlider = el.querySelector('#music-volume');
176+
if (musicSlider) musicSlider.addEventListener('input', function() {
177+
_setStoredNumber('music_volume', this.value / 100);
178+
});
179+
168180
// Toggle buttons
169181
var toggleBtns = el.querySelectorAll('.settings-toggle-btn');
170182
for (var i = 0; i < toggleBtns.length; i++) {

app/manifest.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "Viz Magic",
33
"short_name": "VizMagic",
4-
"description": "Browser RPG built on the VIZ blockchain",
4+
"description": "Viz Magic — accessible browser RPG on the VIZ blockchain",
55
"start_url": "/",
66
"display": "standalone",
77
"theme_color": "#0d1117",
@@ -21,5 +21,10 @@
2121
"purpose": "any maskable"
2222
}
2323
],
24-
"categories": ["games", "entertainment"]
25-
}
24+
"categories": [
25+
"games",
26+
"entertainment"
27+
],
28+
"id": "/",
29+
"orientation": "portrait-primary"
30+
}

app/sw.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
// Viz Magic — Service Worker
2-
var CACHE_NAME = 'viz-magic-v33';
2+
var CACHE_NAME = 'viz-magic-v34';
33
var ASSETS = [
44
'/',
55
'/index.html',
6+
'/manifest.json',
7+
'/assets/icons/icon-192.png',
8+
'/assets/icons/icon-512.png',
69
'/css/main.css',
710
'/css/themes.css',
811
'/css/accessibility.css',

0 commit comments

Comments
 (0)