Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3ab0224
Align Explorer with shared Saito themes and move navigation
arpee Oct 8, 2026
88ce15e
Update saito-js and saito-wasm to 0.3.12
arpee Oct 8, 2026
e9f3635
Add Explorer header navigation menu component
arpee Oct 8, 2026
2721b83
Add Conquest battle playback, statistics and automatic phase skips
arpee Oct 8, 2026
449328b
Add Robots level-start saves, death effects and PNG cover support
arpee Oct 9, 2026
6eddc3a
fix: imperium patches
Oct 9, 2026
5852e13
fix: saitosign
Oct 10, 2026
5001bad
fix: saitosign
Oct 11, 2026
bdca8f7
Fix hamburger menu fill and transition on mobile
f8daniel Oct 12, 2026
2523880
fix: imperium, saitosign and paths
Oct 12, 2026
9ddb103
fix: Separate mobile game menu tabs from the close button
arpee Oct 12, 2026
1d7529b
fix: Separate mobile game menu tabs from the close button
arpee Oct 12, 2026
7f91d6b
Merge branch '2610/game-hamburger' into 2610/combo-menu
arpee Oct 12, 2026
6d26d4a
Merge branch '2610/combo-menu' into staging
arpee Oct 12, 2026
ffde9f8
fix: Include transcript browser runtime and model management
arpee Oct 12, 2026
5fa471a
refactor: Move transcript tests into the Node test directory
arpee Oct 12, 2026
394080f
feat: Minimize blockchain sync into a nonblocking progress pill
arpee Oct 12, 2026
c292d7d
added document file
arpee Oct 12, 2026
45d6d2e
fix: imperium
Oct 12, 2026
ad9465c
fix: css
Oct 12, 2026
c9c5932
style: Update Saito Store icons with angular bag shapes
arpee Oct 12, 2026
a88db7a
style: Standardize Chat actions and menus on Saito icons
arpee Oct 13, 2026
828b140
fix: nft import on key install
Oct 13, 2026
13d5a8e
fix: blockchain snapshot for nfts
Oct 13, 2026
3fd3c5c
fix: imperium images and base game balance
Oct 13, 2026
343a1cb
fix: removal of old files
Oct 13, 2026
de4346a
fix: tech icons
Oct 13, 2026
5b76afe
style: reformat game menu, sync UI, and blockchain expressions
arpee Oct 13, 2026
43ea4e0
Merge pull request #720 from SaitoTech/staging
arpee Oct 13, 2026
76090cd
chore: bump Rust version to 0.3.13
arpee Oct 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
24 changes: 24 additions & 0 deletions README/coding/SAITO-UI-COMPONENTS-AND-APPLICATION-INTERFACES.md
Original file line number Diff line number Diff line change
Expand Up @@ -1738,6 +1738,30 @@ If an overlay needs to notify its parent, callbacks are often sufficient.

Do not require the main module to contain every overlay's implementation merely because the overlay belongs to the module.

### Block sync viewer

`node/lib/saito/ui/saito-sync/saito-sync.js` owns the initial block sync viewer.
Its **minimize** button collapses the overlay into a nonblocking pill while
progress polling continues. Clicking the pill (or pressing Enter or Space while
it is focused) restores the full viewer. Both presentations disappear when sync
reaches the existing near-tip threshold.

The **minimize on startup** checkbox saves
`app.options.saito_sync.minimize_on_startup` through `app.storage.saveOptions()`.
Selecting it also minimizes the current viewer. The preference is read when the
viewer first renders, after options have loaded; it does not force the viewer to
appear if sync is already complete or the active page inhibits it. The
wallet fast-forward prompt still opens at full size when recovery is required.

Pill layout lives in `node/web/saito/css-imports/saito-sync.css`. It shares the
overlay surface and border, with a 3rem animation, a 2rem spinning logo, and 1rem padding. The
`--saito-sync-pill-top`, `--saito-sync-pill-right`, `--saito-sync-pill-bottom`, and
`--saito-sync-pill-left` properties control placement; the mobile media query
provides independent offsets and accounts for safe areas. On mobile, holding and
dragging the pill repositions it within the viewport; releasing a drag does not
expand it, while a tap still does. Its dragged position is kept for the current
viewer and adapts to viewport changes; it is not saved across page reloads.


## 48. Game UI Is a Special Case, Not a Different Philosophy

Expand Down
Empty file added imperium.js
Empty file.
2 changes: 2 additions & 0 deletions node/apps/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ async function init() {

const saito = new Saito({ mod_paths: mods_config.lite });
saito.saito_sync = new SaitoSync(saito, null);
// Temporary DevTools access for previewing the block sync overlay.
//(window as any).devSync = saito.saito_sync;
await saito.storage.initialize();

{
Expand Down
43 changes: 23 additions & 20 deletions node/lib/saito/ui/game-menu/game-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,15 +217,18 @@ class GameMenu {
});
}

