Added complete sound effects application with multiple versions (V1, V2), configuration files, webhook functionality, and associated assets. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
617 lines
No EOL
23 KiB
PHP
617 lines
No EOL
23 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>ElevenLabs Sound Effect Generator</title>
|
|
<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=Montserrat:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
<!-- Microsoft Authentication Library -->
|
|
<script src="https://alcdn.msauth.net/browser/2.15.0/js/msal-browser.min.js" crossorigin="anonymous"></script>
|
|
<style>
|
|
:root {
|
|
--primary-btn-color: #f3ae3e;
|
|
--primary-btn-hover-color: #d4973b;
|
|
--bg-color: #f5f5f5;
|
|
--container-bg: white;
|
|
--text-color: #333;
|
|
--text-muted: #555;
|
|
--border-color: #ddd;
|
|
--input-bg: white;
|
|
--success-bg: #d4edda;
|
|
--success-border: #c3e6cb;
|
|
--success-text: #155724;
|
|
--error-bg: #f8d7da;
|
|
--error-border: #f5c6cb;
|
|
--error-text: #721c24;
|
|
--help-text-color: #666;
|
|
}
|
|
body {
|
|
font-family: 'Montserrat', sans-serif;
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
background-color: var(--bg-color);
|
|
color: var(--text-color);
|
|
transition: background-color 0.3s ease, color 0.3s ease;
|
|
}
|
|
.container {
|
|
background: var(--container-bg);
|
|
padding: 30px;
|
|
border-radius: 10px;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
transition: background-color 0.3s ease;
|
|
}
|
|
h1 {
|
|
color: var(--text-color);
|
|
text-align: center;
|
|
margin-bottom: 30px;
|
|
}
|
|
.form-group {
|
|
margin-bottom: 20px;
|
|
}
|
|
label {
|
|
display: block;
|
|
margin-bottom: 5px;
|
|
font-weight: bold;
|
|
color: var(--text-muted);
|
|
}
|
|
input, textarea, select {
|
|
width: 100%;
|
|
padding: 12px;
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 5px;
|
|
font-size: 16px;
|
|
font-family: 'Montserrat', sans-serif;
|
|
font-weight: 300;
|
|
background-color: var(--input-bg);
|
|
color: var(--text-color);
|
|
box-sizing: border-box;
|
|
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
|
|
}
|
|
textarea {
|
|
height: 100px;
|
|
resize: vertical;
|
|
}
|
|
button {
|
|
background-color: var(--primary-btn-color);
|
|
color: white;
|
|
padding: 12px 30px;
|
|
border: none;
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
width: 100%;
|
|
transition: background-color 0.3s ease;
|
|
}
|
|
button:hover {
|
|
background-color: var(--primary-btn-hover-color);
|
|
}
|
|
button:disabled {
|
|
background-color: #6c757d;
|
|
cursor: not-allowed;
|
|
}
|
|
.help-text {
|
|
font-size: 12px;
|
|
color: var(--help-text-color);
|
|
margin-top: 5px;
|
|
}
|
|
.result {
|
|
margin-top: 30px;
|
|
padding: 20px;
|
|
border-radius: 5px;
|
|
}
|
|
.success {
|
|
background-color: var(--success-bg);
|
|
border: 1px solid var(--success-border);
|
|
color: var(--success-text);
|
|
}
|
|
.error {
|
|
background-color: var(--error-bg);
|
|
border: 1px solid var(--error-border);
|
|
color: var(--error-text);
|
|
}
|
|
.loading {
|
|
text-align: center;
|
|
margin-top: 20px;
|
|
}
|
|
.audio-player {
|
|
margin-top: 20px;
|
|
text-align: center;
|
|
}
|
|
audio {
|
|
width: 100%;
|
|
max-width: 400px;
|
|
}
|
|
.slider-container {
|
|
position: relative;
|
|
margin: 10px 0;
|
|
}
|
|
.slider {
|
|
width: 100%;
|
|
height: 8px;
|
|
border-radius: 5px;
|
|
background: var(--border-color);
|
|
outline: none;
|
|
opacity: 0.7;
|
|
transition: opacity 0.2s, background-color 0.3s ease;
|
|
-webkit-appearance: none;
|
|
}
|
|
.slider:hover {
|
|
opacity: 1;
|
|
}
|
|
.slider::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 50%;
|
|
background: var(--primary-btn-color);
|
|
cursor: pointer;
|
|
}
|
|
.slider::-moz-range-thumb {
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 50%;
|
|
background: var(--primary-btn-color);
|
|
cursor: pointer;
|
|
border: none;
|
|
}
|
|
.slider-value {
|
|
display: inline-block;
|
|
margin-left: 10px;
|
|
font-weight: bold;
|
|
color: var(--primary-btn-color);
|
|
min-width: 60px;
|
|
}
|
|
.slider-label {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
/* Dark Mode Toggle Button */
|
|
.dark-mode-toggle {
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
z-index: 1000;
|
|
width: 48px;
|
|
height: 48px;
|
|
min-width: 48px;
|
|
min-height: 48px;
|
|
border-radius: 24px;
|
|
background-color: var(--primary-btn-color);
|
|
border: none;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
|
color: #000;
|
|
transition: background-color 0.3s ease;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.dark-mode-toggle:hover {
|
|
background-color: var(--primary-btn-hover-color);
|
|
}
|
|
|
|
/* Dark Mode Styles */
|
|
.dark-mode {
|
|
--bg-color: #1e1e1e;
|
|
--container-bg: #2a2a2a;
|
|
--text-color: #f5f5f5;
|
|
--text-muted: #cccccc;
|
|
--border-color: #444;
|
|
--input-bg: #333;
|
|
--success-bg: #1a4a1a;
|
|
--success-border: #2d5a2d;
|
|
--success-text: #90ee90;
|
|
--error-bg: #4a1a1a;
|
|
--error-border: #5a2d2d;
|
|
--error-text: #ffb3b3;
|
|
--help-text-color: #999;
|
|
}
|
|
|
|
.dark-mode .dark-mode-toggle {
|
|
color: #fff;
|
|
}
|
|
|
|
/* Authentication Styles */
|
|
#login-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
min-height: 80vh;
|
|
text-align: center;
|
|
width: 100%;
|
|
max-width: 600px;
|
|
margin: 0 auto;
|
|
background: var(--container-bg);
|
|
border-radius: 10px;
|
|
padding: 40px 20px;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
#login-container h1 {
|
|
font-size: 2rem;
|
|
margin-bottom: 20px;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
#login-container p {
|
|
font-size: 1.1rem;
|
|
margin-bottom: 30px;
|
|
color: var(--text-muted);
|
|
max-width: 80%;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
#login-button {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 15px 40px;
|
|
font-size: 1.1rem;
|
|
background-color: var(--primary-btn-color);
|
|
color: white;
|
|
border: none;
|
|
border-radius: 30px;
|
|
cursor: pointer;
|
|
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
|
font-weight: 600;
|
|
transition: background-color 0.3s ease;
|
|
font-family: 'Montserrat', sans-serif;
|
|
}
|
|
|
|
#login-button:hover {
|
|
background-color: var(--primary-btn-hover-color);
|
|
}
|
|
|
|
#logout-button {
|
|
display: none;
|
|
position: absolute;
|
|
top: 20px;
|
|
right: 80px;
|
|
padding: 8px 16px;
|
|
font-size: 0.9rem;
|
|
background-color: var(--container-bg);
|
|
color: var(--text-muted);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 5px;
|
|
cursor: pointer;
|
|
font-family: 'Montserrat', sans-serif;
|
|
transition: background-color 0.3s ease;
|
|
}
|
|
|
|
#logout-button:hover {
|
|
background-color: var(--border-color);
|
|
}
|
|
|
|
/* Hide protected content by default */
|
|
#protected-content {
|
|
display: none;
|
|
}
|
|
|
|
.dark-mode #logout-button {
|
|
background-color: var(--container-bg);
|
|
color: var(--text-muted);
|
|
border-color: var(--border-color);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Login Screen -->
|
|
<div id="login-container">
|
|
<h1>🎵 Sound Effect Generator</h1>
|
|
<p>Please log in to access the sound effect generator and create amazing audio for your projects.</p>
|
|
<button id="login-button" onclick="signIn()">
|
|
Log In with Oliver SSO
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Logout Button -->
|
|
<button id="logout-button" onclick="signOut()" title="Log Out">
|
|
Log Out
|
|
</button>
|
|
|
|
<!-- Dark Mode Toggle Button -->
|
|
<button id="darkModeToggle" class="dark-mode-toggle" title="Toggle Dark Mode">
|
|
<span id="lightModeIcon">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
|
|
<path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
|
|
</svg>
|
|
</span>
|
|
<span id="darkModeIcon" style="display: none;">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" viewBox="0 0 16 16">
|
|
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"/>
|
|
</svg>
|
|
</span>
|
|
</button>
|
|
|
|
<!-- Protected Content -->
|
|
<div id="protected-content">
|
|
<div class="container">
|
|
<h1>🎵 Sound Effect Generator</h1>
|
|
|
|
<?php
|
|
// Load configuration
|
|
$config = require_once 'config.php';
|
|
|
|
// Clean up old files on page load
|
|
cleanOldFiles($config['generated_files_dir'], $config['max_file_age_hours']);
|
|
|
|
$result = '';
|
|
$error = '';
|
|
$audioFile = '';
|
|
|
|
function cleanOldFiles($directory, $maxAgeHours) {
|
|
if (!is_dir($directory)) {
|
|
return;
|
|
}
|
|
|
|
$files = glob($directory . '*.mp3');
|
|
$cutoffTime = time() - ($maxAgeHours * 3600);
|
|
|
|
foreach ($files as $file) {
|
|
if (is_file($file) && filemtime($file) < $cutoffTime) {
|
|
unlink($file);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$text = $_POST['text'] ?? '';
|
|
$duration = $_POST['duration_seconds'] ?? null;
|
|
$promptInfluence = $_POST['prompt_influence'] ?? 0.3;
|
|
$apiKey = $config['elevenlabs_api_key'];
|
|
|
|
if (empty($text)) {
|
|
$error = 'Please enter a description for your sound effect.';
|
|
} elseif (empty($apiKey) || $apiKey === 'your-api-key-here') {
|
|
$error = 'Please configure your ElevenLabs API key in config.php';
|
|
} else {
|
|
$audioFile = generateSoundEffect($text, $duration, $promptInfluence, $apiKey, $error);
|
|
if ($audioFile) {
|
|
$result = 'Sound effect generated successfully!';
|
|
}
|
|
}
|
|
}
|
|
|
|
function generateSoundEffect($text, $duration, $promptInfluence, $apiKey, &$error) {
|
|
$url = 'https://api.elevenlabs.io/v1/sound-generation';
|
|
|
|
$data = [
|
|
'text' => $text,
|
|
'prompt_influence' => (float)$promptInfluence
|
|
];
|
|
|
|
if (!empty($duration) && is_numeric($duration)) {
|
|
$data['duration_seconds'] = (float)$duration;
|
|
}
|
|
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, $url);
|
|
curl_setopt($ch, CURLOPT_POST, true);
|
|
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
|
|
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
|
'xi-api-key: ' . $apiKey,
|
|
'Content-Type: application/json'
|
|
]);
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 60);
|
|
|
|
$response = curl_exec($ch);
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
|
|
if (curl_error($ch)) {
|
|
$error = 'Connection error: ' . curl_error($ch);
|
|
curl_close($ch);
|
|
return false;
|
|
}
|
|
|
|
curl_close($ch);
|
|
|
|
if ($httpCode === 200) {
|
|
$filename = 'sound_effect_' . date('Y-m-d_H-i-s') . '.mp3';
|
|
$filepath = __DIR__ . '/generated/' . $filename;
|
|
|
|
if (!is_dir(__DIR__ . '/generated')) {
|
|
mkdir(__DIR__ . '/generated', 0755, true);
|
|
}
|
|
|
|
if (file_put_contents($filepath, $response)) {
|
|
return 'generated/' . $filename;
|
|
} else {
|
|
$error = 'Failed to save the generated sound file.';
|
|
return false;
|
|
}
|
|
} else {
|
|
$errorResponse = json_decode($response, true);
|
|
$error = 'API Error (' . $httpCode . '): ' . ($errorResponse['detail'] ?? 'Unknown error occurred');
|
|
return false;
|
|
}
|
|
}
|
|
?>
|
|
|
|
<form method="POST" id="soundForm">
|
|
|
|
<div class="form-group">
|
|
<label for="text">Sound Effect Description:</label>
|
|
<textarea id="text" name="text" placeholder="e.g., Spacious braam suitable for high-impact movie trailer moments" required><?php echo htmlspecialchars($_POST['text'] ?? ''); ?></textarea>
|
|
<div class="help-text">Describe the sound effect you want to generate</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="slider-label">
|
|
<label for="duration_seconds">Duration (seconds):</label>
|
|
<span class="slider-value" id="duration_value">Auto</span>
|
|
</div>
|
|
<div class="slider-container">
|
|
<input type="range" id="duration_seconds" name="duration_seconds" class="slider" min="0" max="22" step="0.1" value="<?php echo htmlspecialchars($_POST['duration_seconds'] ?? '0'); ?>">
|
|
</div>
|
|
<div class="help-text">0 = Auto duration, 0.5 to 22 seconds for manual control</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="slider-label">
|
|
<label for="prompt_influence">Prompt Influence:</label>
|
|
<span class="slider-value" id="influence_value">0.3</span>
|
|
</div>
|
|
<div class="slider-container">
|
|
<input type="range" id="prompt_influence" name="prompt_influence" class="slider" min="0" max="1" step="0.1" value="<?php echo htmlspecialchars($_POST['prompt_influence'] ?? '0.3'); ?>">
|
|
</div>
|
|
<div class="help-text">0-1: Higher values = more prompt-aligned, lower values = more variable</div>
|
|
</div>
|
|
|
|
<button type="submit" id="generateBtn">Generate Sound Effect</button>
|
|
</form>
|
|
|
|
<?php if ($error): ?>
|
|
<div class="result error">
|
|
<strong>Error:</strong> <?php echo htmlspecialchars($error); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($result && $audioFile): ?>
|
|
<div class="result success">
|
|
<strong><?php echo htmlspecialchars($result); ?></strong>
|
|
<div class="audio-player">
|
|
<audio controls>
|
|
<source src="<?php echo htmlspecialchars($audioFile); ?>" type="audio/mpeg">
|
|
Your browser does not support the audio element.
|
|
</audio>
|
|
<br><br>
|
|
<a href="<?php echo htmlspecialchars($audioFile); ?>" download>Download Sound Effect</a>
|
|
</div>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div> <!-- End protected content -->
|
|
|
|
<script>
|
|
// Microsoft Authentication Configuration
|
|
const msalConfig = {
|
|
auth: {
|
|
clientId: "9079054c-9620-4757-a256-23413042f1ef",
|
|
authority: "https://login.microsoftonline.com/e519c2e6-bc6d-4fdf-8d9c-923c2f002385",
|
|
redirectUri: window.location.origin + window.location.pathname
|
|
},
|
|
cache: {
|
|
cacheLocation: "sessionStorage",
|
|
storeAuthStateInCookie: true,
|
|
}
|
|
};
|
|
|
|
const loginRequest = {
|
|
scopes: ["user.read"]
|
|
};
|
|
|
|
const myMSALObj = new msal.PublicClientApplication(msalConfig);
|
|
|
|
// Authentication Functions
|
|
function checkAuthenticationStatus() {
|
|
const accessToken = sessionStorage.getItem('accessToken');
|
|
if (accessToken) {
|
|
showProtectedContent();
|
|
} else {
|
|
document.getElementById('login-container').style.display = 'flex';
|
|
document.getElementById('protected-content').style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function signIn() {
|
|
myMSALObj.loginPopup(loginRequest)
|
|
.then(loginResponse => {
|
|
console.log("User logged in:", loginResponse.account.username);
|
|
sessionStorage.setItem('accessToken', loginResponse.accessToken);
|
|
showProtectedContent();
|
|
}).catch(error => {
|
|
console.error("Error during login:", error);
|
|
document.getElementById('login-container').style.display = 'flex';
|
|
document.getElementById('protected-content').style.display = 'none';
|
|
});
|
|
}
|
|
|
|
function signOut() {
|
|
sessionStorage.removeItem('accessToken');
|
|
console.log("User logged out.");
|
|
document.getElementById('login-container').style.display = 'flex';
|
|
document.getElementById('protected-content').style.display = 'none';
|
|
document.getElementById('logout-button').style.display = 'none';
|
|
}
|
|
|
|
function showProtectedContent() {
|
|
const accessToken = sessionStorage.getItem('accessToken');
|
|
if (accessToken) {
|
|
document.getElementById('login-container').style.display = 'none';
|
|
document.getElementById('protected-content').style.display = 'block';
|
|
document.getElementById('logout-button').style.display = 'block';
|
|
}
|
|
}
|
|
|
|
// Initialize authentication on page load
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
checkAuthenticationStatus();
|
|
|
|
// Dark mode toggle functionality
|
|
// Check for saved dark mode preference
|
|
const darkModeEnabled = localStorage.getItem('darkMode') === 'enabled';
|
|
|
|
if (darkModeEnabled) {
|
|
document.body.classList.add('dark-mode');
|
|
document.getElementById('lightModeIcon').style.display = 'none';
|
|
document.getElementById('darkModeIcon').style.display = 'block';
|
|
}
|
|
|
|
// Toggle dark mode when button is clicked
|
|
document.getElementById('darkModeToggle').addEventListener('click', function() {
|
|
document.body.classList.toggle('dark-mode');
|
|
|
|
// Save preference and toggle icons
|
|
if (document.body.classList.contains('dark-mode')) {
|
|
localStorage.setItem('darkMode', 'enabled');
|
|
document.getElementById('lightModeIcon').style.display = 'none';
|
|
document.getElementById('darkModeIcon').style.display = 'block';
|
|
} else {
|
|
localStorage.setItem('darkMode', 'disabled');
|
|
document.getElementById('lightModeIcon').style.display = 'block';
|
|
document.getElementById('darkModeIcon').style.display = 'none';
|
|
}
|
|
});
|
|
});
|
|
|
|
// Update slider values in real-time
|
|
const durationSlider = document.getElementById('duration_seconds');
|
|
const durationValue = document.getElementById('duration_value');
|
|
const influenceSlider = document.getElementById('prompt_influence');
|
|
const influenceValue = document.getElementById('influence_value');
|
|
|
|
function updateDurationValue() {
|
|
const value = parseFloat(durationSlider.value);
|
|
durationValue.textContent = value === 0 ? 'Auto' : value + 's';
|
|
}
|
|
|
|
function updateInfluenceValue() {
|
|
influenceValue.textContent = influenceSlider.value;
|
|
}
|
|
|
|
durationSlider.addEventListener('input', updateDurationValue);
|
|
influenceSlider.addEventListener('input', updateInfluenceValue);
|
|
|
|
// Initialize values on page load
|
|
updateDurationValue();
|
|
updateInfluenceValue();
|
|
|
|
document.getElementById('soundForm').addEventListener('submit', function() {
|
|
const btn = document.getElementById('generateBtn');
|
|
btn.disabled = true;
|
|
btn.textContent = 'Generating... Please wait';
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|