Release Stable Build v2.0.11 (#119)

* Add electron-updater auto-update support

- Install electron-updater package
- Configure GitHub releases publish settings
- Create AppUpdater class with full update lifecycle
- Integrate auto-update into main.js
- Add comprehensive documentation (AUTO-UPDATES.md, TESTING-UPDATES.md)
- Set up dev-app-update.yml for testing

* Add cache clearing documentation for electron-updater

- Introduced CLEAR-UPDATE-CACHE.md to guide users on clearing the electron-updater cache across macOS, Windows, and Linux.
- Added programmatic method for cache clearing in JavaScript.
- Enhanced update handling in main.js and preload.js to support new update events.
- Updated GUI styles for download buttons and progress indicators in update.js and style.css.

* Update auto-update UI and configuration

- Fix version display (newVersion field)
- Add download progress bar with real-time updates
- Reorder buttons: Install & Restart (primary), Manually Download (secondary)
- Update dev-app-update.yml to point to fork
- Update package.json version to 2.0.2

* Add installation effects and draggable progress bar

Introduces animated installation effects overlay and makes the progress bar draggable. Adds maximize window support, improves window controls styling, and enforces a single app instance. Removes the unused Skins page and related translations. Refines  various UI details for a more polished user experience.

* Adjust news card aspect ratio and add Play tab style

Set a default aspect ratio for .news-card and add a specific style for the LATEST NEWS section in the Play tab to override the aspect ratio and use full height.

* Add splash screen to launcher startup

Introduced a new splash screen (splash.html) and updated main.js to display it on startup before loading the main window. The splash screen is shown for 2.5 seconds as a placeholder for future loading logic, improving user experience during application launch.

* Display launcher version in UI

Adds a version display element to the bottom right of the UI, fetching the version from package.json via a new IPC handler. Updates main.js, preload.js, and ui.js to support retrieving and displaying the version, and adds relevant styles in style.css.

* Custom Mod loading fix (#92)

* feat: Add Repair Game functionality including UserData backup and cache clearing

* feat: Add In-App Logs Viewer and Logs Folder shortcut

* feat: Add Open Logs feature

* disable dev tools

* Fix Settings UI

* Implement custom mod loading, autoimport, auto repair

* Fixed Custom Mod loading issues and merge issues

* feat: Externalize sensitive API keys and Discord client ID into environment variables using dotenv.

* feat(mods): add profile-based mod management and auto-repair

* feat: add 'Close launcher on game start' option and improve app termination behavior (#93)

* update main branch to release/v2.0.2b (#86)

* add more linux pkgs, create auto-release and pre-release feature for Github Actions

* removed package-lock from gitignore

* update .gitignore for local build

* add package-lock.json to maintain stability development

* update version to 2.0.2b also add deps for rpm and arch

* update 2.0.2b: add arm64 support, product and executable name, maintainers; remove snap;

* update 2.0.2b: add latest.yml for win & linux, arm64 support; remove snap

* fix release build naming

* Prepare release v2.0.2b

* feat: add 'Close launcher on game start' option and improve app termination behavior

- Added 'Close launcher on game start' setting in GUI and backend.
- Implemented automatic app quit after game launch if setting is enabled.
- Added Cmd+Q (Mac) and Ctrl+Q/Alt+F4 (Win/Linux) shortcuts to quit the app.
- Updated 'window-close' handler to fully quit the app instead of just closing the window.
- Added i18n support for the new setting in English, Spanish, and Portuguese.

---------

Co-authored-by: Fazri Gading <fazrigading@gmail.com>
Co-authored-by: Arnav Singh <hi.arnavsingh3@gmail.com>

* Update publish config to point to chasem-dev fork

* Fix Linux metadata files in workflow and improve error handling

* Bump version to 2.0.5

* Bump version to 2.0.6

* Fix update popup showing for same version - add version comparison checks

* Bump version to 2.0.7

* Fix SHA512 checksum mismatch handling - clear cache and retry automatically

* Bump version to 2.0.8

* Bump version to 2.0.9

* Fix: Use explicit latest-linux.yml to prevent yml file collision

The glob pattern latest*.yml was matching both latest-linux.yml AND
latest.yml from the Linux build, causing the Windows latest.yml to be
overwritten with incorrect checksums.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Bump version to 2.0.10

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Fix: Remove portable target to fix SHA512 checksum mismatch

The portable and nsis targets both produced x64.exe files with the same
name, causing one to overwrite the other. The latest.yml contained the
checksum from one build while the actual file was from the other build.

Removed portable target - nsis installer is sufficient.
Bump version to 2.0.11

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* Remove outdated documentation files related to auto-updates, build instructions, and testing updates. Update `dev-app-update.yml` and `package.json` to reflect the correct GitHub owner. This cleanup streamlines the project and ensures accurate configuration for future updates.

* Add semantic versioning policy documentation - numerical versions only

* Update package-lock.json to include new dependencies and versions, enhancing project stability and compatibility.

* fixed imgur restriction for UK

* fix: adds EGL env var to detect installed NVIDIA GPU

* Update release.yml

* patch v2.0.11-beta: fix env issue in GA release, warn Intel Mac users, add com templates. (#115)

* fix: throw error for Intel Mac user
* docs: first draft of issue and PR template
* fix: env of curseforge API key and discord client ID

* implemented late patch should be in #115

* Final patch for release.yml v2.0.11

---------

Co-authored-by: chasem-dev <myers.a.chase@gmail.com>
Co-authored-by: AMIAY <letudiantenrap.collab@gmail.com>
Co-authored-by: Rahul Sahani <110347707+Rahul-Sahani04@users.noreply.github.com>
Co-authored-by: Arnav Singh <72737311+ArnavSingh77@users.noreply.github.com>
Co-authored-by: Arnav Singh <hi.arnavsingh3@gmail.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Fazri Gading
2026-01-24 00:07:59 +08:00
committed by GitHub
parent 71974e031f
commit b46ce93af7
47 changed files with 8337 additions and 5365 deletions

View File

@@ -15,7 +15,7 @@
<body class="bg-black text-white overflow-hidden font-sans select-none" tabindex="-1">
<div class="absolute inset-0 z-0">
<img src="https://i.imgur.com/Visrk66.png" alt="Background" class="w-full h-full object-cover" />
<img src="https://assets.authbp.xyz/bg.png" alt="Background" class="w-full h-full object-cover" />
<div class="absolute inset-0 bg-black/60"></div>
<div class="absolute inset-0 bg-[url('data:image/svg+xml,%3Csvg viewBox=" 0 0 256 256"
xmlns="http://www.w3.org/2000/svg" %3E%3Cfilter id="noiseFilter" %3E%3CfeTurbulence type="fractalNoise"
@@ -51,11 +51,7 @@
<i class="fas fa-cog"></i>
<span class="nav-tooltip" data-i18n="nav.settings">Settings</span>
</div>
<div class="nav-item" data-page="skins">
<i class="fas fa-user"></i>
<span class="nav-tooltip" data-i18n="nav.skins">Skins</span>
</div>
<div class="nav-item" data-page="logs" id="openLogsBtn" onclick="openLogs()">
<div class="nav-item logs-nav-item" data-page="logs" id="openLogsBtn" onclick="openLogs()">
<i class="fas fa-terminal"></i>
<span class="nav-tooltip">Logs</span>
</div>
@@ -94,6 +90,9 @@
<button class="control-btn minimize" onclick="window.electronAPI?.minimizeWindow()">
<i class="fas fa-minus"></i>
</button>
<button class="control-btn maximize" onclick="toggleMaximize()">
<i class="fas fa-square"></i>
</button>
<button class="control-btn close" onclick="window.electronAPI?.closeWindow()">
<i class="fas fa-times"></i>
</button>
@@ -104,9 +103,6 @@
<h1 class="game-title">
HY<span class="title-accent">TALE</span>
</h1>
<div class="game-tags">
<span class="tag" data-i18n="header.f2p">FREE TO PLAY</span>
</div>
</div>
<div class="content-pages">
@@ -114,7 +110,7 @@
<div class="install-content">
<div class="install-header">
<h1 class="install-title">
HYTA<span class="title-accent">LE</span>
HY<span class="title-accent">TALE</span>
</h1>
<p class="install-subtitle" data-i18n="install.title">FREE TO PLAY LAUNCHER</p>
</div>
@@ -122,22 +118,26 @@
<div class="install-form">
<div class="form-group">
<label class="form-label" data-i18n="install.playerName">Player Name</label>
<input type="text" id="installPlayerName" data-i18n-placeholder="install.playerNamePlaceholder"
class="form-input" value="Player" />
<input type="text" id="installPlayerName"
data-i18n-placeholder="install.playerNamePlaceholder" class="form-input"
value="Player" />
</div>
<div class="form-group">
<label class="checkbox-group">
<input type="checkbox" id="installCustomCheck" class="custom-checkbox">
<span class="checkbox-label" data-i18n="install.customInstallation">Custom Installation</span>
<span class="checkbox-label" data-i18n="install.customInstallation">Custom
Installation</span>
</label>
<div id="installCustomOptions" class="custom-options">
<div class="form-subgroup">
<label class="form-label" data-i18n="install.installationFolder">Installation Folder</label>
<label class="form-label" data-i18n="install.installationFolder">Installation
Folder</label>
<div class="input-with-button">
<input type="text" id="installPath" data-i18n-placeholder="install.pathPlaceholder"
class="form-input" readonly />
<input type="text" id="installPath"
data-i18n-placeholder="install.pathPlaceholder" class="form-input"
readonly />
<button onclick="browseInstallPath()" class="browse-btn">
<i class="fas fa-folder-open"></i>
</button>
@@ -163,7 +163,8 @@
<i class="fas fa-play-circle mr-2"></i>
<span data-i18n="play.ready">READY TO PLAY</span>
</h2>
<p class="play-subtitle" data-i18n="play.subtitle">Launch Hytale and enter the adventure</p>
<p class="play-subtitle" data-i18n="play.subtitle">Launch Hytale and enter the
adventure</p>
</div>
<button id="homePlayBtn" class="home-play-button" onclick="launch()">
@@ -180,7 +181,8 @@
<span data-i18n="play.latestNews">LATEST NEWS</span>
</h2>
<button class="view-all-btn" onclick="navigateToPage('news')">
<span data-i18n="play.viewAll">VIEW ALL</span> <i class="fas fa-arrow-right ml-1"></i>
<span data-i18n="play.viewAll">VIEW ALL</span> <i
class="fas fa-arrow-right ml-1"></i>
</button>
</div>
<div id="newsGrid" class="news-grid-horizontal"></div>
@@ -191,7 +193,8 @@
<div class="mods-header">
<div class="mods-search-container">
<i class="fas fa-search"></i>
<input type="text" id="modsSearch" data-i18n-placeholder="mods.searchPlaceholder" class="mods-search" />
<input type="text" id="modsSearch" data-i18n-placeholder="mods.searchPlaceholder"
class="mods-search" />
</div>
<div class="mods-actions">
<button id="myModsBtn" class="mods-btn-primary">
@@ -210,7 +213,8 @@
<span data-i18n="mods.previous">PREVIOUS</span>
</button>
<span class="pagination-info">
<span data-i18n="mods.page">Page</span> <span id="currentPage">1</span> <span data-i18n="mods.of">of</span> <span id="totalPages">1</span>
<span data-i18n="mods.page">Page</span> <span id="currentPage">1</span> <span
data-i18n="mods.of">of</span> <span id="totalPages">1</span>
</span>
<button id="nextPage" class="pagination-btn">
<span data-i18n="mods.next">NEXT</span>
@@ -291,12 +295,14 @@
<div class="settings-option">
<div class="settings-input-group">
<label class="settings-input-label" data-i18n="settings.playerName">Player Name</label>
<label class="settings-input-label" data-i18n="settings.playerName">Player
Name</label>
<input type="text" id="settingsPlayerName" class="settings-input"
data-i18n-placeholder="settings.playerNamePlaceholder" maxlength="16" />
<p class="settings-hint">
<i class="fas fa-user"></i>
<span data-i18n="settings.playerNameHint">This name will be used in-game (1-16 characters)</span>
<span data-i18n="settings.playerNameHint">This name will be used in-game
(1-16 characters)</span>
</p>
</div>
</div>
@@ -307,8 +313,11 @@
onclick="openGameLocation()">
<i class="fas fa-folder-open"></i>
<div class="btn-content">
<div class="btn-title" data-i18n="settings.openGameLocation">Open Game Location</div>
<div class="btn-description" data-i18n="settings.openGameLocationDesc">Open the game installation folder</div>
<div class="btn-title" data-i18n="settings.openGameLocation">Open
Game Location</div>
<div class="btn-description"
data-i18n="settings.openGameLocationDesc">Open the game
installation folder</div>
</div>
</button>
</div>
@@ -320,8 +329,10 @@
onclick="repairGame()">
<i class="fas fa-tools"></i>
<div class="btn-content">
<div class="btn-title" data-i18n="settings.repairGame">Repair Game</div>
<div class="btn-description" data-i18n="settings.reinstallGame">Reinstall game files (preserves data)
<div class="btn-title" data-i18n="settings.repairGame">Repair Game
</div>
<div class="btn-description" data-i18n="settings.reinstallGame">
Reinstall game files (preserves data)
</div>
</div>
</button>
@@ -329,18 +340,25 @@
<div class="settings-input-group">
<label class="settings-input-label" data-i18n="settings.gpuPreference">GPU Preference</label>
<label class="settings-input-label" data-i18n="settings.gpuPreference">GPU
Preference</label>
<div class="segmented-control">
<input type="radio" id="gpu-auto" name="gpuPreference" value="auto" checked>
<input type="radio" id="gpu-auto" name="gpuPreference" value="auto"
checked>
<label for="gpu-auto" data-i18n="settings.gpuAuto">Auto</label>
<input type="radio" id="gpu-integrated" name="gpuPreference" value="integrated">
<label for="gpu-integrated" data-i18n="settings.gpuIntegrated">Integrated</label>
<input type="radio" id="gpu-dedicated" name="gpuPreference" value="dedicated">
<label for="gpu-dedicated" data-i18n="settings.gpuDedicated">Dedicated</label>
<input type="radio" id="gpu-integrated" name="gpuPreference"
value="integrated">
<label for="gpu-integrated"
data-i18n="settings.gpuIntegrated">Integrated</label>
<input type="radio" id="gpu-dedicated" name="gpuPreference"
value="dedicated">
<label for="gpu-dedicated"
data-i18n="settings.gpuDedicated">Dedicated</label>
</div>
<p class="settings-hint">
<i class="fas fa-info-circle"></i>
<span data-i18n="settings.gpuHint">Select your preferred GPU (Linux: affects DRI_PRIME)</span>
<span data-i18n="settings.gpuHint">Select your preferred GPU (Linux:
affects DRI_PRIME)</span>
</p>
<div id="gpu-detection-info" class="gpu-detection-info"></div>
</div>
@@ -355,7 +373,8 @@
<div class="settings-option">
<div class="settings-input-group">
<label class="settings-input-label" data-i18n="settings.currentUUID">Current UUID</label>
<label class="settings-input-label" data-i18n="settings.currentUUID">Current
UUID</label>
<div class="uuid-display-container">
<input type="text" id="currentUuid" class="settings-input uuid-input"
readonly data-i18n-placeholder="settings.uuidPlaceholder" />
@@ -369,7 +388,8 @@
</div>
<p class="settings-hint">
<i class="fas fa-info-circle"></i>
<span data-i18n="settings.uuidHint">Your unique player identifier for this username</span>
<span data-i18n="settings.uuidHint">Your unique player identifier for
this username</span>
</p>
</div>
</div>
@@ -379,8 +399,10 @@
<button id="manageUuidsBtn" class="settings-action-btn">
<i class="fas fa-list"></i>
<div class="btn-content">
<div class="btn-title" data-i18n="settings.manageUUIDs">Manage All UUIDs</div>
<div class="btn-description" data-i18n="settings.manageUUIDsDesc">View and manage all player UUIDs</div>
<div class="btn-title" data-i18n="settings.manageUUIDs">Manage All
UUIDs</div>
<div class="btn-description" data-i18n="settings.manageUUIDsDesc">
View and manage all player UUIDs</div>
</div>
</button>
</div>
@@ -398,14 +420,38 @@
<input type="checkbox" id="discordRPCCheck" checked />
<span class="checkmark"></span>
<div class="checkbox-content">
<div class="checkbox-title" data-i18n="settings.enableRPC">Enable Discord Rich Presence</div>
<div class="checkbox-description" data-i18n="settings.discordDescription">Show your launcher activity on Discord
<div class="checkbox-title" data-i18n="settings.enableRPC">Enable
Discord Rich Presence</div>
<div class="checkbox-description"
data-i18n="settings.discordDescription">Show your launcher activity
on Discord
</div>
</div>
</label>
</div>
</div>
<div class="settings-section">
<h3 class="settings-section-title">
<i class="fas fa-window-close"></i>
<span data-i18n="settings.closeLauncher">Launcher Behavior</span>
</h3>
<div class="settings-option">
<label class="settings-checkbox">
<input type="checkbox" id="closeLauncherCheck" />
<span class="checkmark"></span>
<div class="checkbox-content">
<div class="checkbox-title" data-i18n="settings.closeOnStart">Close Launcher on game start</div>
<div class="checkbox-description" data-i18n="settings.closeOnStartDescription">
Automatically close the launcher after Hytale has launched
</div>
</div>
</label>
</div>
</div>
<div class="settings-section">
<h3 class="settings-section-title">
<i class="fas fa-coffee"></i>
@@ -417,8 +463,10 @@
<input type="checkbox" id="customJavaCheck" />
<span class="checkmark"></span>
<div class="checkbox-content">
<div class="checkbox-title" data-i18n="settings.useCustomJava">Use Custom Java Path</div>
<div class="checkbox-description" data-i18n="settings.javaDescription">Override the bundled Java runtime with
<div class="checkbox-title" data-i18n="settings.useCustomJava">Use
Custom Java Path</div>
<div class="checkbox-description" data-i18n="settings.javaDescription">
Override the bundled Java runtime with
your own installation</div>
</div>
</label>
@@ -426,7 +474,8 @@
<div id="customJavaOptions" class="custom-java-options" style="display: none;">
<div class="settings-input-group">
<label class="settings-input-label" data-i18n="settings.javaPath">Java Executable Path</label>
<label class="settings-input-label" data-i18n="settings.javaPath">Java
Executable Path</label>
<div class="settings-input-with-button">
<input type="text" id="customJavaPath" class="settings-input"
data-i18n-placeholder="settings.javaPathPlaceholder" readonly />
@@ -437,12 +486,13 @@
</div>
<p class="settings-hint">
<i class="fas fa-info-circle"></i>
<span data-i18n="settings.javaHint">Select the Java installation folder (supports Windows, Mac, Linux)</span>
<span data-i18n="settings.javaHint">Select the Java installation folder
(supports Windows, Mac, Linux)</span>
</p>
</div>
</div>
</div>
<div class="settings-section">
<h3 class="settings-section-title">
<i class="fas fa-language"></i>
@@ -451,7 +501,8 @@
<div class="settings-option">
<div class="settings-input-group">
<label class="settings-input-label" data-i18n="settings.selectLanguage">Select Language</label>
<label class="settings-input-label"
data-i18n="settings.selectLanguage">Select Language</label>
<select id="languageSelect" class="settings-input">
<!-- Options populated by i18n.js -->
</select>
@@ -462,14 +513,6 @@
</div>
</div>
<div id="skins-page" class="page">
<div class="placeholder-content">
<i class="fas fa-user text-6xl mb-4 text-purple-500"></i>
<h2 data-i18n="skins.title">Skins</h2>
<p data-i18n="skins.comingSoon">Skin customization coming soon...</p>
</div>
</div>
<div id="logs-page" class="page">
<div class="logs-container">
<div class="logs-header">
@@ -482,15 +525,18 @@
<i class="fas fa-copy"></i> <span data-i18n="settings.logsCopy">Copy</span>
</button>
<button class="logs-action-btn" onclick="refreshLogs()">
<i class="fas fa-sync-alt"></i> <span data-i18n="settings.logsRefresh">Refresh</span>
<i class="fas fa-sync-alt"></i> <span
data-i18n="settings.logsRefresh">Refresh</span>
</button>
<button class="logs-action-btn" onclick="openLogsFolder()">
<i class="fas fa-folder-open"></i> <span data-i18n="settings.logsFolder">Open Folder</span>
<i class="fas fa-folder-open"></i> <span data-i18n="settings.logsFolder">Open
Folder</span>
</button>
</div>
</div>
<div id="logsTerminal" class="logs-terminal">
<div class="text-gray-500 text-center mt-10" data-i18n="settings.logsLoading">Loading logs...</div>
<div class="text-gray-500 text-center mt-10" data-i18n="settings.logsLoading">Loading
logs...</div>
</div>
</div>
</div>
@@ -532,6 +578,20 @@
</div>
</div>
<!-- Installation effects overlay -->
<div id="installationEffects" class="installation-effects" style="display: none;">
<div class="space-effects">
<div class="warp-line"></div>
<div class="warp-line"></div>
<div class="warp-line"></div>
<div class="warp-line"></div>
<div class="warp-line"></div>
<div class="warp-line"></div>
<div class="warp-line"></div>
<div class="warp-line"></div>
</div>
</div>
<div id="chatUsernameModal" class="chat-username-modal" style="display: none;">
<div class="chat-username-modal-content">
<div class="chat-username-modal-header">
@@ -545,10 +605,12 @@
Choose a username to join the Players Chat
</p>
<div class="chat-username-input-group">
<label for="chatUsernameInput" class="chat-username-label" data-i18n="chat.username">Username</label>
<label for="chatUsernameInput" class="chat-username-label"
data-i18n="chat.username">Username</label>
<input type="text" id="chatUsernameInput" class="chat-username-input"
data-i18n-placeholder="chat.usernamePlaceholder" maxlength="20" autocomplete="off" />
<span class="chat-username-hint" data-i18n="chat.usernameHint">3-20 characters, letters, numbers, - and _ only</span>
<span class="chat-username-hint" data-i18n="chat.usernameHint">3-20 characters, letters, numbers, -
and _ only</span>
<span id="chatUsernameError" class="chat-username-error"></span>
</div>
</div>
@@ -613,8 +675,7 @@
<h3 class="uuid-section-title" data-i18n="uuid.setCustomUUID">Set Custom UUID</h3>
<div class="uuid-custom-form">
<input type="text" id="customUuidInput" class="uuid-input"
data-i18n-placeholder="uuid.customPlaceholder"
maxlength="36" />
data-i18n-placeholder="uuid.customPlaceholder" maxlength="36" />
<button id="setCustomUuidBtn" class="uuid-set-btn">
<i class="fas fa-check"></i>
<span data-i18n="uuid.setUUID">Set UUID</span>
@@ -622,7 +683,8 @@
</div>
<p class="uuid-custom-hint">
<i class="fas fa-exclamation-triangle"></i>
<span data-i18n="uuid.warning">Warning: Setting a custom UUID will change your current player identity</span>
<span data-i18n="uuid.warning">Warning: Setting a custom UUID will change your current player
identity</span>
</p>
</div>
</div>
@@ -646,8 +708,8 @@
<!-- Populated by JS -->
</div>
<div class="profile-create-section">
<input type="text" id="newProfileName" data-i18n-placeholder="profiles.newProfilePlaceholder" class="profile-input"
maxlength="20">
<input type="text" id="newProfileName" data-i18n-placeholder="profiles.newProfilePlaceholder"
class="profile-input" maxlength="20">
<button class="profile-create-btn" onclick="createNewProfile()">
<i class="fas fa-plus"></i> <span data-i18n="profiles.createProfile">Create Profile</span>
</button>
@@ -656,6 +718,11 @@
</div>
</div>
<div class="version-display-bottom">
<i class="fas fa-code-branch"></i>
<span id="launcherVersion">Loading...</span>
</div>
<footer class="fixed bottom-0 left-0 right-0 z-50 bg-black/80 backdrop-blur-sm px-4 py-2">
<div class="flex items-center justify-center text-xs text-gray-400">
<span>Made by <a href="https://github.com/amiayweb" target="_blank"
@@ -728,12 +795,15 @@
<div class="color-preview">
<h4 data-i18n="chat.colorModal.preview">Preview:</h4>
<div id="colorPreview" class="preview-username" data-i18n="chat.colorModal.previewUsername">YourUsername</div>
<div id="colorPreview" class="preview-username" data-i18n="chat.colorModal.previewUsername">
YourUsername</div>
</div>
</div>
<div class="chat-color-modal-footer">
<button class="btn-secondary" onclick="closeChatColorModal()"><span data-i18n="common.cancel">Cancel</span></button>
<button class="btn-primary" onclick="applyChatColor()"><span data-i18n="chat.colorModal.apply">Apply Color</span></button>
<button class="btn-secondary" onclick="closeChatColorModal()"><span
data-i18n="common.cancel">Cancel</span></button>
<button class="btn-primary" onclick="applyChatColor()"><span data-i18n="chat.colorModal.apply">Apply
Color</span></button>
</div>
</div>
</div>

View File

@@ -39,6 +39,19 @@ export function setupInstallation() {
}
});
}
// Setup installation effects listeners
if (window.electronAPI && window.electronAPI.onInstallationStart) {
window.electronAPI.onInstallationStart(() => {
showInstallationEffects();
});
}
if (window.electronAPI && window.electronAPI.onInstallationEnd) {
window.electronAPI.onInstallationEnd(() => {
hideInstallationEffects();
});
}
}
export async function installGame() {
@@ -78,12 +91,19 @@ export async function installGame() {
}
} catch (error) {
const errorMsg = window.i18n ? window.i18n.t('progress.installationFailed').replace('{error}', error.message) : `Installation failed: ${error.message}`;
// Hide installation effects on error
if (window.hideInstallationEffects) {
window.hideInstallationEffects();
}
// Reset button state on error
resetInstallButton();
if (window.LauncherUI) {
window.LauncherUI.updateProgress({ message: errorMsg });
setTimeout(() => {
window.LauncherUI.hideProgress();
resetInstallButton();
}, 3000);
// Don't hide progress bar, just update the message
// User can see the error and close it manually
}
}
}

