Files
Miguel Villegas 7ff6b67362 v3.0
Refined UI using a completely reconstructed Glassmorphism aesthetic and modern UX updates. Updated support for up to 26.3.
2026-04-20 15:21:23 -05:00

870 lines
38 KiB
HTML

<!DOCTYPE html>
<html lang="en" class="antialiased">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MobaXterm Keygen</title>
<meta name="description" content="Professional Key Generator for MobaXterm - Designed by Zarfala" />
<link rel="icon" href="https://i.imgur.com/FtcF9ZP.png" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link href="./css/pure-min.css?=2" rel="stylesheet" />
<link href="./css/style.css" rel="stylesheet" />
<link href="./css/fire.css" rel="stylesheet" />
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
fontFamily: {
sans: ['Outfit', 'sans-serif'],
},
colors: {
primary: {
50: '#eef2ff',
100: '#e0e7ff',
200: '#c3dafe',
300: '#a3bffa',
400: '#7f9cf5',
500: '#667eea',
600: '#5a67d8',
700: '#4c51bf',
800: '#434190',
900: '#3c366b',
}
},
animation: {
'blob': 'blob 7s infinite',
'fade-in-up': 'fadeInUp 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards',
'float': 'float 6s ease-in-out infinite',
},
keyframes: {
blob: {
'0%': { transform: 'translate(0px, 0px) scale(1)' },
'33%': { transform: 'translate(30px, -50px) scale(1.1)' },
'66%': { transform: 'translate(-20px, 20px) scale(0.9)' },
'100%': { transform: 'translate(0px, 0px) scale(1)' },
},
fadeInUp: {
'0%': { opacity: '0', transform: 'translateY(40px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
float: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-10px)' },
}
}
}
}
}
</script>
<style>
body {
font-family: 'Outfit', sans-serif;
}
/* Custom modern scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(156, 163, 175, 0.5);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(107, 114, 128, 0.8);
}
/* Preloader */
#preloader {
position: fixed;
inset: 0;
background: #f8fafc;
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
transition: background-color 0.3s, opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1), visibility 0.6s;
}
.dark #preloader {
background: #000000;
}
#preloader.fade-out {
opacity: 0;
visibility: hidden;
}
.spinner {
width: 60px;
height: 60px;
position: relative;
}
.spinner::before,
.spinner::after {
content: '';
position: absolute;
inset: 0;
border-radius: 50%;
border: 4px solid transparent;
}
.spinner::before {
border-top-color: #667eea;
border-right-color: #667eea;
animation: spin 1.5s linear infinite;
}
.spinner::after {
border-bottom-color: #ed64a6;
border-left-color: #ed64a6;
animation: spin 1s linear infinite reverse;
}
@keyframes spin {
100% {
transform: rotate(360deg);
}
}
.content-hidden {
visibility: hidden;
opacity: 0;
transition: opacity 0.8s ease-in-out, visibility 0.8s;
}
.content-visible {
visibility: visible;
opacity: 1;
}
/* Glass panel effect */
.glass-panel {
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(40px);
-webkit-backdrop-filter: blur(40px);
border: 1px solid rgba(255, 255, 255, 0.6);
box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.1), 0 0 0 1px rgba(255, 255, 255, 0.4) inset;
}
.dark .glass-panel {
background: rgba(15, 23, 42, 0.75);
backdrop-filter: blur(40px);
-webkit-backdrop-filter: blur(40px);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.05) inset;
}
/* Modern Forms */
.input-modern {
background: rgba(255, 255, 255, 0.7);
border: 2px solid rgba(209, 213, 219, 0.6);
color: #1f2937;
transition: all 0.3s ease;
}
.input-modern:focus {
background: #ffffff;
border-color: #667eea;
box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.2);
}
.dark .input-modern {
background: rgba(15, 23, 42, 0.7);
border: 2px solid rgba(71, 85, 105, 0.6);
color: #f8fafc;
}
.dark .input-modern:focus {
background: rgba(15, 23, 42, 0.95);
border-color: #7f9cf5;
box-shadow: 0 0 0 4px rgba(127, 156, 245, 0.2);
}
/* Button Gradient */
.btn-gradient {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-size: 200% auto;
color: white;
transition: 0.5s ease;
box-shadow: 0 10px 20px -5px rgba(102, 126, 234, 0.4);
}
.btn-gradient:hover {
background-position: right center;
box-shadow: 0 15px 25px -5px rgba(102, 126, 234, 0.5);
transform: translateY(-2px);
}
.btn-gradient:active {
transform: translateY(1px);
box-shadow: 0 5px 10px -5px rgba(102, 126, 234, 0.4);
}
/* Toggle Active Tab */
.tab-active {
position: relative;
color: #667eea;
font-weight: 600;
}
.dark .tab-active {
color: #a3bffa;
}
.tab-active::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(90deg, #667eea, #ed64a6);
border-radius: 3px 3px 0 0;
animation: tabSlide 0.3s ease-out forwards;
}
@keyframes tabSlide {
from {
width: 0;
opacity: 0;
left: 50%;
transform: translateX(-50%);
}
to {
width: 100%;
opacity: 1;
left: 0;
transform: translateX(0);
}
}
select option {
background-color: white;
color: black;
}
.dark select option {
background-color: #1e293b;
color: white;
}
.animate-gradient-text {
background-size: 300%;
animation: gradientText 6s ease-in-out infinite;
}
@keyframes gradientText {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
</style>
<!-- Meta tags for better SEO and sharing -->
<meta property="og:title" content="MobaXterm Keygen" />
<meta property="og:type" content="website" />
<meta property="og:description" content="Professional Key Generator for MobaXterm" />
<meta property="og:site_name" content="MobaXterm Keygen" />
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="MobaXterm Keygen">
<meta name="twitter:description" content="Professional Key Generator for MobaXterm created by Zarfala">
<script>
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
</script>
</head>
<body class="transition-colors duration-500 bg-slate-50 dark:bg-black font-sans text-slate-800 dark:text-slate-200">
<!-- Preloader -->
<div id="preloader">
<div class="spinner"></div>
</div>
<main id="main-content" class="content-hidden antialiased">
<div
class="relative flex min-h-screen flex-col items-center justify-center overflow-hidden px-4 sm:px-6 py-12 lg:py-24">
<!-- Abstract Animated Background Elements -->
<div class="absolute inset-0 overflow-hidden pointer-events-none">
<!-- Enhanced original VIP Jumbo effect -->
<div class="jumbo"></div>
<!-- Modern animated blobs -->
<div
class="absolute top-0 -left-4 w-72 h-72 bg-purple-400 rounded-full mix-blend-multiply filter blur-2xl opacity-20 dark:opacity-20 animate-blob">
</div>
<div
class="absolute top-0 -right-4 w-72 h-72 bg-blue-400 rounded-full mix-blend-multiply filter blur-2xl opacity-20 dark:opacity-20 animate-blob animate-delay-2000">
</div>
<div
class="absolute -bottom-8 left-20 w-72 h-72 bg-pink-400 rounded-full mix-blend-multiply filter blur-2xl opacity-20 dark:opacity-20 animate-blob animate-delay-4000">
</div>
</div>
<div class="z-10 w-full max-w-2xl mx-auto animate-fade-in-up">
<div id="app" class="glass-panel rounded-3xl p-8 md:p-12 transition-all duration-300 relative overflow-hidden">
<!-- Shimmer effect inside card -->
<div
class="absolute inset-0 bg-gradient-to-tr from-white/5 to-white/0 dark:from-white/5 dark:to-transparent pointer-events-none rounded-3xl">
</div>
<!-- Header Section -->
<header class="mb-10 text-center relative z-10">
<!-- Top Badge -->
<div
class="inline-flex items-center justify-center space-x-2 px-3 py-1 mb-6 rounded-full bg-primary-100 dark:bg-primary-900/40 border border-primary-200 dark:border-primary-800 text-xs font-semibold text-primary-700 dark:text-primary-300">
<span class="flex h-2 w-2 relative">
<span
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-primary-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-2 w-2 bg-primary-500"></span>
</span>
<span>v20.X, 26.2 & 26.3 Compatible</span>
</div>
<!-- Main Title -->
<h1 class="flex flex-wrap items-center justify-center gap-x-3 text-4xl sm:text-5xl md:text-6xl font-extrabold tracking-tight text-slate-900 dark:text-white mb-2">
<span>MobaXterm</span>
<span class="text-transparent bg-clip-text bg-gradient-to-r from-blue-600 via-pink-500 to-indigo-600 animate-gradient-text inline-block py-2 pr-2">
Keygen
</span>
</h1>
<p class="text-slate-500 dark:text-slate-400 mt-2 text-sm sm:text-base max-w-sm mx-auto">
Fast, and secure license generator.
</p>
<!-- Theme Toggle -->
<div class="absolute top-0 right-0 sm:-top-4 sm:-right-4 p-2 sm:p-0">
<button onclick="toggleTheme()"
class="p-3 rounded-full bg-white/60 dark:bg-slate-800/60 border border-slate-200 dark:border-slate-700 shadow-sm text-slate-600 dark:text-slate-300 hover:bg-white dark:hover:bg-slate-800 transition-all focus:outline-none focus:ring-2 focus:ring-primary-500"
aria-label="Toggle Theme">
<svg id="theme-icon-dark" class="hidden w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
<svg id="theme-icon-light" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
</button>
</div>
</header>
<!-- Tab Navigation -->
<div class="mb-10 relative z-10 flex justify-center w-full">
<nav
class="flex space-x-2 bg-slate-200/60 dark:bg-slate-800/60 p-1.5 rounded-2xl w-full max-w-sm mx-auto shadow-inner border border-slate-300/30 dark:border-slate-700/50"
aria-label="Tabs">
<button @click="activeTab = 'keyGenerator'"
:class="[activeTab === 'keyGenerator' ? 'bg-white dark:bg-slate-700 text-primary-600 dark:text-primary-400 shadow-sm' : 'text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200', 'w-1/2 py-2.5 text-sm sm:text-base font-bold transition-all duration-300 rounded-xl tracking-wide']">
Key Generator
</button>
<button @click="activeTab = 'customMerger'"
:class="[activeTab === 'customMerger' ? 'bg-white dark:bg-slate-700 text-primary-600 dark:text-primary-400 shadow-sm' : 'text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200', 'w-1/2 py-2.5 text-sm sm:text-base font-bold transition-all duration-300 rounded-xl tracking-wide']">
Merger
</button>
</nav>
</div>
<!-- Key Generator Tab -->
<div v-show="activeTab === 'keyGenerator'" class="relative z-10 animate-fade-in-up"
style="animation-duration: 0.4s;">
<form @submit.prevent="validateAndGenerate">
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<!-- Edition selection -->
<div class="col-span-1 md:col-span-2 group">
<label for="licenseType"
class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-1.5 ml-1">Edition
Type</label>
<div class="relative">
<div
class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none text-slate-400 group-focus-within:text-primary-500 transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z">
</path>
</svg>
</div>
<select name="licenseType" id="licenseType" v-model="licenseType"
class="input-modern block w-full pl-11 pr-10 py-3.5 rounded-xl outline-none appearance-none font-medium cursor-pointer">
<option value="" disabled>Select Edition</option>
<option v-for="(val, key) in LICENSE_TYPES" :value="val">{{key}}</option>
</select>
<div
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-4 text-slate-400 group-focus-within:text-primary-500 transition-colors">
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</div>
</div>
<p v-if="errors.licenseType" class="mt-1.5 ml-1 text-red-500 text-xs font-medium">{{
errors.licenseType }}</p>
</div>
<!-- Username -->
<div class="group">
<label for="userName"
class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-1.5 ml-1">Username</label>
<div class="relative">
<div
class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none text-slate-400 group-focus-within:text-primary-500 transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path>
</svg>
</div>
<input type="text" id="userName" v-model="userName" @input="validateUserName"
placeholder="e.g. JohnDoe"
class="input-modern block w-full pl-11 pr-4 py-3.5 rounded-xl outline-none" />
</div>
<p v-if="errors.userName" class="mt-1.5 ml-1 text-red-500 text-xs font-medium">{{ errors.userName }}
</p>
</div>
<!-- Version -->
<div class="group">
<label for="versionName"
class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-1.5 ml-1">Version</label>
<div class="relative">
<div
class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none text-slate-400 group-focus-within:text-primary-500 transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"></path>
</svg>
</div>
<input type="text" id="versionName" v-model="versionName" @input="validateVersion"
placeholder="26.3" class="input-modern block w-full pl-11 pr-4 py-3.5 rounded-xl outline-none" />
</div>
<p v-if="errors.versionName" class="mt-1.5 ml-1 text-red-500 text-xs font-medium">{{
errors.versionName }}</p>
</div>
<!-- Number of Users -->
<div class="col-span-1 md:col-span-2 group">
<label for="userNum"
class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-1.5 ml-1">Number of
Users</label>
<div class="relative">
<div
class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none text-slate-400 group-focus-within:text-primary-500 transition-colors">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z">
</path>
</svg>
</div>
<input type="number" id="userNum" v-model="userNum" @input="validateUserNum" placeholder="1" min="1"
class="input-modern block w-full pl-11 pr-4 py-3.5 rounded-xl outline-none" />
</div>
<p v-if="errors.userNum" class="mt-1.5 ml-1 text-red-500 text-xs font-medium">{{ errors.userNum }}</p>
</div>
<!-- Submit Button -->
<div class="col-span-1 md:col-span-2 pt-2">
<button type="submit"
class="btn-gradient w-full py-4 px-6 rounded-xl text-lg font-bold uppercase tracking-wider flex items-center justify-center gap-2 focus:outline-none focus:ring-4 focus:ring-primary-500/50">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"></path>
</svg>
Generate License
</button>
</div>
</div>
</form>
</div>
<!-- Custom Settings Merger Tab -->
<div v-show="activeTab === 'customMerger'" class="relative z-10 space-y-8 animate-fade-in-up"
style="animation-duration: 0.4s;">
<!-- Info Box -->
<div
class="bg-primary-50/80 dark:bg-primary-900/10 border border-primary-100 dark:border-primary-800/30 p-5 rounded-2xl">
<div class="flex items-start gap-4">
<div
class="bg-primary-100 dark:bg-primary-800 p-2 rounded-lg text-primary-600 dark:text-primary-300 shrink-0">
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
</div>
<div>
<h3 class="text-base font-bold text-primary-900 dark:text-primary-100 mb-1">How it works</h3>
<ul
class="text-sm text-primary-700 dark:text-primary-300 space-y-1.5 ml-4 list-decimal marker:text-primary-500 font-medium">
<li>Generate a license using the <span class="font-bold">Key Generator</span>.</li>
<li>Run <code
class="bg-white/60 dark:bg-black/40 px-1.5 py-0.5 rounded font-mono text-xs">.\MobaXterm.exe -customizer</code>
</li>
<li>Export settings to <code
class="bg-white/60 dark:bg-black/40 px-1.5 py-0.5 rounded font-mono text-xs">MobaXterm customization.custom</code>
</li>
<li>Upload both files below to merge.</li>
</ul>
</div>
</div>
</div>
<div class="space-y-6">
<!-- License File Upload -->
<div>
<label class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-2 ml-1">
License (.mxtpro)
</label>
<label for="licenseFileInput"
class="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-slate-300 dark:border-slate-600 rounded-2xl cursor-pointer bg-white/30 dark:bg-slate-800/30 hover:bg-white/60 dark:hover:bg-slate-800/60 transition-colors group">
<div class="flex flex-col items-center justify-center pt-5 pb-6">
<svg class="w-8 h-8 mb-3 text-slate-400 group-hover:text-primary-500 transition-colors" fill="none"
stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12">
</path>
</svg>
<p class="mb-1 text-sm text-slate-500 dark:text-slate-400 font-semibold truncate px-4 max-w-full">
<span class="text-primary-600 dark:text-primary-400">{{ licenseFile ? licenseFile.name : 'Click to upload' }}</span></p>
<p v-if="!licenseFile" class="text-xs text-slate-400 dark:text-slate-500">MAX size 10MB</p>
</div>
<input type="file" id="licenseFileInput" @change="handleLicenseFileUpload" accept=".mxtpro"
class="hidden" />
</label>
<p v-if="errors.licenseFile" class="mt-1.5 ml-1 text-red-500 text-xs font-medium">{{ errors.licenseFile
}}</p>
</div>
<!-- Custom File Upload -->
<div>
<label class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-2 ml-1">
Custom Settings (.custom)
</label>
<label for="customSettingsFileInput"
class="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-slate-300 dark:border-slate-600 rounded-2xl cursor-pointer bg-white/30 dark:bg-slate-800/30 hover:bg-white/60 dark:hover:bg-slate-800/60 transition-colors group">
<div class="flex flex-col items-center justify-center pt-5 pb-6">
<svg class="w-8 h-8 mb-3 text-slate-400 group-hover:text-primary-500 transition-colors" fill="none"
stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z">
</path>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path>
</svg>
<p class="mb-1 text-sm text-slate-500 dark:text-slate-400 font-semibold truncate px-4 max-w-full">
<span class="text-primary-600 dark:text-primary-400">{{ customSettingsFile ? customSettingsFile.name : 'Click to upload' }}</span></p>
<p v-if="!customSettingsFile" class="text-xs text-slate-400 dark:text-slate-500">MAX size 10MB</p>
</div>
<input type="file" id="customSettingsFileInput" @change="handleCustomSettingsFileUpload"
accept=".custom" class="hidden" />
</label>
<p v-if="errors.customSettingsFile" class="mt-1.5 ml-1 text-red-500 text-xs font-medium">{{
errors.customSettingsFile }}</p>
</div>
</div>
<div class="pt-2">
<button @click="mergeFiles" :disabled="!licenseFile || !customSettingsFile"
class="btn-gradient w-full py-4 px-6 rounded-xl text-lg font-bold uppercase tracking-wider flex items-center justify-center gap-2 focus:outline-none focus:ring-4 focus:ring-primary-500/50 disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:translate-y-0 disabled:hover:shadow-none">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"></path>
</svg>
Merge Files
</button>
</div>
</div>
<!-- Footer -->
<footer class="mt-14 relative z-10">
<div class="flex flex-col items-center space-y-3">
<div
class="flex items-center space-x-2 bg-white/40 dark:bg-slate-800/40 px-4 py-1.5 rounded-full border border-slate-200 dark:border-slate-700 backdrop-blur-sm">
<span class="text-xs font-medium text-slate-600 dark:text-slate-400">Handcrafted with</span>
<span class="animate-pulse text-red-500"></span>
<span class="text-xs font-medium text-slate-600 dark:text-slate-400">by</span>
<span
class="text-xs font-extrabold bg-clip-text text-transparent bg-gradient-to-r from-purple-500 to-pink-500">
Zarfala </span>
</div>
<div class="text-[11px] font-medium text-slate-500 tracking-wide">
v3.0 © 2026 All rights reserved
</div>
</div>
</footer>
</div>
</div>
</div>
<!-- Toast notification -->
<div id="toast"
class="fixed bottom-6 right-6 px-6 py-4 rounded-xl shadow-2xl transform translate-y-24 opacity-0 transition-all duration-400 cubic-bezier(0.16, 1, 0.3, 1) z-50 hidden flex items-center gap-3 backdrop-blur-md font-semibold text-sm">
<svg id="toast-icon-success" class="w-5 h-5 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<svg id="toast-icon-error" class="w-5 h-5 hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
<span id="toast-text"></span>
</div>
</main>
<!-- Scripts -->
<script src="./js/vue.min-2.6.12.js"></script>
<script src="./js/FileSaver.js"></script>
<script src="./js/jszip.js"></script>
<script src="./js/fire.js"></script>
<script src="./js/alert.js"></script>
<script>
// Preloader functionality
window.addEventListener('load', function () {
setTimeout(function () {
const preloader = document.getElementById('preloader');
const mainContent = document.getElementById('main-content');
preloader.classList.add('fade-out');
mainContent.classList.remove('content-hidden');
mainContent.classList.add('content-visible');
setTimeout(function () {
preloader.style.display = 'none';
}, 600);
}, 400); // slight artificial delay for premium feel
});
// Theme toggle functionality
function updateThemeIcons() {
const isDark = document.documentElement.classList.contains('dark');
document.getElementById('theme-icon-dark').classList.toggle('hidden', isDark);
document.getElementById('theme-icon-light').classList.toggle('hidden', !isDark);
}
function toggleTheme() {
if (document.documentElement.classList.contains('dark')) {
document.documentElement.classList.remove('dark');
localStorage.theme = 'light';
} else {
document.documentElement.classList.add('dark');
localStorage.theme = 'dark';
}
updateThemeIcons();
}
// Theme is initialized in the <head> to prevent loader flash
updateThemeIcons();
// Toast notification function
function showToast(message, type = 'success') {
const toast = document.getElementById('toast');
const text = document.getElementById('toast-text');
const iconSuccess = document.getElementById('toast-icon-success');
const iconError = document.getElementById('toast-icon-error');
text.textContent = message;
toast.classList.remove('hidden');
if (type === 'success') {
toast.className = toast.className.replace(/bg-\w+-50\/\d+/g, '');
toast.className = toast.className.replace(/text-\w+-600/g, '');
toast.classList.add('bg-green-50/90', 'dark:bg-green-900/90', 'text-green-700', 'dark:text-green-100', 'border', 'border-green-200', 'dark:border-green-800');
toast.classList.remove('bg-red-50/90', 'dark:bg-red-900/90', 'text-red-700', 'dark:text-red-100', 'border-red-200', 'dark:border-red-800');
iconSuccess.classList.remove('hidden');
iconError.classList.add('hidden');
} else {
toast.className = toast.className.replace(/bg-\w+-50\/\d+/g, '');
toast.className = toast.className.replace(/text-\w+-600/g, '');
toast.classList.add('bg-red-50/90', 'dark:bg-red-900/90', 'text-red-700', 'dark:text-red-100', 'border', 'border-red-200', 'dark:border-red-800');
toast.classList.remove('bg-green-50/90', 'dark:bg-green-900/90', 'text-green-700', 'dark:text-green-100', 'border-green-200', 'dark:border-green-800');
iconError.classList.remove('hidden');
iconSuccess.classList.add('hidden');
}
// Trigger animation
requestAnimationFrame(() => {
toast.classList.remove('translate-y-24', 'opacity-0');
});
// Hide after delay
setTimeout(() => {
toast.classList.add('translate-y-24', 'opacity-0');
setTimeout(() => {
toast.classList.add('hidden');
}, 400);
}, 4000);
}
</script>
<script type="module">
import { LicenseType, generateLicense } from './js/mobaXtermGenerater.js'
import { mergeZipFiles } from './js/mergeZipFiles.js'
new Vue({
el: '#app',
data() {
return {
LICENSE_TYPES: LicenseType,
licenseType: '',
userName: '',
versionName: '26.3',
userNum: '',
activeTab: 'keyGenerator',
licenseFile: null,
customSettingsFile: null,
errors: {
licenseType: '',
userName: '',
versionName: '',
userNum: '',
licenseFile: '',
customSettingsFile: ''
}
}
},
methods: {
validateLicenseType() {
if (!this.licenseType) {
this.errors.licenseType = 'Please select an edition';
return false;
}
this.errors.licenseType = '';
return true;
},
validateUserName() {
if (!this.userName) {
this.errors.userName = 'Username is required';
return false;
}
const regex = /^[a-zA-Z]+$/;
if (!regex.test(this.userName)) {
this.errors.userName = 'Only letters allowed (no spaces, numbers or special chars)';
return false;
}
this.errors.userName = '';
return true;
},
validateVersion() {
if (!this.versionName) {
this.errors.versionName = 'Version is required';
return false;
}
const regex = /^[1-9][0-9]*\.?\d{0,1}$/;
if (!regex.test(this.versionName)) {
this.errors.versionName = 'Enter a valid version format (e.g., 26.3)';
return false;
}
// Check if version is supported
const majorVersion = parseInt(this.versionName.split('.')[0]);
if (majorVersion !== 20 && majorVersion !== 25 && majorVersion !== 26) {
this.errors.versionName = 'Currently only versions 20.X, 25.X and 26.X are supported';
return false;
}
this.errors.versionName = '';
return true;
},
validateUserNum() {
if (!this.userNum) {
this.errors.userNum = 'Number of users is required';
return false;
}
const regex = /^[1-9]+\d*$/;
if (!regex.test(this.userNum)) {
this.errors.userNum = 'Enter a valid number greater than 0';
return false;
}
this.errors.userNum = '';
return true;
},
validateAndGenerate() {
const isLicenseTypeValid = this.validateLicenseType();
const isUserNameValid = this.validateUserName();
const isVersionValid = this.validateVersion();
const isUserNumValid = this.validateUserNum();
if (isLicenseTypeValid && isUserNameValid && isVersionValid && isUserNumValid) {
try {
let versionNameArr = this.versionName.split('.');
const majorVersion = parseInt(versionNameArr[0]);
const minorVersion = versionNameArr.length === 2 ? (parseInt(versionNameArr[1]) || 0) : 0;
let licenseStr = generateLicense(this.licenseType, this.userName, this.userNum, majorVersion, minorVersion);
this.generateLicenseFile(licenseStr);
showToast('License generated successfully!', 'success');
} catch (error) {
console.error('Error generating license:', error);
showToast('Error generating license. Please try again.', 'error');
}
}
},
generateLicenseFile(licenseStr) {
let zip = new JSZip();
zip.file("Pro.key", licenseStr);
zip.generateAsync({ type: "blob" }).then((content) => {
saveAs(content, "Custom.mxtpro");
}).catch((error) => {
console.error('Error creating zip file:', error);
showToast('Error creating license file. Please try again.', 'error');
});
},
handleLicenseFileUpload(event) {
const file = event.target.files[0];
if (file) {
if (file.name.toLowerCase().endsWith('.mxtpro')) {
this.licenseFile = file;
this.errors.licenseFile = '';
} else {
this.licenseFile = null;
this.errors.licenseFile = 'Please select a valid .mxtpro file';
}
}
},
handleCustomSettingsFileUpload(event) {
const file = event.target.files[0];
if (file) {
if (file.name.toLowerCase().endsWith('.custom')) {
this.customSettingsFile = file;
this.errors.customSettingsFile = '';
} else {
this.customSettingsFile = null;
this.errors.customSettingsFile = 'Please select a valid .custom file';
}
}
},
mergeFiles() {
if (!this.licenseFile || !this.customSettingsFile) {
showToast('Please upload both required files', 'error');
return;
}
mergeZipFiles(this.licenseFile, this.customSettingsFile)
.then((mergedContent) => {
saveAs(mergedContent, "Custom.mxtpro");
showToast('Files merged successfully!', 'success');
})
.catch((error) => {
console.error('Error merging files:', error);
showToast('Error merging files. Please try again.', 'error');
});
}
}
});
</script>
</body>
</html>