mirror of
https://github.com/zarfadev/MobaXterm-Keygen.git
synced 2026-08-28 21:00:49 +00:00
7ff6b67362
Refined UI using a completely reconstructed Glassmorphism aesthetic and modern UX updates. Updated support for up to 26.3.
870 lines
38 KiB
HTML
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> |