mirror of
https://gitea.shironeko-all.duckdns.org/shironeko/Hytale-F2P-2.git
synced 2026-02-26 02:31:46 -03:00
Profile System & Mod Loading Fixes
Added a full profile system and fixed a few critical mod loading issues. What changed Profiles — Implemented proper profile management (create, switch, delete). Each profile now has its own isolated mod list. Mod Isolation — Fixed ModManager so mods are strictly scoped to the active profile. Browsing and installing only affects the selected profile. Critical Fix — Fixed a path bug where mods were being saved to ~/AppData/Local on macOS (Windows path) instead of ~/Library/Application Support. Mods now save to the correct location and load correctly in-game. Stability — Added an auto-sync step before every launch to make sure the physical mods folder always matches the active profile. UI — Added a profile selector dropdown and a profile management modal.
This commit is contained in:
200
GUI/index.html
200
GUI/index.html
@@ -1,25 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Hytale F2P Launcher</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<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" />
|
||||
<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" baseFrequency="0.65" numOctaves="3" stitchTiles="stitch"/%3E%3C/filter%3E%3Crect width="100%25" height="100%25" filter="url(%23noiseFilter)" opacity="0.1"/%3E%3C/svg%3E')] opacity-20"></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"
|
||||
baseFrequency="0.65" numOctaves="3" stitchTiles="stitch" /%3E%3C/filter%3E%3Crect width="100%25"
|
||||
height="100%25" filter="url(%23noiseFilter)" opacity="0.1" /%3E%3C/svg%3E')] opacity-20"></div>
|
||||
</div>
|
||||
|
||||
<div class="flex w-full h-screen relative z-10">
|
||||
<nav class="sidebar">
|
||||
<div class="sidebar-logo">
|
||||
<img src="./icon.png" alt="Hytale Logo" />
|
||||
<img src="./icon.png" alt="Hytale Logo" />
|
||||
|
||||
</div>
|
||||
|
||||
@@ -50,7 +57,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</nav>
|
||||
|
||||
<main class="main-content">
|
||||
@@ -61,6 +68,24 @@
|
||||
<span id="onlineCount" class="counter-value">0</span>
|
||||
</div>
|
||||
|
||||
<div class="profile-selector" id="profileSelector">
|
||||
<button class="profile-btn" onclick="toggleProfileDropdown()">
|
||||
<i class="fas fa-user-circle"></i>
|
||||
<span id="currentProfileName">Default</span>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
<div class="profile-dropdown" id="profileDropdown">
|
||||
<div class="profile-list" id="profileList">
|
||||
<!-- Profiles populated by JS -->
|
||||
</div>
|
||||
<div class="profile-divider"></div>
|
||||
<div class="profile-action" onclick="openProfileManager()">
|
||||
<i class="fas fa-cog"></i>
|
||||
<span>Manage Profiles</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="window-controls">
|
||||
<button class="control-btn minimize" onclick="window.electronAPI?.minimizeWindow()">
|
||||
<i class="fas fa-minus"></i>
|
||||
@@ -89,24 +114,26 @@
|
||||
</h1>
|
||||
<p class="install-subtitle">FREE TO PLAY LAUNCHER</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="install-form">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Player Name</label>
|
||||
<input type="text" id="installPlayerName" placeholder="Enter your name" class="form-input" value="Player" />
|
||||
<input type="text" id="installPlayerName" placeholder="Enter your name"
|
||||
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">Custom Installation</span>
|
||||
</label>
|
||||
|
||||
|
||||
<div id="installCustomOptions" class="custom-options">
|
||||
<div class="form-subgroup">
|
||||
<label class="form-label">Installation Folder</label>
|
||||
<div class="input-with-button">
|
||||
<input type="text" id="installPath" placeholder="Default location" class="form-input" readonly />
|
||||
<input type="text" id="installPath" placeholder="Default location"
|
||||
class="form-input" readonly />
|
||||
<button onclick="browseInstallPath()" class="browse-btn">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
</button>
|
||||
@@ -114,7 +141,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<button id="installBtn" class="install-button" onclick="installGame()">
|
||||
<i class="fas fa-download mr-2"></i>
|
||||
<span id="installText">INSTALL HYTALE</span>
|
||||
@@ -134,7 +161,7 @@
|
||||
</h2>
|
||||
<p class="play-subtitle">Launch Hytale and enter the adventure</p>
|
||||
</div>
|
||||
|
||||
|
||||
<button id="homePlayBtn" class="home-play-button" onclick="launch()">
|
||||
<i class="fas fa-play"></i>
|
||||
<span>PLAY HYTALE</span>
|
||||
@@ -216,21 +243,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="chat-body">
|
||||
<div id="chatMessages" class="chat-messages">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="chat-footer">
|
||||
<div class="chat-input-container">
|
||||
<textarea
|
||||
id="chatInput"
|
||||
class="chat-input"
|
||||
placeholder="Type your message... (Links are automatically censored)"
|
||||
rows="1"
|
||||
maxlength="500"
|
||||
></textarea>
|
||||
<textarea id="chatInput" class="chat-input"
|
||||
placeholder="Type your message... (Links are automatically censored)" rows="1"
|
||||
maxlength="500"></textarea>
|
||||
<button id="chatSendBtn" class="chat-send-btn">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
</button>
|
||||
@@ -254,36 +277,32 @@
|
||||
SETTINGS
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="settings-content">
|
||||
<div class="settings-section">
|
||||
<h3 class="settings-section-title">
|
||||
<i class="fas fa-coffee"></i>
|
||||
Java Runtime
|
||||
</h3>
|
||||
|
||||
|
||||
<div class="settings-option">
|
||||
<label class="settings-checkbox">
|
||||
<input type="checkbox" id="customJavaCheck" />
|
||||
<span class="checkmark"></span>
|
||||
<div class="checkbox-content">
|
||||
<div class="checkbox-title">Use Custom Java Path</div>
|
||||
<div class="checkbox-description">Override the bundled Java runtime with your own installation</div>
|
||||
<div class="checkbox-description">Override the bundled Java runtime with
|
||||
your own installation</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="customJavaOptions" class="custom-java-options" style="display: none;">
|
||||
<div class="settings-input-group">
|
||||
<label class="settings-input-label">Java Executable Path</label>
|
||||
<div class="settings-input-with-button">
|
||||
<input
|
||||
type="text"
|
||||
id="customJavaPath"
|
||||
class="settings-input"
|
||||
placeholder="Select Java path..."
|
||||
readonly
|
||||
/>
|
||||
<input type="text" id="customJavaPath" class="settings-input"
|
||||
placeholder="Select Java path..." readonly />
|
||||
<button id="browseJavaBtn" class="settings-browse-btn">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
Browse
|
||||
@@ -302,14 +321,15 @@
|
||||
<i class="fab fa-discord"></i>
|
||||
Discord Integration
|
||||
</h3>
|
||||
|
||||
|
||||
<div class="settings-option">
|
||||
<label class="settings-checkbox">
|
||||
<input type="checkbox" id="discordRPCCheck" checked />
|
||||
<span class="checkmark"></span>
|
||||
<div class="checkbox-content">
|
||||
<div class="checkbox-title">Enable Discord Rich Presence</div>
|
||||
<div class="checkbox-description">Show your launcher activity on Discord</div>
|
||||
<div class="checkbox-description">Show your launcher activity on Discord
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
@@ -320,27 +340,23 @@
|
||||
<i class="fas fa-gamepad"></i>
|
||||
Game Options
|
||||
</h3>
|
||||
|
||||
|
||||
<div class="settings-option">
|
||||
<div class="settings-input-group">
|
||||
<label class="settings-input-label">Player Name</label>
|
||||
<input
|
||||
type="text"
|
||||
id="settingsPlayerName"
|
||||
class="settings-input"
|
||||
placeholder="Enter your player name"
|
||||
maxlength="16"
|
||||
/>
|
||||
<input type="text" id="settingsPlayerName" class="settings-input"
|
||||
placeholder="Enter your player name" maxlength="16" />
|
||||
<p class="settings-hint">
|
||||
<i class="fas fa-user"></i>
|
||||
This name will be used in-game (1-16 characters)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="settings-option">
|
||||
<div class="settings-button-group">
|
||||
<button id="openGameLocationBtn" class="settings-action-btn" onclick="openGameLocation()">
|
||||
<button id="openGameLocationBtn" class="settings-action-btn"
|
||||
onclick="openGameLocation()">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
<div class="btn-content">
|
||||
<div class="btn-title">Open Game Location</div>
|
||||
@@ -356,22 +372,18 @@
|
||||
<i class="fas fa-fingerprint"></i>
|
||||
Player UUID Management
|
||||
</h3>
|
||||
|
||||
|
||||
<div class="settings-option">
|
||||
<div class="settings-input-group">
|
||||
<label class="settings-input-label">Current UUID</label>
|
||||
<div class="uuid-display-container">
|
||||
<input
|
||||
type="text"
|
||||
id="currentUuid"
|
||||
class="settings-input uuid-input"
|
||||
readonly
|
||||
placeholder="Loading UUID..."
|
||||
/>
|
||||
<input type="text" id="currentUuid" class="settings-input uuid-input"
|
||||
readonly placeholder="Loading UUID..." />
|
||||
<button id="copyUuidBtn" class="uuid-btn copy-btn" title="Copy UUID">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
<button id="regenerateUuidBtn" class="uuid-btn regenerate-btn" title="Generate New UUID">
|
||||
<button id="regenerateUuidBtn" class="uuid-btn regenerate-btn"
|
||||
title="Generate New UUID">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -458,14 +470,8 @@
|
||||
</p>
|
||||
<div class="chat-username-input-group">
|
||||
<label for="chatUsernameInput" class="chat-username-label">Username</label>
|
||||
<input
|
||||
type="text"
|
||||
id="chatUsernameInput"
|
||||
class="chat-username-input"
|
||||
placeholder="Enter your username..."
|
||||
maxlength="20"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<input type="text" id="chatUsernameInput" class="chat-username-input"
|
||||
placeholder="Enter your username..." maxlength="20" autocomplete="off" />
|
||||
<span class="chat-username-hint">3-20 characters, letters, numbers, - and _ only</span>
|
||||
<span id="chatUsernameError" class="chat-username-error"></span>
|
||||
</div>
|
||||
@@ -495,7 +501,7 @@
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="uuid-modal-body">
|
||||
<div class="uuid-current-section">
|
||||
<h3 class="uuid-section-title">Current User UUID</h3>
|
||||
@@ -504,7 +510,8 @@
|
||||
<button id="modalCopyUuidBtn" class="uuid-action-btn copy-btn" title="Copy UUID">
|
||||
<i class="fas fa-copy"></i>
|
||||
</button>
|
||||
<button id="modalRegenerateUuidBtn" class="uuid-action-btn regenerate-btn" title="Generate New UUID">
|
||||
<button id="modalRegenerateUuidBtn" class="uuid-action-btn regenerate-btn"
|
||||
title="Generate New UUID">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -529,13 +536,9 @@
|
||||
<div class="uuid-custom-section">
|
||||
<h3 class="uuid-section-title">Set Custom UUID</h3>
|
||||
<div class="uuid-custom-form">
|
||||
<input
|
||||
type="text"
|
||||
id="customUuidInput"
|
||||
class="uuid-input"
|
||||
<input type="text" id="customUuidInput" class="uuid-input"
|
||||
placeholder="Enter custom UUID (format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)"
|
||||
maxlength="36"
|
||||
/>
|
||||
maxlength="36" />
|
||||
<button id="setCustomUuidBtn" class="uuid-set-btn">
|
||||
<i class="fas fa-check"></i>
|
||||
Set UUID
|
||||
@@ -550,27 +553,64 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profile Manager Modal -->
|
||||
<div id="profileManagerModal" class="profile-modal" style="display: none;">
|
||||
<div class="profile-modal-content">
|
||||
<div class="profile-modal-header">
|
||||
<h2 class="profile-modal-title">
|
||||
<i class="fas fa-users-cog mr-2"></i>
|
||||
Manage Profiles
|
||||
</h2>
|
||||
<button class="modal-close-btn" onclick="closeProfileManager()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="profile-modal-body">
|
||||
<div class="profile-manager-list" id="managerProfileList">
|
||||
<!-- Populated by JS -->
|
||||
</div>
|
||||
<div class="profile-create-section">
|
||||
<input type="text" id="newProfileName" placeholder="New Profile Name" class="profile-input"
|
||||
maxlength="20">
|
||||
<button class="profile-create-btn" onclick="createNewProfile()">
|
||||
<i class="fas fa-plus"></i> Create Profile
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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" class="text-blue-400 hover:text-blue-300 transition-colors">@amiayweb</a> & <a href="https://github.com/Relyz1993" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@Relyz</a></span>
|
||||
<span>Made by <a href="https://github.com/amiayweb" target="_blank"
|
||||
class="text-blue-400 hover:text-blue-300 transition-colors">@amiayweb</a> & <a
|
||||
href="https://github.com/Relyz1993" target="_blank"
|
||||
class="text-blue-400 hover:text-blue-300 transition-colors">@Relyz</a></span>
|
||||
<span class="mx-2">|</span>
|
||||
<span>Contributors:
|
||||
<a href="https://github.com/chasem-dev" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@chasem-dev</a>,
|
||||
<a href="https://github.com/crimera" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@crimera</a>,
|
||||
<a href="https://github.com/sanasol" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@sanasol</a>,
|
||||
<a href="https://github.com/Terromur" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@terromur</a>,
|
||||
<a href="https://github.com/ericiskoolbeans" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@ericiskoolbeans</a>,
|
||||
<a href="https://github.com/fazrigading" target="_blank" class="text-blue-400 hover:text-blue-300 transition-colors">@fazrigading</a>
|
||||
<span>Contributors:
|
||||
<a href="https://github.com/chasem-dev" target="_blank"
|
||||
class="text-blue-400 hover:text-blue-300 transition-colors">@chasem-dev</a>,
|
||||
<a href="https://github.com/crimera" target="_blank"
|
||||
class="text-blue-400 hover:text-blue-300 transition-colors">@crimera</a>,
|
||||
<a href="https://github.com/sanasol" target="_blank"
|
||||
class="text-blue-400 hover:text-blue-300 transition-colors">@sanasol</a>,
|
||||
<a href="https://github.com/Terromur" target="_blank"
|
||||
class="text-blue-400 hover:text-blue-300 transition-colors">@terromur</a>,
|
||||
<a href="https://github.com/ericiskoolbeans" target="_blank"
|
||||
class="text-blue-400 hover:text-blue-300 transition-colors">@ericiskoolbeans</a>,
|
||||
<a href="https://github.com/fazrigading" target="_blank"
|
||||
class="text-blue-400 hover:text-blue-300 transition-colors">@fazrigading</a>
|
||||
</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="module" src="js/script.js"></script> <!-- Discord Notification -->
|
||||
<script type="module" src="js/script.js"></script> <!-- Discord Notification -->
|
||||
<div id="discordNotification" class="discord-notification">
|
||||
<div class="notification-content">
|
||||
<i class="fab fa-discord"></i>
|
||||
<span class="notification-text">Join our Discord community!</span>
|
||||
<button class="notification-action" onclick="window.electronAPI?.openExternal('https://discord.gg/n6HZ7NwSQd')">
|
||||
<button class="notification-action"
|
||||
onclick="window.electronAPI?.openExternal('https://discord.gg/n6HZ7NwSQd')">
|
||||
Join Discord
|
||||
</button>
|
||||
</div>
|
||||
@@ -625,4 +665,4 @@
|
||||
<script type="module" src="js/update.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
@@ -14,15 +14,15 @@ export function setupLauncher() {
|
||||
uninstallBtn = document.getElementById('uninstallBtn');
|
||||
playerNameInput = document.getElementById('playerName');
|
||||
javaPathInput = document.getElementById('javaPath');
|
||||
|
||||
|
||||
if (playerNameInput) {
|
||||
playerNameInput.addEventListener('change', savePlayerName);
|
||||
}
|
||||
|
||||
|
||||
if (javaPathInput) {
|
||||
javaPathInput.addEventListener('change', saveJavaPath);
|
||||
}
|
||||
|
||||
|
||||
if (window.electronAPI && window.electronAPI.onProgressUpdate) {
|
||||
window.electronAPI.onProgressUpdate((data) => {
|
||||
if (!isDownloading) return;
|
||||
@@ -31,43 +31,207 @@ export function setupLauncher() {
|
||||
}
|
||||
});
|
||||
}
|
||||
if (window.electronAPI && window.electronAPI.onProgressUpdate) {
|
||||
window.electronAPI.onProgressUpdate((data) => {
|
||||
if (!isDownloading) return;
|
||||
if (window.LauncherUI) {
|
||||
window.LauncherUI.updateProgress(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initial Profile Load
|
||||
loadProfiles();
|
||||
|
||||
// Close dropdown on outside click
|
||||
document.addEventListener('click', (e) => {
|
||||
const selector = document.getElementById('profileSelector');
|
||||
if (selector && !selector.contains(e.target)) {
|
||||
const dropdown = document.getElementById('profileDropdown');
|
||||
if (dropdown) dropdown.classList.remove('show');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// PROFILE MANAGEMENT
|
||||
// ==========================================
|
||||
|
||||
async function loadProfiles() {
|
||||
try {
|
||||
if (!window.electronAPI || !window.electronAPI.profile) return;
|
||||
|
||||
const profiles = await window.electronAPI.profile.list();
|
||||
const activeProfile = await window.electronAPI.profile.getActive();
|
||||
|
||||
renderProfileList(profiles, activeProfile);
|
||||
updateCurrentProfileUI(activeProfile);
|
||||
} catch (error) {
|
||||
console.error('Failed to load profiles:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function renderProfileList(profiles, activeProfile) {
|
||||
const list = document.getElementById('profileList');
|
||||
const managerList = document.getElementById('managerProfileList');
|
||||
|
||||
if (!list) return;
|
||||
|
||||
// Dropdown List
|
||||
list.innerHTML = profiles.map(p => `
|
||||
<div class="profile-item ${p.id === activeProfile.id ? 'active' : ''}"
|
||||
onclick="switchProfile('${p.id}')">
|
||||
<span>${p.name}</span>
|
||||
${p.id === activeProfile.id ? '<i class="fas fa-check ml-auto"></i>' : ''}
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// Manager Modal List
|
||||
if (managerList) {
|
||||
managerList.innerHTML = profiles.map(p => `
|
||||
<div class="profile-manager-item ${p.id === activeProfile.id ? 'active' : ''}">
|
||||
<div class="flex items-center gap-3">
|
||||
<i class="fas fa-user-circle text-xl text-gray-400"></i>
|
||||
<div>
|
||||
<div class="font-bold">${p.name}</div>
|
||||
<div class="text-xs text-gray-500">ID: ${p.id.substring(0, 8)}...</div>
|
||||
</div>
|
||||
</div>
|
||||
${p.id !== activeProfile.id ? `
|
||||
<button class="profile-delete-btn" onclick="deleteProfile('${p.id}')" title="Delete Profile">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
` : '<span class="text-xs text-green-500 font-bold px-2">ACTIVE</span>'}
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
function updateCurrentProfileUI(profile) {
|
||||
const nameEl = document.getElementById('currentProfileName');
|
||||
if (nameEl && profile) {
|
||||
nameEl.textContent = profile.name;
|
||||
}
|
||||
}
|
||||
|
||||
window.toggleProfileDropdown = () => {
|
||||
const dropdown = document.getElementById('profileDropdown');
|
||||
if (dropdown) {
|
||||
dropdown.classList.toggle('show');
|
||||
}
|
||||
};
|
||||
|
||||
window.openProfileManager = () => {
|
||||
const modal = document.getElementById('profileManagerModal');
|
||||
if (modal) {
|
||||
modal.style.display = 'flex';
|
||||
// Refresh list
|
||||
loadProfiles();
|
||||
}
|
||||
// Close dropdown
|
||||
const dropdown = document.getElementById('profileDropdown');
|
||||
if (dropdown) dropdown.classList.remove('show');
|
||||
};
|
||||
|
||||
window.closeProfileManager = () => {
|
||||
const modal = document.getElementById('profileManagerModal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
};
|
||||
|
||||
window.createNewProfile = async () => {
|
||||
const input = document.getElementById('newProfileName');
|
||||
if (!input || !input.value.trim()) return;
|
||||
|
||||
try {
|
||||
const name = input.value.trim();
|
||||
await window.electronAPI.profile.create(name);
|
||||
input.value = '';
|
||||
await loadProfiles();
|
||||
} catch (error) {
|
||||
console.error('Failed to create profile:', error);
|
||||
alert('Failed to create profile: ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
window.deleteProfile = async (id) => {
|
||||
if (!confirm('Are you sure you want to delete this profile? parameters and mods configuration will be lost.')) return;
|
||||
|
||||
try {
|
||||
await window.electronAPI.profile.delete(id);
|
||||
await loadProfiles();
|
||||
} catch (error) {
|
||||
console.error('Failed to delete profile:', error);
|
||||
alert('Failed to delete profile: ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
window.switchProfile = async (id) => {
|
||||
try {
|
||||
if (window.LauncherUI) window.LauncherUI.showProgress();
|
||||
if (window.LauncherUI) window.LauncherUI.updateProgress({ message: 'Switching Profile...' });
|
||||
|
||||
await window.electronAPI.profile.activate(id);
|
||||
|
||||
// Refresh UI
|
||||
await loadProfiles();
|
||||
|
||||
// Refresh Mods
|
||||
if (window.modsManager) {
|
||||
if (window.modsManager.loadInstalledMods) await window.modsManager.loadInstalledMods();
|
||||
if (window.modsManager.loadBrowseMods) await window.modsManager.loadBrowseMods();
|
||||
}
|
||||
|
||||
// Close dropdown
|
||||
const dropdown = document.getElementById('profileDropdown');
|
||||
if (dropdown) dropdown.classList.remove('show');
|
||||
|
||||
if (window.LauncherUI) {
|
||||
window.LauncherUI.updateProgress({ message: 'Profile Switched!' });
|
||||
setTimeout(() => window.LauncherUI.hideProgress(), 1000);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to switch profile:', error);
|
||||
alert('Failed to switch profile: ' + error.message);
|
||||
if (window.LauncherUI) window.LauncherUI.hideProgress();
|
||||
}
|
||||
};
|
||||
|
||||
export async function launch() {
|
||||
if (isDownloading || (playBtn && playBtn.disabled)) return;
|
||||
|
||||
|
||||
let playerName = 'Player';
|
||||
if (window.SettingsAPI && window.SettingsAPI.getCurrentPlayerName) {
|
||||
playerName = window.SettingsAPI.getCurrentPlayerName();
|
||||
} else if (playerNameInput && playerNameInput.value.trim()) {
|
||||
playerName = playerNameInput.value.trim();
|
||||
}
|
||||
|
||||
|
||||
let javaPath = '';
|
||||
if (window.SettingsAPI && window.SettingsAPI.getCurrentJavaPath) {
|
||||
javaPath = window.SettingsAPI.getCurrentJavaPath();
|
||||
}
|
||||
|
||||
|
||||
if (window.LauncherUI) window.LauncherUI.showProgress();
|
||||
isDownloading = true;
|
||||
if (playBtn) {
|
||||
playBtn.disabled = true;
|
||||
playText.textContent = 'LAUNCHING...';
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
if (window.LauncherUI) window.LauncherUI.updateProgress({ message: 'Starting game...' });
|
||||
|
||||
|
||||
if (window.electronAPI && window.electronAPI.launchGame) {
|
||||
const result = await window.electronAPI.launchGame(playerName, javaPath, '');
|
||||
|
||||
|
||||
isDownloading = false;
|
||||
|
||||
|
||||
if (window.LauncherUI) {
|
||||
window.LauncherUI.hideProgress();
|
||||
}
|
||||
resetPlayButton();
|
||||
|
||||
|
||||
if (result.success) {
|
||||
if (window.electronAPI.minimizeWindow) {
|
||||
setTimeout(() => {
|
||||
@@ -79,7 +243,7 @@ export async function launch() {
|
||||
}
|
||||
} else {
|
||||
isDownloading = false;
|
||||
|
||||
|
||||
if (window.LauncherUI) {
|
||||
window.LauncherUI.hideProgress();
|
||||
}
|
||||
@@ -87,7 +251,7 @@ export async function launch() {
|
||||
}
|
||||
} catch (error) {
|
||||
isDownloading = false;
|
||||
|
||||
|
||||
if (window.LauncherUI) {
|
||||
window.LauncherUI.hideProgress();
|
||||
}
|
||||
@@ -231,15 +395,15 @@ export async function uninstallGame() {
|
||||
}
|
||||
|
||||
async function performUninstall() {
|
||||
|
||||
|
||||
if (window.LauncherUI) window.LauncherUI.showProgress();
|
||||
if (window.LauncherUI) window.LauncherUI.updateProgress({ message: 'Uninstalling game...' });
|
||||
if (uninstallBtn) uninstallBtn.disabled = true;
|
||||
|
||||
|
||||
try {
|
||||
if (window.electronAPI && window.electronAPI.uninstallGame) {
|
||||
const result = await window.electronAPI.uninstallGame();
|
||||
|
||||
|
||||
if (result.success) {
|
||||
if (window.LauncherUI) {
|
||||
window.LauncherUI.updateProgress({ message: 'Game uninstalled successfully!' });
|
||||
@@ -304,7 +468,7 @@ async function saveJavaPath() {
|
||||
|
||||
function toggleCustomJava() {
|
||||
if (!customJavaOptions) return;
|
||||
|
||||
|
||||
if (customJavaCheck && customJavaCheck.checked) {
|
||||
customJavaOptions.style.display = 'block';
|
||||
} else {
|
||||
|
||||
105
GUI/js/mods.js
105
GUI/js/mods.js
@@ -22,10 +22,10 @@ function setupModsEventListeners() {
|
||||
let searchTimeout;
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
searchQuery = e.target.value.toLowerCase().trim();
|
||||
|
||||
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
modsPage = 0;
|
||||
modsPage = 0;
|
||||
loadBrowseMods();
|
||||
}, 500);
|
||||
});
|
||||
@@ -52,7 +52,7 @@ function setupModsEventListeners() {
|
||||
|
||||
const prevPageBtn = document.getElementById('prevPage');
|
||||
const nextPageBtn = document.getElementById('nextPage');
|
||||
|
||||
|
||||
if (prevPageBtn) {
|
||||
prevPageBtn.addEventListener('click', () => {
|
||||
if (modsPage > 0) {
|
||||
@@ -61,7 +61,7 @@ function setupModsEventListeners() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (nextPageBtn) {
|
||||
nextPageBtn.addEventListener('click', () => {
|
||||
if (modsPage < modsTotalPages - 1) {
|
||||
@@ -97,7 +97,7 @@ async function loadInstalledMods() {
|
||||
|
||||
const mods = await window.electronAPI?.loadInstalledMods(modsPath);
|
||||
installedMods = mods || [];
|
||||
|
||||
|
||||
displayInstalledMods(installedMods);
|
||||
} catch (error) {
|
||||
console.error('Error loading installed mods:', error);
|
||||
@@ -196,13 +196,13 @@ async function loadBrowseMods() {
|
||||
|
||||
const offset = modsPage * modsPageSize;
|
||||
let url = `${CURSEFORGE_API}/mods/search?gameId=${HYTALE_GAME_ID}&pageSize=${modsPageSize}&sortOrder=desc&sortField=6&index=${offset}`;
|
||||
|
||||
|
||||
if (searchQuery && searchQuery.length > 0) {
|
||||
url += `&searchFilter=${encodeURIComponent(searchQuery)}`;
|
||||
}
|
||||
|
||||
|
||||
console.log('Fetching mods from page', modsPage + 1, 'offset:', offset, 'search:', searchQuery || 'none', 'URL:', url);
|
||||
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'x-api-key': API_KEY,
|
||||
@@ -221,7 +221,7 @@ async function loadBrowseMods() {
|
||||
const data = await response.json();
|
||||
console.log('API Response data:', data);
|
||||
console.log('Total mods found:', data.data?.length || 0);
|
||||
|
||||
|
||||
browseMods = (data.data || []).map(mod => ({
|
||||
id: mod.id.toString(),
|
||||
name: mod.name,
|
||||
@@ -282,18 +282,25 @@ function displayBrowseMods(mods) {
|
||||
}
|
||||
|
||||
function createBrowseModCard(mod) {
|
||||
const isInstalled = installedMods.some(installed =>
|
||||
installed.name.toLowerCase().includes(mod.name.toLowerCase()) ||
|
||||
installed.curseForgeId == mod.id
|
||||
);
|
||||
const isInstalled = installedMods.some(installed => {
|
||||
// Check by CurseForge ID (most reliable)
|
||||
if (installed.curseForgeId && installed.curseForgeId.toString() === mod.id.toString()) {
|
||||
return true;
|
||||
}
|
||||
// Check by exact name match for manually installed mods
|
||||
if (installed.name.toLowerCase() === mod.name.toLowerCase()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return `
|
||||
<div class=\"mod-card ${isInstalled ? 'installed' : ''}\" data-mod-id=\"${mod.id}\">
|
||||
<div class=\"mod-image\">
|
||||
${mod.thumbnailUrl ?
|
||||
`<img src=\"${mod.thumbnailUrl}\" alt=\"${mod.name}\" onerror=\"this.parentElement.innerHTML='<i class=\\\"fas fa-puzzle-piece\\\"></i>'\">` :
|
||||
`<i class=\"fas fa-puzzle-piece\"></i>`
|
||||
}
|
||||
${mod.thumbnailUrl ?
|
||||
`<img src=\"${mod.thumbnailUrl}\" alt=\"${mod.name}\" onerror=\"this.parentElement.innerHTML='<i class=\\\"fas fa-puzzle-piece\\\"></i>'\">` :
|
||||
`<i class=\"fas fa-puzzle-piece\"></i>`
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class=\"mod-info\">
|
||||
@@ -319,16 +326,16 @@ function createBrowseModCard(mod) {
|
||||
<i class=\"fas fa-external-link-alt\"></i>
|
||||
VIEW
|
||||
</button>
|
||||
${!isInstalled ?
|
||||
`<button id=\"install-${mod.id}\" class=\"mod-btn-toggle bg-primary text-black hover:bg-primary/80\">
|
||||
${!isInstalled ?
|
||||
`<button id=\"install-${mod.id}\" class=\"mod-btn-toggle bg-primary text-black hover:bg-primary/80\">
|
||||
<i class=\"fas fa-download\"></i>
|
||||
INSTALL
|
||||
</button>` :
|
||||
`<button class=\"mod-btn-toggle bg-white/10 text-white\" disabled>
|
||||
`<button class=\"mod-btn-toggle bg-white/10 text-white\" disabled>
|
||||
<i class=\"fas fa-check\"></i>
|
||||
INSTALLED
|
||||
</button>`
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -337,9 +344,9 @@ function createBrowseModCard(mod) {
|
||||
async function downloadAndInstallMod(modInfo) {
|
||||
try {
|
||||
window.LauncherUI?.showProgress(`Downloading ${modInfo.name}...`);
|
||||
|
||||
|
||||
const result = await window.electronAPI?.downloadMod(modInfo);
|
||||
|
||||
|
||||
if (result?.success) {
|
||||
const newMod = {
|
||||
id: result.modInfo.id,
|
||||
@@ -354,11 +361,11 @@ async function downloadAndInstallMod(modInfo) {
|
||||
curseForgeId: modInfo.modId,
|
||||
curseForgeFileId: modInfo.fileId
|
||||
};
|
||||
|
||||
|
||||
installedMods.push(newMod);
|
||||
|
||||
|
||||
await loadInstalledMods();
|
||||
await loadBrowseMods();
|
||||
await loadBrowseMods();
|
||||
window.LauncherUI?.hideProgress();
|
||||
showNotification(`${modInfo.name} installed successfully! 🎉`, 'success');
|
||||
} else {
|
||||
@@ -374,10 +381,10 @@ async function downloadAndInstallMod(modInfo) {
|
||||
async function toggleMod(modId) {
|
||||
try {
|
||||
window.LauncherUI?.showProgress('Toggling mod...');
|
||||
|
||||
|
||||
const modsPath = await window.electronAPI?.getModsPath();
|
||||
const result = await window.electronAPI?.toggleMod(modId, modsPath);
|
||||
|
||||
|
||||
if (result?.success) {
|
||||
await loadInstalledMods();
|
||||
window.LauncherUI?.hideProgress();
|
||||
@@ -400,13 +407,13 @@ async function deleteMod(modId) {
|
||||
async () => {
|
||||
try {
|
||||
window.LauncherUI?.showProgress('Deleting mod...');
|
||||
|
||||
|
||||
const modsPath = await window.electronAPI?.getModsPath();
|
||||
const result = await window.electronAPI?.uninstallMod(modId, modsPath);
|
||||
|
||||
|
||||
if (result?.success) {
|
||||
await loadInstalledMods();
|
||||
await loadBrowseMods();
|
||||
await loadBrowseMods();
|
||||
window.LauncherUI?.hideProgress();
|
||||
showNotification(`"${mod.name}" deleted successfully`, 'success');
|
||||
} else {
|
||||
@@ -436,21 +443,21 @@ function showNotification(message, type = 'info', duration = 4000) {
|
||||
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `mod-notification ${type}`;
|
||||
|
||||
|
||||
const icons = {
|
||||
success: 'fa-check-circle',
|
||||
error: 'fa-exclamation-circle',
|
||||
info: 'fa-info-circle',
|
||||
warning: 'fa-exclamation-triangle'
|
||||
};
|
||||
|
||||
|
||||
const colors = {
|
||||
success: '#10b981',
|
||||
error: '#ef4444',
|
||||
error: '#ef4444',
|
||||
info: '#3b82f6',
|
||||
warning: '#f59e0b'
|
||||
};
|
||||
|
||||
|
||||
notification.innerHTML = `
|
||||
<div class="notification-content">
|
||||
<i class="fas ${icons[type]}"></i>
|
||||
@@ -460,7 +467,7 @@ function showNotification(message, type = 'info', duration = 4000) {
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
`;
|
||||
|
||||
|
||||
notification.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
@@ -481,14 +488,14 @@ function showNotification(message, type = 'info', duration = 4000) {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
|
||||
const contentStyle = `
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
|
||||
const closeStyle = `
|
||||
background: none;
|
||||
border: none;
|
||||
@@ -500,17 +507,17 @@ function showNotification(message, type = 'info', duration = 4000) {
|
||||
transition: opacity 0.2s;
|
||||
margin-left: 10px;
|
||||
`;
|
||||
|
||||
|
||||
notification.querySelector('.notification-content').style.cssText = contentStyle;
|
||||
notification.querySelector('.notification-close').style.cssText = closeStyle;
|
||||
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
|
||||
// Animate in
|
||||
setTimeout(() => {
|
||||
notification.style.transform = 'translateX(0)';
|
||||
}, 10);
|
||||
|
||||
|
||||
// Auto remove
|
||||
setTimeout(() => {
|
||||
if (notification.parentElement) {
|
||||
@@ -651,13 +658,13 @@ function updatePagination() {
|
||||
|
||||
if (currentPageEl) currentPageEl.textContent = modsPage + 1;
|
||||
if (totalPagesEl) totalPagesEl.textContent = modsTotalPages;
|
||||
|
||||
|
||||
if (prevBtn) {
|
||||
prevBtn.disabled = modsPage === 0;
|
||||
prevBtn.style.opacity = modsPage === 0 ? '0.5' : '1';
|
||||
prevBtn.style.cursor = modsPage === 0 ? 'not-allowed' : 'pointer';
|
||||
}
|
||||
|
||||
|
||||
if (nextBtn) {
|
||||
nextBtn.disabled = modsPage >= modsTotalPages - 1;
|
||||
nextBtn.style.opacity = modsPage >= modsTotalPages - 1 ? '0.5' : '1';
|
||||
@@ -681,7 +688,7 @@ function showInstalledModsError(message) {
|
||||
function viewModPage(modId) {
|
||||
console.log('Looking for mod with ID:', modId, 'Type:', typeof modId);
|
||||
console.log('Available mods:', browseMods.map(m => ({ id: m.id, name: m.name, type: typeof m.id })));
|
||||
|
||||
|
||||
const mod = browseMods.find(m => m.id.toString() === modId.toString());
|
||||
if (mod) {
|
||||
console.log('Found mod:', mod.name);
|
||||
@@ -694,9 +701,9 @@ function viewModPage(modId) {
|
||||
const nameSlug = mod.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
||||
modUrl = `https://www.curseforge.com/hytale/mods/${nameSlug}`;
|
||||
}
|
||||
|
||||
|
||||
console.log('Opening URL:', modUrl);
|
||||
|
||||
|
||||
if (window.electronAPI && window.electronAPI.openExternalLink) {
|
||||
window.electronAPI.openExternalLink(modUrl);
|
||||
} else {
|
||||
@@ -717,7 +724,9 @@ window.modsManager = {
|
||||
deleteMod,
|
||||
openMyModsModal,
|
||||
closeMyModsModal,
|
||||
viewModPage
|
||||
viewModPage,
|
||||
loadInstalledMods,
|
||||
loadBrowseMods
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initModsManager);
|
||||
|
||||
102
GUI/js/ui.js
102
GUI/js/ui.js
@@ -45,22 +45,32 @@ function handleNavigation() {
|
||||
function setupWindowControls() {
|
||||
const minimizeBtn = document.querySelector('.window-controls .minimize');
|
||||
const closeBtn = document.querySelector('.window-controls .close');
|
||||
|
||||
|
||||
const windowControls = document.querySelector('.window-controls');
|
||||
const header = document.querySelector('.header');
|
||||
|
||||
|
||||
const profileSelector = document.querySelector('.profile-selector');
|
||||
|
||||
if (profileSelector) {
|
||||
profileSelector.style.pointerEvents = 'auto';
|
||||
profileSelector.style.zIndex = '10000';
|
||||
}
|
||||
|
||||
if (windowControls) {
|
||||
windowControls.style.pointerEvents = 'auto';
|
||||
windowControls.style.zIndex = '10000';
|
||||
}
|
||||
|
||||
|
||||
if (header) {
|
||||
header.style.webkitAppRegion = 'drag';
|
||||
if (windowControls) {
|
||||
windowControls.style.webkitAppRegion = 'no-drag';
|
||||
}
|
||||
if (profileSelector) {
|
||||
profileSelector.style.webkitAppRegion = 'no-drag';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (window.electronAPI) {
|
||||
if (minimizeBtn) {
|
||||
minimizeBtn.onclick = (e) => {
|
||||
@@ -82,7 +92,7 @@ function showLauncherOrInstall(isInstalled) {
|
||||
const install = document.getElementById('install-page');
|
||||
const sidebar = document.querySelector('.sidebar');
|
||||
const gameTitle = document.querySelector('.game-title-section');
|
||||
|
||||
|
||||
if (isInstalled) {
|
||||
if (launcher) launcher.style.display = '';
|
||||
if (install) install.style.display = 'none';
|
||||
@@ -137,14 +147,14 @@ function updateProgress(data) {
|
||||
if (data.message && progressText) {
|
||||
progressText.textContent = data.message;
|
||||
}
|
||||
|
||||
|
||||
if (data.percent !== null && data.percent !== undefined) {
|
||||
const percent = Math.min(100, Math.max(0, Math.round(data.percent)));
|
||||
if (progressPercent) progressPercent.textContent = `${percent}%`;
|
||||
if (progressBarFill) progressBarFill.style.width = `${percent}%`;
|
||||
if (progressBar) progressBar.style.width = `${percent}%`;
|
||||
}
|
||||
|
||||
|
||||
if (data.speed && data.downloaded && data.total) {
|
||||
const speedMB = (data.speed / 1024 / 1024).toFixed(2);
|
||||
const downloadedMB = (data.downloaded / 1024 / 1024).toFixed(2);
|
||||
@@ -157,13 +167,13 @@ function updateProgress(data) {
|
||||
function setupAnimations() {
|
||||
document.body.style.opacity = '0';
|
||||
document.body.style.transform = 'translateY(20px)';
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
document.body.style.transition = 'all 0.6s ease';
|
||||
document.body.style.opacity = '1';
|
||||
document.body.style.transform = 'translateY(0)';
|
||||
}, 100);
|
||||
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes fadeInUp {
|
||||
@@ -182,7 +192,7 @@ function setupAnimations() {
|
||||
|
||||
function setupFirstLaunchHandlers() {
|
||||
console.log('Setting up first launch handlers...');
|
||||
|
||||
|
||||
window.electronAPI.onFirstLaunchUpdate((data) => {
|
||||
console.log('Received first launch update event:', data);
|
||||
showFirstLaunchUpdateDialog(data);
|
||||
@@ -195,12 +205,12 @@ function setupFirstLaunchHandlers() {
|
||||
showProgress();
|
||||
updateProgress(data);
|
||||
});
|
||||
|
||||
|
||||
let lockButtonTimeout = null;
|
||||
|
||||
|
||||
window.electronAPI.onLockPlayButton((locked) => {
|
||||
lockPlayButton(locked);
|
||||
|
||||
|
||||
if (locked) {
|
||||
if (lockButtonTimeout) {
|
||||
clearTimeout(lockButtonTimeout);
|
||||
@@ -221,12 +231,12 @@ function setupFirstLaunchHandlers() {
|
||||
|
||||
function showFirstLaunchUpdateDialog(data) {
|
||||
console.log('Creating first launch modal...');
|
||||
|
||||
|
||||
const existingModal = document.querySelector('.first-launch-modal-overlay');
|
||||
if (existingModal) {
|
||||
existingModal.remove();
|
||||
}
|
||||
|
||||
|
||||
const modalOverlay = document.createElement('div');
|
||||
modalOverlay.className = 'first-launch-modal-overlay';
|
||||
modalOverlay.style.cssText = `
|
||||
@@ -243,7 +253,7 @@ function showFirstLaunchUpdateDialog(data) {
|
||||
justify-content: center !important;
|
||||
pointer-events: all !important;
|
||||
`;
|
||||
|
||||
|
||||
const modalDialog = document.createElement('div');
|
||||
modalDialog.className = 'first-launch-modal-dialog';
|
||||
modalDialog.style.cssText = `
|
||||
@@ -257,7 +267,7 @@ function showFirstLaunchUpdateDialog(data) {
|
||||
overflow: hidden !important;
|
||||
animation: modalSlideIn 0.3s ease-out !important;
|
||||
`;
|
||||
|
||||
|
||||
modalDialog.innerHTML = `
|
||||
<div style="background: linear-gradient(135deg, rgba(147, 51, 234, 0.2), rgba(59, 130, 246, 0.2)); padding: 25px; border-bottom: 1px solid rgba(255,255,255,0.1);">
|
||||
<h2 style="margin: 0; color: #fff; font-size: 1.5rem; font-weight: 600; text-align: center;">
|
||||
@@ -306,9 +316,9 @@ function showFirstLaunchUpdateDialog(data) {
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
modalOverlay.appendChild(modalDialog);
|
||||
|
||||
|
||||
modalOverlay.onclick = (e) => {
|
||||
if (e.target === modalOverlay) {
|
||||
e.preventDefault();
|
||||
@@ -316,7 +326,7 @@ function showFirstLaunchUpdateDialog(data) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
document.addEventListener('keydown', function preventEscape(e) {
|
||||
if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
@@ -324,33 +334,33 @@ function showFirstLaunchUpdateDialog(data) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
document.body.appendChild(modalOverlay);
|
||||
|
||||
|
||||
const updateBtn = document.getElementById('updateGameBtn');
|
||||
updateBtn.onclick = () => {
|
||||
acceptFirstLaunchUpdate();
|
||||
};
|
||||
|
||||
|
||||
window.firstLaunchExistingGame = data.existingGame;
|
||||
|
||||
|
||||
console.log('First launch modal created and displayed');
|
||||
}
|
||||
|
||||
function lockPlayButton(locked) {
|
||||
const playButton = document.getElementById('homePlayBtn');
|
||||
|
||||
|
||||
if (!playButton) {
|
||||
console.warn('Play button not found');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (locked) {
|
||||
playButton.style.opacity = '0.5';
|
||||
playButton.style.pointerEvents = 'none';
|
||||
playButton.style.cursor = 'not-allowed';
|
||||
playButton.setAttribute('data-locked', 'true');
|
||||
|
||||
|
||||
const spanElement = playButton.querySelector('span');
|
||||
if (spanElement) {
|
||||
if (!playButton.getAttribute('data-original-text')) {
|
||||
@@ -358,21 +368,21 @@ function lockPlayButton(locked) {
|
||||
}
|
||||
spanElement.textContent = 'CHECKING...';
|
||||
}
|
||||
|
||||
|
||||
console.log('Play button locked');
|
||||
} else {
|
||||
playButton.style.opacity = '';
|
||||
playButton.style.pointerEvents = '';
|
||||
playButton.style.cursor = '';
|
||||
playButton.removeAttribute('data-locked');
|
||||
|
||||
|
||||
const spanElement = playButton.querySelector('span');
|
||||
const originalText = playButton.getAttribute('data-original-text');
|
||||
if (spanElement && originalText) {
|
||||
spanElement.textContent = originalText;
|
||||
playButton.removeAttribute('data-original-text');
|
||||
}
|
||||
|
||||
|
||||
console.log('Play button unlocked');
|
||||
}
|
||||
}
|
||||
@@ -381,12 +391,12 @@ function lockPlayButton(locked) {
|
||||
|
||||
async function acceptFirstLaunchUpdate() {
|
||||
const existingGame = window.firstLaunchExistingGame;
|
||||
|
||||
|
||||
if (!existingGame) {
|
||||
showNotification('Error: Game data not found', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const modal = document.querySelector('.first-launch-modal-overlay');
|
||||
if (modal) {
|
||||
modal.style.pointerEvents = 'none';
|
||||
@@ -397,21 +407,21 @@ async function acceptFirstLaunchUpdate() {
|
||||
btn.textContent = '🔄 Updating...';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
showProgress();
|
||||
updateProgress({ message: 'Starting mandatory game update...', percent: 0 });
|
||||
|
||||
|
||||
const result = await window.electronAPI.acceptFirstLaunchUpdate(existingGame);
|
||||
|
||||
|
||||
window.electronAPI.markAsLaunched && window.electronAPI.markAsLaunched();
|
||||
|
||||
|
||||
if (modal) {
|
||||
modal.remove();
|
||||
}
|
||||
|
||||
|
||||
lockPlayButton(false);
|
||||
|
||||
|
||||
if (result.success) {
|
||||
hideProgress();
|
||||
showNotification('Game updated successfully! 🎉', 'success');
|
||||
@@ -434,7 +444,7 @@ function dismissFirstLaunchDialog() {
|
||||
if (modal) {
|
||||
modal.remove();
|
||||
}
|
||||
|
||||
|
||||
lockPlayButton(false);
|
||||
window.electronAPI.markAsLaunched && window.electronAPI.markAsLaunched();
|
||||
}
|
||||
@@ -443,13 +453,13 @@ function showNotification(message, type = 'info') {
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification notification-${type}`;
|
||||
notification.textContent = message;
|
||||
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
notification.classList.add('show');
|
||||
}, 100);
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 5000);
|
||||
@@ -463,9 +473,9 @@ function setupUI() {
|
||||
progressPercent = document.getElementById('progressPercent');
|
||||
progressSpeed = document.getElementById('progressSpeed');
|
||||
progressSize = document.getElementById('progressSize');
|
||||
|
||||
|
||||
lockPlayButton(true);
|
||||
|
||||
|
||||
setTimeout(() => {
|
||||
const playButton = document.getElementById('homePlayBtn');
|
||||
if (playButton && playButton.getAttribute('data-locked') === 'true') {
|
||||
@@ -476,13 +486,13 @@ function setupUI() {
|
||||
}
|
||||
}
|
||||
}, 25000);
|
||||
|
||||
|
||||
handleNavigation();
|
||||
setupWindowControls();
|
||||
setupSidebarLogo();
|
||||
setupAnimations();
|
||||
setupFirstLaunchHandlers();
|
||||
|
||||
|
||||
document.body.focus();
|
||||
}
|
||||
|
||||
|
||||
462
GUI/style.css
462
GUI/style.css
@@ -10,7 +10,7 @@ body {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0, 0, 0, 0.4),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
@@ -263,6 +263,7 @@ body {
|
||||
|
||||
.window-controls {
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
gap: 0.5rem;
|
||||
z-index: 9999;
|
||||
position: relative;
|
||||
@@ -293,6 +294,7 @@ body {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
@@ -552,7 +554,7 @@ body {
|
||||
letter-spacing: 0.1em;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 4px 0 0 #7c3aed,
|
||||
0 8px 20px rgba(147, 51, 234, 0.3);
|
||||
display: flex;
|
||||
@@ -564,7 +566,7 @@ body {
|
||||
|
||||
.install-button:hover {
|
||||
background: linear-gradient(135deg, #7c3aed, #2563eb);
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 2px 0 0 #7c3aed,
|
||||
0 12px 30px rgba(147, 51, 234, 0.4);
|
||||
transform: translateY(2px);
|
||||
@@ -572,7 +574,7 @@ body {
|
||||
|
||||
.install-button:active {
|
||||
transform: translateY(4px);
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 0px 0 0 #7c3aed,
|
||||
0 4px 15px rgba(147, 51, 234, 0.3);
|
||||
}
|
||||
@@ -590,6 +592,7 @@ body {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content-pages {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
@@ -666,7 +669,7 @@ body {
|
||||
letter-spacing: 0.05em;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 4px 0 0 #7c3aed,
|
||||
0 8px 20px rgba(147, 51, 234, 0.3);
|
||||
display: flex;
|
||||
@@ -677,7 +680,7 @@ body {
|
||||
|
||||
.home-play-button:hover {
|
||||
background: linear-gradient(135deg, #7c3aed, #2563eb);
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 2px 0 0 #7c3aed,
|
||||
0 12px 30px rgba(147, 51, 234, 0.4);
|
||||
transform: translateY(2px);
|
||||
@@ -685,7 +688,7 @@ body {
|
||||
|
||||
.home-play-button:active {
|
||||
transform: translateY(4px);
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 0px 0 0 #7c3aed,
|
||||
0 4px 15px rgba(147, 51, 234, 0.3);
|
||||
}
|
||||
@@ -1347,7 +1350,7 @@ body {
|
||||
letter-spacing: 0.1em;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 4px 0 0 #15803d,
|
||||
0 8px 20px rgba(34, 197, 94, 0.3);
|
||||
z-index: 10;
|
||||
@@ -1357,7 +1360,7 @@ body {
|
||||
|
||||
.play-button:hover {
|
||||
background: linear-gradient(135deg, #16a34a, #15803d);
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 2px 0 0 #15803d,
|
||||
0 12px 30px rgba(34, 197, 94, 0.4);
|
||||
transform: translateY(2px);
|
||||
@@ -1365,7 +1368,7 @@ body {
|
||||
|
||||
.play-button:active {
|
||||
transform: translateY(4px);
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 0px 0 0 #15803d,
|
||||
0 4px 15px rgba(34, 197, 94, 0.3);
|
||||
}
|
||||
@@ -1415,7 +1418,7 @@ body {
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
z-index: 50;
|
||||
box-shadow:
|
||||
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);
|
||||
@@ -1424,14 +1427,15 @@ body {
|
||||
|
||||
@keyframes progressGlow {
|
||||
0% {
|
||||
box-shadow:
|
||||
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);
|
||||
border-color: rgba(147, 51, 234, 0.3);
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow:
|
||||
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);
|
||||
@@ -1463,8 +1467,15 @@ body {
|
||||
}
|
||||
|
||||
@keyframes textPulse {
|
||||
0%, 100% { opacity: 0.8; }
|
||||
50% { opacity: 1; }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#progressPercent {
|
||||
@@ -1476,11 +1487,14 @@ body {
|
||||
}
|
||||
|
||||
@keyframes percentGlow {
|
||||
0%, 100% {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
text-shadow: 0 0 20px rgba(147, 51, 234, 0.8);
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
|
||||
50% {
|
||||
text-shadow: 0 0 30px rgba(147, 51, 234, 1);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
@@ -1493,7 +1507,7 @@ body {
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
inset 0 2px 4px rgba(0, 0, 0, 0.5),
|
||||
0 0 20px rgba(147, 51, 234, 0.1);
|
||||
}
|
||||
@@ -1505,28 +1519,31 @@ body {
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.2),
|
||||
transparent
|
||||
);
|
||||
background: linear-gradient(90deg,
|
||||
transparent,
|
||||
rgba(255, 255, 255, 0.2),
|
||||
transparent);
|
||||
animation: shimmer 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { left: -100%; }
|
||||
100% { left: 100%; }
|
||||
0% {
|
||||
left: -100%;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg,
|
||||
#9333ea 0%,
|
||||
#a855f7 25%,
|
||||
#3b82f6 50%,
|
||||
#06b6d4 75%,
|
||||
#10b981 100%
|
||||
);
|
||||
background: linear-gradient(90deg,
|
||||
#9333ea 0%,
|
||||
#a855f7 25%,
|
||||
#3b82f6 50%,
|
||||
#06b6d4 75%,
|
||||
#10b981 100%);
|
||||
background-size: 200% 100%;
|
||||
border-radius: 10px;
|
||||
width: 0%;
|
||||
@@ -1534,14 +1551,19 @@ body {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
animation: progressFlow 3s linear infinite;
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 0 30px rgba(147, 51, 234, 0.6),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
@keyframes progressFlow {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar-fill::after {
|
||||
@@ -1552,16 +1574,22 @@ body {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(90deg,
|
||||
transparent 0%,
|
||||
rgba(255, 255, 255, 0.4) 50%,
|
||||
transparent 100%
|
||||
);
|
||||
transparent 0%,
|
||||
rgba(255, 255, 255, 0.4) 50%,
|
||||
transparent 100%);
|
||||
animation: progressPulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes progressPulse {
|
||||
0%, 100% { opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-details {
|
||||
@@ -1798,8 +1826,13 @@ body {
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { transform: translateX(-100%); }
|
||||
100% { transform: translateX(200%); }
|
||||
0% {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateX(200%);
|
||||
}
|
||||
}
|
||||
|
||||
.mod-image img {
|
||||
@@ -2028,8 +2061,13 @@ body {
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-mods span {
|
||||
@@ -2312,39 +2350,39 @@ body {
|
||||
.mods-header {
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
.mods-title {
|
||||
font-size: 1.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.mods-controls {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
|
||||
.mods-controls button {
|
||||
padding: 0.6rem 1.2rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
|
||||
.mods-main-content {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
.mods-search-bar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.search-input-container {
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
|
||||
.mods-filters {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
|
||||
.mods-list {
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
@@ -2355,15 +2393,15 @@ body {
|
||||
.mods-header {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.mods-main-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.mods-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
.modal-content {
|
||||
width: 95vw;
|
||||
padding: 1.5rem;
|
||||
@@ -2593,7 +2631,7 @@ body {
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
overflow: hidden;
|
||||
margin-top: 1%;
|
||||
margin-top: 1%;
|
||||
}
|
||||
|
||||
.mod-header {
|
||||
@@ -2879,6 +2917,7 @@ body {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -2902,6 +2941,7 @@ body {
|
||||
transform: translateY(50px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
@@ -3319,8 +3359,15 @@ body {
|
||||
}
|
||||
|
||||
@keyframes pulse-dot {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3373,6 +3420,7 @@ body {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
@@ -3550,6 +3598,7 @@ body {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -3571,6 +3620,7 @@ body {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
@@ -3798,13 +3848,13 @@ body {
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.settings-checkbox input[type="checkbox"]:checked + .checkmark {
|
||||
.settings-checkbox input[type="checkbox"]:checked+.checkmark {
|
||||
background: linear-gradient(135deg, #9333ea, #3b82f6);
|
||||
border-color: #9333ea;
|
||||
box-shadow: 0 0 20px rgba(147, 51, 234, 0.4);
|
||||
}
|
||||
|
||||
.settings-checkbox input[type="checkbox"]:checked + .checkmark::after {
|
||||
.settings-checkbox input[type="checkbox"]:checked+.checkmark::after {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@@ -4051,7 +4101,7 @@ body {
|
||||
background: linear-gradient(145deg, #1f2937 0%, #111827 50%, #0f172a 100%) !important;
|
||||
padding: 2rem !important;
|
||||
border-radius: 1rem !important;
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 25px 50px rgba(0, 0, 0, 0.8),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.1) !important;
|
||||
@@ -4068,6 +4118,7 @@ body {
|
||||
opacity: 0;
|
||||
transform: scale(0.8) translateY(-20px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
@@ -4166,7 +4217,7 @@ body {
|
||||
border: none !important;
|
||||
cursor: pointer !important;
|
||||
transition: all 0.2s ease !important;
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 4px 15px rgba(59, 130, 246, 0.3),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1) !important;
|
||||
text-decoration: none !important;
|
||||
@@ -4176,7 +4227,7 @@ body {
|
||||
.update-download-btn:hover:not(:disabled) {
|
||||
background: linear-gradient(135deg, #2563eb, #7c3aed) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 8px 25px rgba(59, 130, 246, 0.4),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.2) !important;
|
||||
}
|
||||
@@ -4189,7 +4240,7 @@ body {
|
||||
background: linear-gradient(135deg, #6b7280, #9ca3af) !important;
|
||||
cursor: not-allowed !important;
|
||||
transform: none !important;
|
||||
box-shadow:
|
||||
box-shadow:
|
||||
0 2px 8px rgba(107, 114, 128, 0.3),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05) !important;
|
||||
}
|
||||
@@ -4208,8 +4259,15 @@ body {
|
||||
}
|
||||
|
||||
@keyframes updatePulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.02); }
|
||||
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4330,6 +4388,7 @@ body {
|
||||
opacity: 0;
|
||||
transform: translateY(-20px) scale(0.95);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
@@ -4743,29 +4802,29 @@ body {
|
||||
width: 95vw;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
|
||||
.uuid-modal-body {
|
||||
padding: 1rem;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
.uuid-current-display,
|
||||
.uuid-custom-form {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
.uuid-list-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.uuid-list-item {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.uuid-item-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -4773,16 +4832,27 @@ body {
|
||||
|
||||
/* Chat Badges et Animations */
|
||||
@keyframes rainbow {
|
||||
0% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
0% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes contributorGlow {
|
||||
0%, 100% {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
background-position: 0% 50%;
|
||||
filter: brightness(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
background-position: 100% 50%;
|
||||
filter: brightness(1.2);
|
||||
@@ -4929,12 +4999,12 @@ body {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.color-type-option input[type="radio"]:checked + .radio-custom {
|
||||
.color-type-option input[type="radio"]:checked+.radio-custom {
|
||||
border-color: #9333ea;
|
||||
background: rgba(147, 51, 234, 0.2);
|
||||
}
|
||||
|
||||
.color-type-option input[type="radio"]:checked + .radio-custom::after {
|
||||
.color-type-option input[type="radio"]:checked+.radio-custom::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@@ -5090,6 +5160,7 @@ body {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 16px rgba(147, 51, 234, 0.4);
|
||||
}
|
||||
|
||||
.color-picker-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -5114,3 +5185,238 @@ body {
|
||||
border-color: #fff;
|
||||
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* Profile Selector Styles */
|
||||
.profile-selector {
|
||||
position: relative;
|
||||
pointer-events: auto;
|
||||
margin-right: auto;
|
||||
margin-left: 1rem;
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
.profile-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
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;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-family: 'Space Grotesk', sans-serif;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
z-index: 100000 !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
.profile-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: rgba(147, 51, 234, 0.4);
|
||||
}
|
||||
|
||||
.profile-btn i {
|
||||
color: #9333ea;
|
||||
}
|
||||
|
||||
.profile-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
margin-top: 0.5rem;
|
||||
width: 200px;
|
||||
background: rgba(20, 20, 20, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
padding: 0.5rem;
|
||||
display: none;
|
||||
/* Toggled by JS */
|
||||
flex-direction: column;
|
||||
z-index: 2000;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
|
||||
animation: fadeIn 0.1s ease;
|
||||
}
|
||||
|
||||
.profile-dropdown.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.profile-item {
|
||||
padding: 0.6rem 0.8rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: #ccc;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.profile-item:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.profile-item.active {
|
||||
background: rgba(147, 51, 234, 0.2);
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.profile-item.active::before {
|
||||
content: '•';
|
||||
color: #9333ea;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.profile-divider {
|
||||
height: 1px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.profile-action {
|
||||
padding: 0.6rem 0.8rem;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: #9333ea;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.profile-action:hover {
|
||||
background: rgba(147, 51, 234, 0.1);
|
||||
}
|
||||
|
||||
/* Profile Modal Styles */
|
||||
.profile-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
animation: fadeIn 0.2s ease;
|
||||
}
|
||||
|
||||
.profile-modal-content {
|
||||
background: #111;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 16px;
|
||||
width: 500px;
|
||||
max-width: 90%;
|
||||
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.profile-modal-header {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.profile-modal-title {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.profile-modal-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.profile-manager-list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.profile-manager-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.8rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.profile-manager-item:hover {
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.profile-manager-item.active {
|
||||
border-color: #9333ea;
|
||||
background: rgba(147, 51, 234, 0.1);
|
||||
}
|
||||
|
||||
.profile-delete-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #ef4444;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
padding: 0.4rem;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.profile-delete-btn:hover {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
}
|
||||
|
||||
.profile-create-section {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.profile-input {
|
||||
flex: 1;
|
||||
padding: 0.8rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.profile-create-btn {
|
||||
padding: 0 1.2rem;
|
||||
background: #9333ea;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.profile-create-btn:hover {
|
||||
background: #7c3aed;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user