const gameTitle = this.escapeHTML(this.game_mod?.name || 'Game');
const gameTitle = this.escapeHTML(
this.game_mod?.returnName?.() || this.game_mod?.name || 'Game'
);
let html = `<ul><li class="game-menu-mobile-header">
<button type="button" id="game-menu-close" class="game-menu-mobile-header-button">
<span class="game-menu-game-state"><i class="fa-solid fa-xmark" aria-hidden="true"></i><span>Close</span></span>
<span class="game-menu-saito-state"><i class="fa-solid fa-gamepad" aria-hidden="true"></i><span>${gameTitle}</span></span>
<button type="button" id="game-menu-game" class="game-menu-mobile-header-button" aria-pressed="true" title="${gameTitle}">
<span class="game-menu-mobile-header-label">${gameTitle}</span>
</button>
<button type="button" id="game-menu-saito" class="game-menu-mobile-header-button" aria-pressed="false">
<span class="game-menu-saito-logo" aria-hidden="true"></span><span>Saito</span>
</button>
<button type="button" id="game-menu-saito" class="game-menu-mobile-header-button">
<span class="game-menu-game-state"><img src="/saito/img/saito-cube.svg" alt=""><span>Saito</span></span>
<span class="game-menu-saito-state"><i class="fa-solid fa-xmark" aria-hidden="true"></i><span>Close</span></span>
<button type="button" id="game-menu-close" class="game-menu-mobile-header-button" aria-label="Close menu">
<i class="fa-solid fa-xmark" aria-hidden="true"></i>
</button>
</li>`;
for (let i = 0; i < this.icons.length; i++) {
Expand Down Expand Up @@ -269,7 +272,7 @@ class GameMenu {
!gameMenu.contains(e.target) &&
(!saitoMenu || !saitoMenu.contains(e.target))
) {
this.hideSubMenus(true);
this.hideAllMenus();
}
};
document.body.addEventListener('click', this.mobile_body_click_handler);
Expand Down Expand Up @@ -470,25 +473,23 @@ class GameMenu {
if (mobileClose) {
mobileClose.onclick = (e) => {
e.stopPropagation();
const menu = document.querySelector('.game-menu');
if (menu?.classList.contains('mobile-saito-visible')) {
this.closeMobileSaitoMenu();
} else {
this.hideSubMenus(true);
}
this.hideAllMenus();
};
}

const mobileGame = document.querySelector('#game-menu-game');
if (mobileGame) {
mobileGame.onclick = (e) => {
e.stopPropagation();
this.closeMobileSaitoMenu();
};
}

const mobileSaito = document.querySelector('#game-menu-saito');
if (mobileSaito) {
mobileSaito.onclick = (e) => {
e.stopPropagation();
const menu = document.querySelector('.game-menu');
if (menu?.classList.contains('mobile-saito-visible')) {
this.closeMobileSaitoMenu();
} else {
this.openMobileSaitoMenu();
}
this.openMobileSaitoMenu();
};
}
}
Expand All @@ -513,6 +514,8 @@ class GameMenu {
const menu = document.querySelector('.game-menu');
if (menu && this.isMobileView()) {
menu.classList.toggle('mobile-saito-visible', open);
menu.querySelector('#game-menu-game')?.setAttribute('aria-pressed', String(!open));
menu.querySelector('#game-menu-saito')?.setAttribute('aria-pressed', String(open));
}
}

Expand Down
2 changes: 1 addition & 1 deletion node/lib/saito/ui/modals/saito-link/saito-link.template.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module.exports = (app, link_self) => {
if (link_self.share_to_chat) {
let chat_mod = app.modules.returnModuleBySlug('chat');
if (chat_mod?.communityGroupName) {
html += `<div id="chat-invite-link" class="saito-modal-menu-option"><i class="fas fa-comments"></i><div class="saito-modal-menu-option-label">Share Link in ${chat_mod.communityGroupName}</div></div>`;
html += `<div id="chat-invite-link" class="saito-modal-menu-option"><i class="saito-chat-icon" aria-hidden="true"></i><div class="saito-modal-menu-option-label">Share Link in ${chat_mod.communityGroupName}</div></div>`;
}
}

Expand Down
25 changes: 25 additions & 0 deletions node/lib/saito/ui/saito-header/saito-header.js
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,16 @@ class SaitoHeader extends UIModTemplate {
};
}

//
// QR code is collapsed by default on phones (see saito-header.css)
//
if (document.getElementById('toggle-qrcode')) {
document.getElementById('toggle-qrcode').onclick = () => {
const wallet_info = document.querySelector('.wallet-info');
this.setQRCodeVisible(!wallet_info?.classList.contains('show-qrcode'));
};
}