View File

@@ -1,5 +1,5 @@
const API_KEY = '$2a$10$bqk254NMZOWVTzLVJCcxEOmhcyUujKxA5xk.kQCN9q0KNYFJd5b32';
let API_KEY = null;
const CURSEFORGE_API = 'https://api.curseforge.com/v1';
const HYTALE_GAME_ID = 70216;
@@ -11,6 +11,15 @@ let modsPageSize = 20;
let modsTotalPages = 1;
export async function initModsManager() {
try {
if (window.electronAPI && window.electronAPI.getEnvVar) {
API_KEY = await window.electronAPI.getEnvVar('CURSEFORGE_API_KEY');
console.log('Loaded API Key:', API_KEY ? 'Yes' : 'No');
}
} catch (err) {
console.error('Failed to load API Key:', err);
}
setupModsEventListeners();
await loadInstalledMods();
await loadBrowseMods();
@@ -417,10 +426,10 @@ async function deleteMod(modId) {
const mod = installedMods.find(m => m.id === modId);
if (!mod) return;
const confirmMsg = window.i18n ?
const confirmMsg = window.i18n ?
window.i18n.t('mods.confirmDelete').replace('{name}', mod.name) + ' ' + window.i18n.t('mods.confirmDeleteDesc') :
`Are you sure you want to delete "${mod.name}"? This action cannot be undone.`;
showConfirmModal(
confirmMsg,
async () => {

View File

@@ -3,9 +3,11 @@ let customJavaCheck;
let customJavaOptions;
let customJavaPath;
let browseJavaBtn;
let settingsPlayerName;
let discordRPCCheck;
let gpuPreferenceRadios;
let settingsPlayerName;
let discordRPCCheck;
let closeLauncherCheck;
let gpuPreferenceRadios;
// UUID Management elements
let currentUuidDisplay;
@@ -159,9 +161,11 @@ function setupSettingsElements() {
customJavaOptions = document.getElementById('customJavaOptions');
customJavaPath = document.getElementById('customJavaPath');
browseJavaBtn = document.getElementById('browseJavaBtn');
settingsPlayerName = document.getElementById('settingsPlayerName');
discordRPCCheck = document.getElementById('discordRPCCheck');
gpuPreferenceRadios = document.querySelectorAll('input[name="gpuPreference"]');
settingsPlayerName = document.getElementById('settingsPlayerName');
discordRPCCheck = document.getElementById('discordRPCCheck');
closeLauncherCheck = document.getElementById('closeLauncherCheck');
gpuPreferenceRadios = document.querySelectorAll('input[name="gpuPreference"]');
// UUID Management elements
currentUuidDisplay = document.getElementById('currentUuid');
@@ -190,9 +194,14 @@ function setupSettingsElements() {
settingsPlayerName.addEventListener('change', savePlayerName);
}
if (discordRPCCheck) {
discordRPCCheck.addEventListener('change', saveDiscordRPC);
}
if (discordRPCCheck) {
discordRPCCheck.addEventListener('change', saveDiscordRPC);
}
if (closeLauncherCheck) {
closeLauncherCheck.addEventListener('change', saveCloseLauncher);
}
// UUID event listeners
if (copyUuidBtn) {
@@ -335,18 +344,43 @@ async function saveDiscordRPC() {
}
}
async function loadDiscordRPC() {
try {
if (window.electronAPI && window.electronAPI.loadDiscordRPC) {
const enabled = await window.electronAPI.loadDiscordRPC();
if (discordRPCCheck) {
discordRPCCheck.checked = enabled;
}
}
} catch (error) {
console.error('Error loading Discord RPC setting:', error);
}
}
async function loadDiscordRPC() {
try {
if (window.electronAPI && window.electronAPI.loadDiscordRPC) {
const enabled = await window.electronAPI.loadDiscordRPC();
if (discordRPCCheck) {
discordRPCCheck.checked = enabled;
}
}
} catch (error) {
console.error('Error loading Discord RPC setting:', error);
}
}
async function saveCloseLauncher() {
try {
if (window.electronAPI && window.electronAPI.saveCloseLauncher && closeLauncherCheck) {
const enabled = closeLauncherCheck.checked;
await window.electronAPI.saveCloseLauncher(enabled);
}
} catch (error) {
console.error('Error saving close launcher setting:', error);
}
}
async function loadCloseLauncher() {
try {
if (window.electronAPI && window.electronAPI.loadCloseLauncher) {
const enabled = await window.electronAPI.loadCloseLauncher();
if (closeLauncherCheck) {
closeLauncherCheck.checked = enabled;
}
}
} catch (error) {
console.error('Error loading close launcher setting:', error);
}
}
async function savePlayerName() {
try {
@@ -457,13 +491,15 @@ async function loadGpuPreference() {
}
}
async function loadAllSettings() {
await loadCustomJavaPath();
await loadPlayerName();
await loadCurrentUuid();
await loadDiscordRPC();
await loadGpuPreference();
}
async function loadAllSettings() {
await loadCustomJavaPath();
await loadPlayerName();
await loadCurrentUuid();
await loadDiscordRPC();
await loadCloseLauncher();
await loadGpuPreference();
}
async function openGameLocation() {
try {

View File

@@ -479,6 +479,9 @@ function setupUI() {
progressSpeed = document.getElementById('progressSpeed');
progressSize = document.getElementById('progressSize');
// Setup draggable progress bar
setupProgressDrag();
lockPlayButton(true);
setTimeout(() => {
@@ -497,10 +500,26 @@ function setupUI() {
setupSidebarLogo();
setupAnimations();
setupFirstLaunchHandlers();
loadLauncherVersion();
document.body.focus();
}
// Load launcher version from package.json
async function loadLauncherVersion() {
try {
if (window.electronAPI && window.electronAPI.getVersion) {
const version = await window.electronAPI.getVersion();
const versionElement = document.getElementById('launcherVersion');
if (versionElement) {
versionElement.textContent = `v${version}`;
}
}
} catch (error) {
console.error('Failed to load launcher version:', error);
}
}
window.LauncherUI = {
showPage,
setActiveNav,
@@ -510,4 +529,91 @@ window.LauncherUI = {
updateProgress
};
// Make installation effects globally available
window.showInstallationEffects = showInstallationEffects;
window.hideInstallationEffects = hideInstallationEffects;
// Draggable progress bar functionality
function setupProgressDrag() {
if (!progressOverlay) return;
let isDragging = false;
let offsetX;
let offsetY;
progressOverlay.addEventListener('mousedown', dragStart);
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', dragEnd);
function dragStart(e) {
// Only drag if clicking on the overlay itself, not on buttons or inputs
if (e.target.closest('.progress-bar-fill')) return;
if (e.target === progressOverlay || e.target.closest('.progress-content')) {
isDragging = true;
progressOverlay.classList.add('dragging');
// Get the current position of the progress overlay
const rect = progressOverlay.getBoundingClientRect();
offsetX = e.clientX - rect.left - progressOverlay.offsetWidth / 2;
offsetY = e.clientY - rect.top;
}
}
function drag(e) {
if (isDragging) {
e.preventDefault();
// Calculate new position
const newX = e.clientX - offsetX - progressOverlay.offsetWidth / 2;
const newY = e.clientY - offsetY;
// Get window bounds
const maxX = window.innerWidth - progressOverlay.offsetWidth;
const maxY = window.innerHeight - progressOverlay.offsetHeight;
const minX = 0;
const minY = 0;
// Constrain to window bounds
const constrainedX = Math.max(minX, Math.min(newX, maxX));
const constrainedY = Math.max(minY, Math.min(newY, maxY));
progressOverlay.style.left = constrainedX + 'px';
progressOverlay.style.bottom = 'auto';
progressOverlay.style.top = constrainedY + 'px';
progressOverlay.style.transform = 'none';
}
}
function dragEnd() {
isDragging = false;
progressOverlay.classList.remove('dragging');
}
}
// Show/hide installation effects
function showInstallationEffects() {
const installationEffects = document.getElementById('installationEffects');
if (installationEffects) {
installationEffects.style.display = 'block';
}
}
function hideInstallationEffects() {
const installationEffects = document.getElementById('installationEffects');
if (installationEffects) {
installationEffects.style.display = 'none';
}
}
// Toggle maximize/restore window function
function toggleMaximize() {
if (window.electronAPI && window.electronAPI.maximizeWindow) {
window.electronAPI.maximizeWindow();
}
}
// Make toggleMaximize globally available
window.toggleMaximize = toggleMaximize;
document.addEventListener('DOMContentLoaded', setupUI);

View File

@@ -10,6 +10,23 @@ class ClientUpdateManager {
this.showUpdatePopup(updateInfo);
});
// Listen for electron-updater events
window.electronAPI.onUpdateAvailable((updateInfo) => {
this.showUpdatePopup(updateInfo);
});
window.electronAPI.onUpdateDownloadProgress((progress) => {
this.updateDownloadProgress(progress);
});
window.electronAPI.onUpdateDownloaded((updateInfo) => {
this.showUpdateDownloaded(updateInfo);
});
window.electronAPI.onUpdateError((errorInfo) => {
this.handleUpdateError(errorInfo);
});
this.checkForUpdatesOnDemand();
}
@@ -33,23 +50,46 @@ class ClientUpdateManager {
<div class="update-popup-versions">
<div class="version-row">
<span class="version-label">Current Version:</span>
<span class="version-current">${updateInfo.currentVersion}</span>
<span class="version-current">${updateInfo.currentVersion || updateInfo.version || 'Unknown'}</span>
</div>
<div class="version-row">
<span class="version-label">New Version:</span>
<span class="version-new">${updateInfo.newVersion}</span>
<span class="version-new">${updateInfo.newVersion || updateInfo.version || 'Unknown'}</span>
</div>
</div>
<div class="update-popup-message">
A new version of Hytale F2P Launcher is available.<br>
Please download the latest version to continue using the launcher.
<span id="update-status-text">Downloading update automatically...</span>
<div id="update-error-message" style="display: none; margin-top: 0.75rem; padding: 0.75rem; background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.3); border-radius: 0.5rem; color: #fca5a5; font-size: 0.875rem;">
<i class="fas fa-exclamation-triangle" style="margin-right: 0.5rem;"></i>
<span id="update-error-text"></span>
</div>
</div>
<button id="update-download-btn" class="update-download-btn">
<i class="fas fa-external-link-alt" style="margin-right: 0.5rem;"></i>
Download Update
</button>
<div id="update-progress-container" style="display: none; margin-bottom: 1rem;">
<div style="display: flex; justify-content: space-between; margin-bottom: 0.5rem; font-size: 0.75rem; color: #9ca3af;">
<span id="update-progress-percent">0%</span>
<span id="update-progress-speed">0 KB/s</span>
</div>
<div style="width: 100%; height: 8px; background: rgba(255, 255, 255, 0.1); border-radius: 4px; overflow: hidden;">
<div id="update-progress-bar" style="width: 0%; height: 100%; background: linear-gradient(90deg, #3b82f6, #9333ea); transition: width 0.3s ease;"></div>
</div>
<div style="margin-top: 0.5rem; font-size: 0.75rem; color: #9ca3af; text-align: center;">
<span id="update-progress-size">0 MB / 0 MB</span>
</div>
</div>
<div id="update-buttons-container" style="display: none;">
<button id="update-install-btn" class="update-download-btn">
<i class="fas fa-check" style="margin-right: 0.5rem;"></i>
Install & Restart
</button>
<button id="update-download-btn" class="update-download-btn update-download-btn-secondary" style="margin-top: 0.75rem;">
<i class="fas fa-external-link-alt" style="margin-right: 0.5rem;"></i>
Manually Download
</button>
</div>
<div class="update-popup-footer">
This popup cannot be closed until you update the launcher
@@ -62,6 +102,31 @@ class ClientUpdateManager {
this.blockInterface();
// Show progress container immediately (auto-download is enabled)
const progressContainer = document.getElementById('update-progress-container');
if (progressContainer) {
progressContainer.style.display = 'block';
}
const installBtn = document.getElementById('update-install-btn');
if (installBtn) {
installBtn.addEventListener('click', async (e) => {
e.preventDefault();
e.stopPropagation();
installBtn.disabled = true;
installBtn.innerHTML = '<i class="fas fa-spinner fa-spin" style="margin-right: 0.5rem;"></i>Installing...';
try {
await window.electronAPI.quitAndInstallUpdate();
} catch (error) {
console.error('❌ Error installing update:', error);
installBtn.disabled = false;
installBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Install & Restart';
}
});
}
const downloadBtn = document.getElementById('update-download-btn');
if (downloadBtn) {
downloadBtn.addEventListener('click', async (e) => {
@@ -80,7 +145,7 @@ class ClientUpdateManager {
} catch (error) {
console.error('❌ Error opening download page:', error);
downloadBtn.disabled = false;
downloadBtn.innerHTML = '<i class="fas fa-external-link-alt" style="margin-right: 0.5rem;"></i>Download Update';
downloadBtn.innerHTML = '<i class="fas fa-external-link-alt" style="margin-right: 0.5rem;"></i>Manually Download';
}
});
}
@@ -99,6 +164,134 @@ class ClientUpdateManager {
console.log('🔔 Update popup displayed with new style');
}
updateDownloadProgress(progress) {
const progressBar = document.getElementById('update-progress-bar');
const progressPercent = document.getElementById('update-progress-percent');
const progressSpeed = document.getElementById('update-progress-speed');
const progressSize = document.getElementById('update-progress-size');
if (progressBar && progress) {
const percent = Math.round(progress.percent || 0);
progressBar.style.width = `${percent}%`;
if (progressPercent) {
progressPercent.textContent = `${percent}%`;
}
if (progressSpeed && progress.bytesPerSecond) {
const speedMBps = (progress.bytesPerSecond / 1024 / 1024).toFixed(2);
progressSpeed.textContent = `${speedMBps} MB/s`;
}
if (progressSize && progress.transferred && progress.total) {
const transferredMB = (progress.transferred / 1024 / 1024).toFixed(2);
const totalMB = (progress.total / 1024 / 1024).toFixed(2);
progressSize.textContent = `${transferredMB} MB / ${totalMB} MB`;
}
// Don't update status text here - it's already set and the progress bar shows the percentage
}
}
showUpdateDownloaded(updateInfo) {
const statusText = document.getElementById('update-status-text');
const progressContainer = document.getElementById('update-progress-container');
const buttonsContainer = document.getElementById('update-buttons-container');
if (statusText) {
statusText.textContent = 'Update downloaded! Ready to install.';
}
if (progressContainer) {
progressContainer.style.display = 'none';
}
if (buttonsContainer) {
buttonsContainer.style.display = 'block';
}
console.log('✅ Update downloaded, ready to install');
}
handleUpdateError(errorInfo) {
console.error('Update error:', errorInfo);
// If manual download is required, update the UI (this will handle status text)
if (errorInfo.requiresManualDownload) {
this.showManualDownloadRequired(errorInfo);
return; // Don't do anything else, showManualDownloadRequired handles everything
}
// For non-critical errors, just show error message without changing status
const errorMessage = document.getElementById('update-error-message');
const errorText = document.getElementById('update-error-text');
if (errorMessage && errorText) {
let message = errorInfo.message || 'An error occurred during the update process.';
if (errorInfo.isMacSigningError) {
message = 'Auto-update requires code signing. Please download manually.';
}
errorText.textContent = message;
errorMessage.style.display = 'block';
}
}
showManualDownloadRequired(errorInfo) {
const statusText = document.getElementById('update-status-text');
const progressContainer = document.getElementById('update-progress-container');
const buttonsContainer = document.getElementById('update-buttons-container');
const installBtn = document.getElementById('update-install-btn');
const downloadBtn = document.getElementById('update-download-btn');
const errorMessage = document.getElementById('update-error-message');
const errorText = document.getElementById('update-error-text');
// Hide progress and install button
if (progressContainer) {
progressContainer.style.display = 'none';
}
if (installBtn) {
installBtn.style.display = 'none';
}
// Update status message (only once, don't change it again)
if (statusText && !statusText.dataset.manualMode) {
statusText.textContent = 'Please download and install the update manually.';
statusText.dataset.manualMode = 'true'; // Mark that we've set manual mode
}
// Show error message with details
if (errorMessage && errorText) {
let message = 'Auto-update is not available. ';
if (errorInfo.isMacSigningError) {
message = 'This app requires code signing for automatic updates.';
} else if (errorInfo.isLinuxInstallError) {
message = 'Auto-installation requires root privileges. Please download and install the update manually using your package manager.';
} else if (errorInfo.message) {
message = errorInfo.message;
} else {
message = 'An error occurred during the update process.';
}
errorText.textContent = message;
errorMessage.style.display = 'block';
}
// Show and enable the manual download button (make it primary since it's the only option)
if (downloadBtn) {
downloadBtn.style.display = 'block';
downloadBtn.disabled = false;
downloadBtn.classList.remove('update-download-btn-secondary');
downloadBtn.innerHTML = '<i class="fas fa-external-link-alt" style="margin-right: 0.5rem;"></i>Download Update Manually';
}
// Show buttons container if not already visible
if (buttonsContainer) {
buttonsContainer.style.display = 'block';
}
console.log('⚠️ Manual download required due to update error');
}
blockInterface() {
const mainContent = document.querySelector('.flex.w-full.h-screen');
if (mainContent) {
@@ -144,7 +337,12 @@ class ClientUpdateManager {
async checkForUpdatesOnDemand() {
try {
const updateInfo = await window.electronAPI.checkForUpdates();
if (updateInfo.updateAvailable) {
// Double-check that versions are actually different before showing popup
if (updateInfo.updateAvailable &&
updateInfo.newVersion &&
updateInfo.currentVersion &&
updateInfo.newVersion !== updateInfo.currentVersion) {
this.showUpdatePopup(updateInfo);
}
return updateInfo;

View File

@@ -4,14 +4,12 @@
"mods": "Mods",
"news": "News",
"chat": "Players Chat",
"settings": "Settings",
"skins": "Skins"
"settings": "Settings"
},
"header": {
"playersLabel": "Players:",
"manageProfiles": "Manage Profiles",
"defaultProfile": "Default",
"f2p": "FREE TO PLAY"
"defaultProfile": "Default"
},
"install": {
"title": "FREE TO PLAY LAUNCHER",
@@ -124,7 +122,10 @@
"logsCopy": "Copy",
"logsRefresh": "Refresh",
"logsFolder": "Open Folder",
"logsLoading": "Loading logs..."
"logsLoading": "Loading logs...",
"closeLauncher": "Launcher Behavior",
"closeOnStart": "Close Launcher on game start",
"closeOnStartDescription": "Automatically close the launcher after Hytale has launched"
},
"uuid": {
"modalTitle": "UUID Management",
@@ -148,10 +149,6 @@
"notificationText": "Join our Discord community!",
"joinButton": "Join Discord"
},
"skins": {
"title": "Skins",
"comingSoon": "Skin customization coming soon..."
},
"common": {
"confirm": "Confirm",
"cancel": "Cancel",

View File

@@ -4,14 +4,12 @@
"mods": "Mods",
"news": "Noticias",
"chat": "Chat de Jugadores",
"settings": "Configuración",
"skins": "Aspectos"
"settings": "Configuración"
},
"header": {
"playersLabel": "Jugadores:",
"manageProfiles": "Gestionar Perfiles",
"defaultProfile": "Predeterminado",
"f2p": "FREE TO PLAY"
"defaultProfile": "Predeterminado"
},
"install": {
"title": "LAUNCHER GRATUITO",
@@ -124,7 +122,10 @@
"logsCopy": "Copiar",
"logsRefresh": "Actualizar",
"logsFolder": "Abrir Carpeta",
"logsLoading": "Cargando registros..."
"logsLoading": "Cargando registros...",
"closeLauncher": "Comportamiento del Launcher",
"closeOnStart": "Cerrar Launcher al iniciar el juego",
"closeOnStartDescription": "Cierra automáticamente el launcher después de que Hytale se haya iniciado"
},
"uuid": {
"modalTitle": "Gestión de UUID",
@@ -148,10 +149,6 @@
"notificationText": "¡Únete a nuestra comunidad de Discord!",
"joinButton": "Unirse a Discord"
},
"skins": {
"title": "Aspectos",
"comingSoon": "Personalización de aspectos próximamente..."
},
"common": {
"confirm": "Confirmar",
"cancel": "Cancelar",

View File

@@ -4,14 +4,12 @@
"mods": "Mods",
"news": "Notícias",
"chat": "Chat de Jogadores",
"settings": "Configurações",
"skins": "Aparências"
"settings": "Configurações"
},
"header": {
"playersLabel": "Jogadores:",
"manageProfiles": "Gerenciar Perfis",
"defaultProfile": "Padrão",
"f2p": "FREE TO PLAY"
"defaultProfile": "Padrão"
},
"install": {
"title": "LANÇADOR JOGO GRATUITO",
@@ -124,7 +122,10 @@
"logsCopy": "Copiar",
"logsRefresh": "Atualizar",
"logsFolder": "Abrir Pasta",
"logsLoading": "Carregando registros..."
"logsLoading": "Carregando registros...",
"closeLauncher": "Comportamento do Lançador",
"closeOnStart": "Fechar Lançador ao iniciar o jogo",
"closeOnStartDescription": "Fechar automaticamente o lançador após o Hytale ter sido iniciado"
},
"uuid": {
"modalTitle": "Gerenciamento de UUID",
@@ -148,10 +149,7 @@
"notificationText": "Junte-se à nossa comunidade do Discord!",
"joinButton": "Entrar no Discord"
},
"skins": {
"title": "Aparências",
"comingSoon": "Personalização de aparências em breve..."
},
"common": {
"confirm": "Confirmar",
"cancel": "Cancelar",

178
GUI/splash.html Normal file
View File

@@ -0,0 +1,178 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hytale F2P</title>
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 100%;
height: 100vh;
background: transparent;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Space Grotesk', sans-serif;
overflow: hidden;
position: relative;
border-radius: 16px;
}
.background {
position: absolute;
inset: 0;
z-index: 0;
border-radius: 16px;
overflow: hidden;
}
.background img {
width: 100%;
height: 100%;
object-fit: cover;
}
.background::after {
content: '';
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.7);
}
.splash-container {
position: relative;
z-index: 10;
text-align: center;
animation: fadeIn 0.5s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.logo {
width: 120px;
height: 120px;
margin: 0 auto 2rem;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
}
.logo img {
width: 100%;
height: 100%;
object-fit: contain;
filter: drop-shadow(0 0 30px rgba(147, 51, 234, 0.5));
}
.title {
font-size: 3rem;
font-weight: 700;
color: white;
margin-bottom: 1rem;
letter-spacing: 0.1em;
}
.title-accent {
background: linear-gradient(135deg, #9333ea, #a855f7, #c084fc);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.subtitle {
font-size: 0.875rem;
color: #9ca3af;
margin-bottom: 2rem;
text-transform: uppercase;
letter-spacing: 0.2em;
}
.loader {
width: 200px;
height: 4px;
background: rgba(147, 51, 234, 0.2);
border-radius: 2px;
margin: 0 auto;
overflow: hidden;
position: relative;
}
.loader::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, #9333ea, #a855f7, #c084fc);
animation: loading 1.5s ease-in-out infinite;
box-shadow: 0 0 20px rgba(147, 51, 234, 0.6);
}
@keyframes loading {
0% {
left: -100%;
}
100% {
left: 100%;
}
}
.loading-text {
margin-top: 1rem;
font-size: 0.75rem;
color: #6b7280;
animation: blink 1.5s ease-in-out infinite;
}
@keyframes blink {
0%, 100% {
opacity: 0.5;
}
50% {
opacity: 1;
}
}
</style>
</head>
<body>
<div class="background">
<img src="https://assets.authbp.xyz/bg.png" alt="Background">
</div>
<div class="splash-container">
<div class="logo">
<img src="./icon.png" alt="Hytale Logo">
</div>
<h1 class="title">
HY<span class="title-accent">TALE</span>
</h1>
<p class="subtitle">FREE TO PLAY LAUNCHER</p>
<div class="loader"></div>
<p class="loading-text">Loading...</p>
</div>
</body>
</html>

View File

@@ -26,7 +26,7 @@ body {
backdrop-filter: blur(20px);
border-right: 1px solid rgba(255, 255, 255, 0.1);
position: relative;
z-index: 20;
z-index: 45;
}
.sidebar-logo {
@@ -109,6 +109,12 @@ body {
transform: scale(1.1);
}
/* Allow logs navigation during installation */
.logs-nav-item {
z-index: 100;
position: relative;
}
.nav-tooltip {
position: absolute;
left: 100%;
@@ -210,6 +216,63 @@ body {
border-color: rgba(147, 51, 234, 0.3);
}
.version-display {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
font-family: 'JetBrains Mono', monospace;
font-size: 0.75rem;
color: #9ca3af;
pointer-events: auto;
transition: all 0.3s ease;
}
.version-display i {
color: #9333ea;
font-size: 0.875rem;
}
.version-display:hover {
background: rgba(0, 0, 0, 0.6);
border-color: rgba(147, 51, 234, 0.3);
color: #ffffff;
}
.version-display-bottom {
position: fixed;
bottom: 3rem;
right: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
font-family: 'JetBrains Mono', monospace;
font-size: 0.75rem;
color: #9ca3af;
z-index: 45;
transition: all 0.3s ease;
}
.version-display-bottom i {
color: #9333ea;
font-size: 0.875rem;
}
.version-display-bottom:hover {
background: rgba(0, 0, 0, 0.8);
border-color: rgba(147, 51, 234, 0.3);
color: #ffffff;
}
.user-info {
display: flex;
@@ -374,10 +437,10 @@ body {
}
.control-btn {
width: 20px;
height: 20px;
border-radius: 50%;
border: none;
width: 28px;
height: 28px;
border-radius: 6px;
border: 1px solid rgba(255, 255, 255, 0.1);
cursor: pointer !important;
transition: all 0.3s ease;
display: flex !important;
@@ -386,24 +449,36 @@ body {
position: relative;
z-index: 100000 !important;
pointer-events: auto !important;
backdrop-filter: blur(10px);
}
.control-btn i {
font-size: 0.5rem;
opacity: 0;
font-size: 0.75rem;
opacity: 0.7;
transition: opacity 0.3s ease;
color: white;
}
.control-btn:hover i {
opacity: 1;
}
.maximize {
background: rgba(34, 197, 94, 0.2);
}
.maximize:hover {
background: rgba(34, 197, 94, 0.4);
border-color: rgba(34, 197, 94, 0.5);
}
.minimize {
background: rgba(251, 191, 36, 0.2);
}
.minimize:hover {
background: #fbbf24;
background: rgba(251, 191, 36, 0.4);
border-color: rgba(251, 191, 36, 0.5);
}
.close {
@@ -411,7 +486,8 @@ body {
}
.close:hover {
background: #ef4444;
background: rgba(239, 68, 68, 0.4);
border-color: rgba(239, 68, 68, 0.5);
}
@@ -429,7 +505,7 @@ body {
}
.title-accent {
color: #9333ea;
color: #bf84f7;
text-shadow: 0 0 20px rgba(147, 51, 234, 0.5);
}
@@ -928,15 +1004,22 @@ body {
.news-grid-horizontal {
display: flex;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
grid-auto-rows: minmax(200px, 1fr);
gap: 1rem;
overflow-x: auto;
overflow-y: auto;
overflow-x: hidden;
padding-bottom: 1rem;
scrollbar-width: thin;
scrollbar-color: rgba(147, 51, 234, 0.3) transparent;
flex: 1;
min-height: 0;
align-content: start;
}
.news-grid-horizontal::-webkit-scrollbar {
width: 6px;
height: 6px;
}
@@ -954,9 +1037,11 @@ body {
}
.news-grid-horizontal .news-item {
min-width: 300px;
max-width: 300px;
height: 200px;
width: 100%;
min-width: 0;
max-width: none;
height: auto;
aspect-ratio: 16 / 9;
flex-shrink: 0;
}
@@ -997,6 +1082,12 @@ body {
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* Style spécifique pour LATEST NEWS (Play tab) */
.news-grid-horizontal .news-card {
aspect-ratio: unset;
height: 100%;
}
.news-card:hover {
box-shadow: 0 8px 40px rgba(147, 51, 234, 0.2);
border-color: rgba(147, 51, 234, 0.3);
@@ -1500,44 +1591,55 @@ body {
.progress-overlay {
position: fixed;
bottom: 1rem;
left: 1rem;
right: 1rem;
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(30px);
border: 2px solid rgba(147, 51, 234, 0.3);
border-radius: 16px;
padding: 2rem;
z-index: 50;
bottom: 1.5rem;
left: 50%;
transform: translateX(-50%);
width: 400px;
background: rgba(15, 23, 42, 0.95);
backdrop-filter: blur(20px);
border: 1px solid rgba(147, 51, 234, 0.3);
border-radius: 12px;
padding: 1.25rem;
z-index: 60;
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.6),
0 0 40px rgba(147, 51, 234, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
0 4px 16px rgba(0, 0, 0, 0.5),
0 0 30px rgba(147, 51, 234, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
animation: progressGlow 3s ease-in-out infinite alternate;
cursor: move;
user-select: none;
}
.progress-overlay.dragging {
cursor: grabbing;
box-shadow:
0 8px 24px rgba(0, 0, 0, 0.7),
0 0 50px rgba(147, 51, 234, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
@keyframes progressGlow {
0% {
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.6),
0 0 40px rgba(147, 51, 234, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
0 4px 16px rgba(0, 0, 0, 0.5),
0 0 30px rgba(147, 51, 234, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
border-color: rgba(147, 51, 234, 0.3);
}
100% {
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.6),
0 0 60px rgba(147, 51, 234, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
border-color: rgba(147, 51, 234, 0.5);
0 4px 16px rgba(0, 0, 0, 0.5),
0 0 40px rgba(147, 51, 234, 0.25),
inset 0 1px 0 rgba(255, 255, 255, 0.05);
border-color: rgba(147, 51, 234, 0.4);
}
}
.progress-content {
display: flex;
flex-direction: column;
gap: 1.5rem;
gap: 0.75rem;
}
.progress-info {
@@ -1548,7 +1650,7 @@ body {
.progress-info span {
font-family: 'JetBrains Mono', monospace;
font-size: 0.875rem;
font-size: 0.8rem;
}
#progressText {
@@ -1572,8 +1674,8 @@ body {
#progressPercent {
color: #9333ea;
font-weight: 700;
font-size: 2rem;
text-shadow: 0 0 20px rgba(147, 51, 234, 0.8);
font-size: 1.25rem;
text-shadow: 0 0 15px rgba(147, 51, 234, 0.6);
animation: percentGlow 1.5s ease-in-out infinite;
}
@@ -1592,15 +1694,15 @@ body {
}
.progress-bar-container {
height: 16px;
height: 10px;
background: linear-gradient(90deg, #1f2937, #374151);
border: 2px solid rgba(147, 51, 234, 0.2);
border-radius: 12px;
border: 1px solid rgba(147, 51, 234, 0.2);
border-radius: 8px;
overflow: hidden;
position: relative;
box-shadow:
inset 0 2px 4px rgba(0, 0, 0, 0.5),
0 0 20px rgba(147, 51, 234, 0.1);
0 0 15px rgba(147, 51, 234, 0.1);
}
.progress-bar-container::before {
@@ -1636,15 +1738,15 @@ body {
#06b6d4 75%,
#10b981 100%);
background-size: 200% 100%;
border-radius: 10px;
border-radius: 6px;
width: 0%;
transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
animation: progressFlow 3s linear infinite;
box-shadow:
0 0 30px rgba(147, 51, 234, 0.6),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
0 0 20px rgba(147, 51, 234, 0.5),
inset 0 1px 0 rgba(255, 255, 255, 0.2);
}
@keyframes progressFlow {
@@ -1692,6 +1794,71 @@ body {
text-shadow: 0 0 5px rgba(156, 163, 175, 0.3);
}
/* Installation effects */
.installation-effects {
position: fixed;
top: 0;
left: 80px;
width: calc(100% - 80px);
height: 100%;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(10px);
z-index: 40;
pointer-events: auto;
overflow: hidden;
}
.space-effects {
position: absolute;
width: 100%;
height: 100%;
perspective: 1000px;
}
.warp-line {
position: absolute;
width: 2px;
height: 100%;
background: linear-gradient(180deg,
transparent 0%,
rgba(147, 51, 234, 0.8) 50%,
transparent 100%);
box-shadow: 0 0 10px rgba(147, 51, 234, 0.8),
0 0 20px rgba(147, 51, 234, 0.4);
animation: warpSpeed 1.5s linear infinite;
opacity: 0;
}
.warp-line:nth-child(1) { left: 10%; animation-delay: 0s; }
.warp-line:nth-child(2) { left: 25%; animation-delay: 0.2s; }
.warp-line:nth-child(3) { left: 40%; animation-delay: 0.4s; }
.warp-line:nth-child(4) { left: 55%; animation-delay: 0.6s; }
.warp-line:nth-child(5) { left: 70%; animation-delay: 0.8s; }
.warp-line:nth-child(6) { left: 85%; animation-delay: 1s; }
.warp-line:nth-child(7) { left: 15%; animation-delay: 0.3s; }
.warp-line:nth-child(8) { left: 60%; animation-delay: 0.7s; }
@keyframes warpSpeed {
0% {
transform: translateY(-100%) scaleY(0);
opacity: 0;
}
10% {
opacity: 1;
}
50% {
opacity: 1;
transform: translateY(0%) scaleY(1);
}
90% {
opacity: 1;
}
100% {
transform: translateY(100%) scaleY(2);
opacity: 0;
}
}
.mods-manager {
display: flex;
@@ -4404,6 +4571,27 @@ select.settings-input option {
0 0 0 1px rgba(255, 255, 255, 0.05) !important;
}
.update-download-btn-secondary {
background: rgba(255, 255, 255, 0.1) !important;
border: 1px solid rgba(255, 255, 255, 0.2) !important;
box-shadow:
0 2px 8px rgba(0, 0, 0, 0.2),
0 0 0 1px rgba(255, 255, 255, 0.05) !important;
}
.update-download-btn-secondary:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.15) !important;
border-color: rgba(255, 255, 255, 0.3) !important;
transform: translateY(-1px) !important;
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.3),
0 0 0 1px rgba(255, 255, 255, 0.1) !important;
}
.update-download-btn-secondary:active:not(:disabled) {
transform: translateY(0) !important;
}
.update-popup-footer {
text-align: center !important;