if (document.getElementById('share-address')) {
const shareButton = document.getElementById('share-address');
if (typeof navigator.share !== 'function') {
Expand Down Expand Up @@ -924,12 +934,27 @@ class SaitoHeader extends UIModTemplate {
}
}

setQRCodeVisible(visible) {
const wallet_info = document.querySelector('.wallet-info');
const toggle = document.getElementById('toggle-qrcode');
if (wallet_info) {
wallet_info.classList.toggle('show-qrcode', visible);
}
if (toggle) {
const label = visible ? 'Hide QR code' : 'Show QR code';
toggle.setAttribute('aria-pressed', String(visible));
toggle.setAttribute('aria-label', label);
toggle.setAttribute('title', label);
}
}

hideMenu() {
const sidebar = document.querySelector('.saito-header-hamburger-contents');
if (!sidebar) {
return;
}
sidebar.classList.remove('show-wallet');
this.setQRCodeVisible(false);

const backdrop = document.querySelector('.saito-header-backdrop');
if (sidebar.classList.contains('show-menu')) {
Expand Down
11 changes: 7 additions & 4 deletions node/lib/saito/ui/saito-header/saito-header.template.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ let SaitoHeaderTemplate = (app, mod, headerClass) => {
<i class="fas fa-copy"></i>
</div>
<div class="pubkey-mobile-wrapper">
<button type="button" class="wallet-mobile-action" id="toggle-qrcode" aria-label="Show QR code" aria-pressed="false" title="Show QR code">
<i class="fa-solid fa-qrcode" aria-hidden="true"></i>
</button>
<button type="button" class="wallet-mobile-action" id="share-address" aria-label="Share wallet address" title="Share wallet address">
<i class="fa-solid fa-share-nodes" aria-hidden="true"></i>
</button>
Expand All @@ -44,25 +47,25 @@ let SaitoHeaderTemplate = (app, mod, headerClass) => {
<div class="wallet-btn-container">
<div class="saito-large-square-button" id="wallet-btn-withdraw">
<div class="saito-icon-button">
<span class="wallet-action-icon wallet-send-icon" aria-hidden="true"></span>
<i class="fa-solid fa-arrow-up" aria-hidden="true"></i>
</div>
<span>Send</span>
</div>
<div class="saito-large-square-button" id="wallet-btn-switch">
<div class="saito-icon-button">
<span class="wallet-action-icon wallet-tokens-icon" aria-hidden="true"></span>
<i class="fa-solid fa-wallet" aria-hidden="true"></i>
</div>
<span>Tokens</span>
</div>
<div class="saito-large-square-button" id="wallet-btn-nft">
<div class="saito-icon-button">
<span class="wallet-nft-icon" aria-hidden="true"></span>
<i class="fa-solid fa-shapes" aria-hidden="true"></i>
</div>
<span>NFTs</span>
</div>
<div class="saito-large-square-button" id="wallet-btn-settings">
<div class="saito-icon-button">
<span class="wallet-action-icon wallet-settings-icon" aria-hidden="true"></span>
<i class="fa-solid fa-gear" aria-hidden="true"></i>
</div>
<span>Admin</span>
</div>
Expand Down
56 changes: 33 additions & 23 deletions node/lib/saito/ui/saito-module/saito-module.template.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,42 @@
let SaitoModuleTemplate = (app, mod) => {
let html = `
<div class="saito-module-overlay">
<header class="saito-module-header" style="background-image: url(${mod.returnBanner()});">
<h1 class="saito-module-titlebar">${mod.returnName()}</h1>
</header>

<section class="saito-module-details">
<div class="detail-key">Version</div>
<div class="detail-value">${mod.version.toFixed(2)}</div>

<div class="detail-key">Publisher</div>
<div class="detail-value">${mod?.publisher ? mod.publisher : 'Saito'}</div>

<div class="detail-key">Categories</div>
<div class="detail-value">${mod.categories}</div>
const esc = (value) => app.browser.escapeHTML(value == null ? '' : String(value));

<div class="detail-key">Description</div>
<div class="detail-value">${mod.description}</div>
</section>`;
let version = mod.version;
if (typeof version === 'number' && Number.isFinite(version)) {
version = version.toFixed(2);
}

let settings = '';
if (mod.hasSettings()) {
html += `<h4>Settings</h4>
<section class="saito-module-settings">
</section>`;
settings = `<section class="saito-module-settings"></section>`;
}

html += `</div>`;
return html;
return `
<div class="saito-module-overlay saito-overlay-form">
<header class="saito-overlay-form-header">
<h2 class="saito-overlay-form-header-title">${esc(mod.returnName())}</h2>
</header>

<section class="details">
<div class="field">
<span class="label">Version</span>
<span class="value">${esc(version)}</span>
</div>
<div class="field">
<span class="label">Publisher</span>
<span class="value">${esc(mod.publisher ? mod.publisher : 'Saito')}</span>
</div>
<div class="field">
<span class="label">Categories</span>
<span class="value">${esc(mod.categories)}</span>
</div>
<div class="field">
<span class="label">Description</span>
<span class="value">${esc(mod.description)}</span>
</div>
</section>
${settings}
</div>`;
};

module.exports = SaitoModuleTemplate;
Loading
Loading