/* Unggoy Loader - React Frontend */
const { useState, useEffect, useRef, useCallback } = React;
const { createPortal } = ReactDOM;

const APP_BUILD = 'web';
const API_BASE = '/api';

// ===== Auth Token Storage =====
function getToken() {
  return localStorage.getItem('unggoy_token');
}

function setToken(token) {
  localStorage.setItem('unggoy_token', token);
}

function clearToken() {
  localStorage.removeItem('unggoy_token');
  localStorage.removeItem('unggoy_username');
  localStorage.removeItem('unggoy_role');
  localStorage.removeItem('unggoy_tier');
}

function getStoredUsername() {
  return localStorage.getItem('unggoy_username') || '';
}

function setStoredUsername(username) {
  localStorage.setItem('unggoy_username', username);
}

function getStoredRole() {
  return localStorage.getItem('unggoy_role') || 'viewer';
}

function setStoredRole(role) {
  localStorage.setItem('unggoy_role', role);
}

function getStoredTier() {
  return parseInt(localStorage.getItem('unggoy_tier') || '1', 10);
}

function setStoredTier(tier) {
  localStorage.setItem('unggoy_tier', String(tier));
}

// ===== Tier helpers =====
var TIER_BADGE_NAMES = {1: 'BASIC', 2: 'STANDARD', 3: 'PREMIUM', 4: 'VIP'};

function TierBadge({ tier }) {
  var name = TIER_BADGE_NAMES[tier] || 'BASIC';
  return (
    <span className={'tier-badge tier-badge-' + tier}>
      {name}
    </span>
  );
}

function TierRequiredTag({ tier }) {
  var name = TIER_BADGE_NAMES[tier] || 'BASIC';
  return (
    <span className={'tier-required-tag tier-badge-' + tier}>
      {'\uD83D\uDD12'} {name}
    </span>
  );
}

// ===== API Helpers =====
async function apiFetch(path, options) {
  const token = getToken();
  const headers = (options && options.headers) ? { ...options.headers } : {};

  if (token) {
    headers['Authorization'] = 'Bearer ' + token;
  }

  const mergedOptions = { ...options, headers };

  const resp = await fetch(API_BASE + path, mergedOptions);

  // On 401, clear token and trigger re-render
  if (resp.status === 401) {
    clearToken();
    window.dispatchEvent(new Event('unggoy-auth-expired'));
    const err = await resp.json().catch(() => ({ error: 'Not authenticated' }));
    throw new Error(err.detail?.error || err.error || 'Not authenticated');
  }

  if (!resp.ok) {
    const err = await resp.json().catch(() => ({ error: resp.statusText }));
    throw new Error(err.detail?.error || err.error || resp.statusText);
  }
  return resp.json();
}

// Authenticated raw fetch (for FormData uploads)
async function authFetch(url, options) {
  const token = getToken();
  const headers = (options && options.headers) ? { ...options.headers } : {};

  if (token) {
    headers['Authorization'] = 'Bearer ' + token;
  }

  const mergedOptions = { ...options, headers };
  const resp = await fetch(url, mergedOptions);

  if (resp.status === 401) {
    clearToken();
    window.dispatchEvent(new Event('unggoy-auth-expired'));
    throw new Error('Not authenticated');
  }

  return resp;
}

async function downloadWithAuth(url, filename) {
  try {
    var resp = await authFetch(url, {});
    if (!resp.ok) throw new Error('Download failed: ' + resp.status);
    var blob = await resp.blob();
    var blobUrl = URL.createObjectURL(blob);
    var a = document.createElement('a');
    a.href = blobUrl;
    a.download = filename || 'download';
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    URL.revokeObjectURL(blobUrl);
  } catch (err) {
    console.error('Download error:', err);
  }
}

function pad(n) {
  return n < 10 ? '0' + n : '' + n;
}

function timestamp() {
  const now = new Date();
  return '[' + pad(now.getHours()) + ':' + pad(now.getMinutes()) + ':' + pad(now.getSeconds()) + ']';
}

function getFileSubtitle(filename) {
  const ext = filename.split('.').pop().toLowerCase();
  switch (ext) {
    case 'exe': return 'Main Executable';
    case 'bmp': return 'Steganography Payload Container';
    case 'dll': return 'Dynamic Library';
    case 'bin': return 'Binary Data';
    default: return 'Supplementary File';
  }
}

function formatDate(ts) {
  if (!ts) return '--';
  const d = new Date(ts * 1000);
  return d.toLocaleDateString() + ' ' + d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}

// ===== Role helpers =====
function canBuild(role) {
  return role === 'admin' || role === 'operator' || role === 'user';
}

function isAdmin(role) {
  return role === 'admin';
}

function isTierRestricted(role) {
  return role === 'user';
}

// ===== Login Screen =====

function LoginScreen({ onLogin }) {
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);
  const usernameRef = useRef(null);

  useEffect(() => {
    if (usernameRef.current) usernameRef.current.focus();
  }, []);

  async function handleSubmit(e) {
    e.preventDefault();
    if (!username.trim() || !password) {
      setError('Username and password are required.');
      return;
    }

    setLoading(true);
    setError('');

    try {
      const resp = await fetch(API_BASE + '/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username: username.trim(), password }),
      });

      if (!resp.ok) {
        const err = await resp.json().catch(() => ({}));
        throw new Error(err.detail?.error || 'Login failed');
      }

      const data = await resp.json();
      setToken(data.token);
      setStoredUsername(data.username);
      setStoredRole(data.role || 'viewer');
      setStoredTier(data.tier || 1);
      onLogin(data.username, data.role || 'viewer', data.tier || 1, data.must_change_password || false);
    } catch (err) {
      setError(err.message || 'Login failed');
    } finally {
      setLoading(false);
    }
  }

  return (
    <div className="login-backdrop">
      <div className="login-card">
        <div className="login-header">
          <h1 className="login-logo">UNGGOY LOADER</h1>
          <span className="login-subtitle">AUTHENTICATION REQUIRED</span>
        </div>

        <form className="login-form" onSubmit={handleSubmit}>
          {error && (
            <div className="login-error">
              <span className="login-error-icon">{'\u26A0'}</span>
              {error}
            </div>
          )}

          <div className="login-field">
            <label className="login-label" htmlFor="login-user">USERNAME</label>
            <input
              id="login-user"
              ref={usernameRef}
              className="login-input"
              type="text"
              value={username}
              onChange={(e) => setUsername(e.target.value)}
              autoComplete="username"
              spellCheck={false}
              disabled={loading}
            />
          </div>

          <div className="login-field">
            <label className="login-label" htmlFor="login-pass">PASSWORD</label>
            <input
              id="login-pass"
              className="login-input"
              type="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              autoComplete="current-password"
              disabled={loading}
            />
          </div>

          <button
            className={'btn btn-primary login-btn' + (loading ? ' login-btn--loading' : '')}
            type="submit"
            disabled={loading}
          >
            {loading ? 'AUTHENTICATING...' : 'LOGIN'}
          </button>
        </form>

        <div className="login-footer">
          <span className="login-footer-text">SECURE ACCESS ONLY</span>
        </div>
      </div>
    </div>
  );
}

// ===== Reusable Components =====

function SectionHeader({ title, style }) {
  return (
    <div className="section-header" style={style}>
      <span className="section-icon">{'\u25B8'}</span>
      <span className="section-title">{title}</span>
      <div className="section-line"></div>
    </div>
  );
}

function Divider({ style }) {
  return <div className="divider" style={style}></div>;
}

function RoleBadge({ role }) {
  return (
    <span className={'role-badge role-badge-' + role}>
      {role.toUpperCase()}
    </span>
  );
}

function FileInput({ label, accept, fileState, onFileChange, displayName, disabled, children }) {
  const inputRef = useRef(null);

  const handleChange = (e) => {
    if (e.target.files && e.target.files.length > 0) {
      onFileChange(e.target.files[0]);
    } else {
      onFileChange(null);
    }
  };

  return (
    <div className="file-row">
      <label className="file-label">{label}</label>
      <div className="file-input-group">
        <span className={'file-name' + (displayName && displayName !== 'No file selected' ? ' has-file' : '')}>
          {displayName || 'No file selected'}
        </span>
        <input
          type="file"
          ref={inputRef}
          accept={accept}
          className="file-hidden"
          onChange={handleChange}
          disabled={disabled}
        />
        {children}
        <button
          className="btn btn-file"
          onClick={() => inputRef.current && inputRef.current.click()}
          disabled={disabled}
        >
          &hellip;
        </button>
      </div>
    </div>
  );
}

// ===== File Manager =====
function formatFileSize(bytes) {
  if (bytes === 0) return '0 B';
  var units = ['B', 'KB', 'MB', 'GB'];
  var i = Math.floor(Math.log(bytes) / Math.log(1024));
  if (i >= units.length) i = units.length - 1;
  return (bytes / Math.pow(1024, i)).toFixed(i === 0 ? 0 : 1) + ' ' + units[i];
}

var FILE_CATEGORIES = [
  { key: 'payload', label: 'PAYLOAD' },
  { key: 'cover', label: 'COVER' },
  { key: 'icon', label: 'ICON' },
  { key: 'host_pe', label: 'HOST PE' },
  { key: 'signature', label: 'SIGNATURE' },
];

function FileManager({ serverDefaults, onDefaultsChanged, disabled }) {
  var [collapsed, setCollapsed] = useState(true);
  var [activeCategory, setActiveCategory] = useState('payload');
  var [files, setFiles] = useState([]);
  var [defaults, setDefaults] = useState(serverDefaults || {});
  var [loading, setLoading] = useState(false);
  var [uploading, setUploading] = useState(false);
  var [dragover, setDragover] = useState(false);
  var [deleteConfirm, setDeleteConfirm] = useState(null);
  var fileInputRef = useRef(null);

  // Sync defaults from parent
  useEffect(function() {
    setDefaults(serverDefaults || {});
  }, [serverDefaults]);

  // Load files when category changes
  useEffect(function() {
    loadFiles();
  }, [activeCategory]);

  function loadFiles() {
    setLoading(true);
    apiFetch('/files?category=' + encodeURIComponent(activeCategory))
      .then(function(data) {
        setFiles(data.files || []);
      })
      .catch(function(err) {
        console.error('Failed to load files:', err);
        setFiles([]);
      })
      .finally(function() {
        setLoading(false);
      });
  }

  function loadDefaults() {
    apiFetch('/files/defaults/me')
      .then(function(data) {
        var d = data.defaults || {};
        setDefaults(d);
        if (onDefaultsChanged) onDefaultsChanged(d);
      })
      .catch(function() {});
  }

  function handleUpload(fileObj) {
    if (!fileObj || disabled) return;
    setUploading(true);
    var fd = new FormData();
    fd.append('file', fileObj);
    fd.append('category', activeCategory);
    authFetch(API_BASE + '/files/upload', { method: 'POST', body: fd })
      .then(function(resp) {
        if (!resp.ok) throw new Error('Upload failed');
        return resp.json();
      })
      .then(function() {
        loadFiles();
      })
      .catch(function(err) {
        console.error('Upload error:', err);
      })
      .finally(function() {
        setUploading(false);
      });
  }

  function handleFileInputChange(e) {
    if (e.target.files && e.target.files.length > 0) {
      handleUpload(e.target.files[0]);
      e.target.value = '';
    }
  }

  function handleDrop(e) {
    e.preventDefault();
    e.stopPropagation();
    setDragover(false);
    if (disabled) return;
    if (e.dataTransfer.files && e.dataTransfer.files.length > 0) {
      handleUpload(e.dataTransfer.files[0]);
    }
  }

  function handleDragOver(e) {
    e.preventDefault();
    e.stopPropagation();
    if (!disabled) setDragover(true);
  }

  function handleDragLeave(e) {
    e.preventDefault();
    e.stopPropagation();
    setDragover(false);
  }

  function handleSetDefault(fileId) {
    if (disabled) return;
    var body = {};
    body[activeCategory] = fileId;
    apiFetch('/files/defaults', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    }).then(function(data) {
      var d = data.defaults || {};
      setDefaults(d);
      if (onDefaultsChanged) onDefaultsChanged(d);
    }).catch(function(err) {
      console.error('Set default error:', err);
    });
  }

  function handleClearDefault() {
    if (disabled) return;
    var body = {};
    body[activeCategory] = null;
    apiFetch('/files/defaults', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    }).then(function(data) {
      var d = data.defaults || {};
      setDefaults(d);
      if (onDefaultsChanged) onDefaultsChanged(d);
    }).catch(function(err) {
      console.error('Clear default error:', err);
    });
  }

  function handleDelete(fileId) {
    if (disabled) return;
    apiFetch('/files/' + fileId, { method: 'DELETE' })
      .then(function() {
        setDeleteConfirm(null);
        loadFiles();
        loadDefaults();
      })
      .catch(function(err) {
        console.error('Delete error:', err);
      });
  }

  var currentDefault = defaults[activeCategory];

  return (
    <section className="card file-manager">
      <div className="section-header" style={{cursor: 'pointer'}} onClick={function() { setCollapsed(!collapsed); }}>
        <span className="section-icon" style={{display: 'inline-block', transition: 'transform 0.2s', transform: collapsed ? 'rotate(0deg)' : 'rotate(90deg)'}}>
          {'\u25B8'}
        </span>
        <span className="section-title">FILE MANAGER</span>
        <div className="section-line"></div>
      </div>

      {!collapsed && <React.Fragment>
      <div className="category-tabs">
        {FILE_CATEGORIES.map(function(cat) {
          var isActive = cat.key === activeCategory;
          var hasDefault = !!defaults[cat.key];
          return (
            <button
              key={cat.key}
              className={'category-tab' + (isActive ? ' active' : '') + (hasDefault ? ' has-default' : '')}
              onClick={function() { setActiveCategory(cat.key); }}
            >
              {cat.label}
              {hasDefault && <span className="category-tab-dot"></span>}
            </button>
          );
        })}
      </div>

      {/* Upload zone */}
      <div
        className={'upload-zone' + (dragover ? ' dragover' : '') + (uploading ? ' uploading' : '')}
        onDrop={handleDrop}
        onDragOver={handleDragOver}
        onDragLeave={handleDragLeave}
        onClick={function() { if (!disabled && fileInputRef.current) fileInputRef.current.click(); }}
      >
        <input
          type="file"
          ref={fileInputRef}
          className="file-hidden"
          onChange={handleFileInputChange}
          disabled={disabled}
        />
        {uploading ? (
          <div className="upload-zone-text">
            <span className="upload-zone-icon">{'\u23F3'}</span>
            <span>UPLOADING...</span>
          </div>
        ) : (
          <div className="upload-zone-text">
            <span className="upload-zone-icon">{'\u2B06'}</span>
            <span>DROP FILE HERE OR CLICK TO BROWSE</span>
            <span className="upload-zone-hint">Max 50 MB</span>
          </div>
        )}
      </div>

      {/* File list */}
      {loading ? (
        <div className="file-table-empty">Loading...</div>
      ) : files.length === 0 ? (
        <div className="file-table-empty">No files uploaded for this category</div>
      ) : (
        <div className="file-table-wrap">
          <table className="file-table">
            <thead>
              <tr>
                <th>NAME</th>
                <th>SIZE</th>
                <th>UPLOADED</th>
                <th>DEFAULT</th>
                <th>ACTIONS</th>
              </tr>
            </thead>
            <tbody>
              {files.map(function(f) {
                var isDefault = currentDefault === f.id;
                return (
                  <tr key={f.id} className={isDefault ? 'file-row-default' : ''}>
                    <td className="file-table-name" title={f.original_name}>{f.original_name}</td>
                    <td>{formatFileSize(f.file_size)}</td>
                    <td>{formatDate(f.uploaded_at)}</td>
                    <td>
                      {isDefault ? (
                        <span className="file-default-star" title="Default file">{'\u2605'}</span>
                      ) : (
                        <button
                          className="btn btn-secondary btn-xs"
                          onClick={function() { handleSetDefault(f.id); }}
                          disabled={disabled}
                          title="Set as default"
                        >SET</button>
                      )}
                    </td>
                    <td className="file-table-actions">
                      {isDefault && (
                        <button
                          className="btn btn-secondary btn-xs"
                          onClick={handleClearDefault}
                          disabled={disabled}
                          title="Clear default"
                        >UNSET</button>
                      )}
                      {deleteConfirm === f.id ? (
                        <span className="file-delete-confirm">
                          <button className="btn-danger btn-xs" onClick={function() { handleDelete(f.id); }}>CONFIRM</button>
                          <button className="btn btn-secondary btn-xs" onClick={function() { setDeleteConfirm(null); }}>CANCEL</button>
                        </span>
                      ) : (
                        <button
                          className="btn-danger-outline btn-xs"
                          onClick={function() { setDeleteConfirm(f.id); }}
                          disabled={disabled}
                          title="Delete file"
                        >DELETE</button>
                      )}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}
      </React.Fragment>}
    </section>
  );
}

function LogEntry({ entry }) {
  return (
    <div className={'log-entry log-' + (entry.tag || 'dim')}>
      <span className="log-time">{entry.time}</span>
      <span className="log-arrow">{'\u00BB'}</span>
      <span className="log-msg">{entry.message}</span>
    </div>
  );
}

function DownloadCard({ file }) {
  const isPrimary = file.type === 'primary';
  return (
    <div className={'download-card ' + (isPrimary ? 'download-card-primary' : 'download-card-secondary')}>
      <div className="download-card__header">
        <div className="download-card__icon">
          {isPrimary ? '\u25C6' : '\u25C7'}
        </div>
        <div className="download-card__info">
          <span className="download-card__filename">{file.filename}</span>
          <span className="download-card__subtitle">{file.subtitle}</span>
        </div>
      </div>
      <button className="download-card__btn" onClick={function() { downloadWithAuth(file.downloadUrl, file.filename); }}>
        {'\u2B07'}  DOWNLOAD
      </button>
    </div>
  );
}

function DownloadArea({ files, onDownloadAll }) {
  if (!files || files.length === 0) return null;

  return (
    <div className="download-area">
      <SectionHeader title="BUILD OUTPUT" />
      <div className="download-grid">
        {files.map((f) => (
          <DownloadCard key={f.filename} file={f} />
        ))}
      </div>
      {files.length > 1 && (
        <button className="download-all-btn" onClick={onDownloadAll}>
          {'\u2B07'}  DOWNLOAD ALL
        </button>
      )}
    </div>
  );
}

function InfoTooltip({ visible, moduleKey, anchorRect, modules, infoTexts, stealthRatings, onClose }) {
  const tooltipRef = useRef(null);

  useEffect(() => {
    if (!visible) return;

    const handleClick = (e) => {
      if (tooltipRef.current && !tooltipRef.current.contains(e.target) && !e.target.classList.contains('btn-info')) {
        onClose();
      }
    };
    const handleKey = (e) => {
      if (e.key === 'Escape') onClose();
    };

    document.addEventListener('click', handleClick);
    document.addEventListener('keydown', handleKey);
    return () => {
      document.removeEventListener('click', handleClick);
      document.removeEventListener('keydown', handleKey);
    };
  }, [visible, onClose]);

  if (!visible || !moduleKey || !anchorRect) return null;

  const mod = modules[moduleKey] || {};
  const infoText = infoTexts[moduleKey] || 'No description available.';
  const rating = stealthRatings[moduleKey] || 0;

  let stealthStars = '';
  if (rating > 0) {
    for (let i = 0; i < 5; i++) {
      stealthStars += i < rating ? '\u2605' : '\u2606';
    }
  }

  // Position calculation
  const tooltipWidth = 360;
  let left = anchorRect.right + 10;
  if (left + tooltipWidth > window.innerWidth) {
    left = anchorRect.left - tooltipWidth - 10;
  }
  if (left < 8) left = 8;
  let top = anchorRect.top;

  // Clamp top so tooltip doesn't overflow bottom
  const maxTop = window.innerHeight - 200;
  if (top > maxTop) top = maxTop;

  return createPortal(
    <div
      ref={tooltipRef}
      className="info-tooltip"
      role="tooltip"
      aria-live="polite"
      style={{ left: left + 'px', top: top + 'px' }}
    >
      <div className="info-tooltip-title">{mod.name || moduleKey}</div>
      {stealthStars && (
        <div className="info-tooltip-stealth">(Stealth: {stealthStars})</div>
      )}
      <div className="info-tooltip-body">{infoText}</div>
    </div>,
    document.body
  );
}

// ===== Admin Panel Components =====

function ConfirmModal({ title, message, onConfirm, onCancel }) {
  return (
    <div className="modal-backdrop" onClick={function(e) { if (e.target === e.currentTarget) onCancel(); }}>
      <div className="modal-card">
        <h3 className="modal-title">{title}</h3>
        <p className="modal-message">{message}</p>
        <div className="modal-actions">
          <button className="btn btn-secondary btn-sm" onClick={onCancel}>CANCEL</button>
          <button className="btn btn-danger btn-sm" onClick={onConfirm}>CONFIRM</button>
        </div>
      </div>
    </div>
  );
}

function ResetPasswordModal({ username, onClose }) {
  const [newPw, setNewPw] = useState('');
  const [confirmPw, setConfirmPw] = useState('');
  const [error, setError] = useState('');
  const [success, setSuccess] = useState(false);
  const [loading, setLoading] = useState(false);

  async function handleSubmit(e) {
    e.preventDefault();
    setError('');
    if (newPw.length < 8) { setError('Password must be at least 8 characters'); return; }
    if (newPw !== confirmPw) { setError('Passwords do not match'); return; }
    setLoading(true);
    try {
      await apiFetch('/admin/users/' + encodeURIComponent(username) + '/reset-password', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ new_password: newPw }),
      });
      setSuccess(true);
      setTimeout(onClose, 1200);
    } catch (err) {
      setError(err.message || 'Failed to reset password');
    } finally {
      setLoading(false);
    }
  }

  return (
    <div className="modal-backdrop" onClick={function(e) { if (e.target === e.currentTarget) onClose(); }}>
      <div className="modal-card">
        <h3 className="modal-title">RESET PASSWORD</h3>
        <p className="modal-subtitle">User: <strong>{username}</strong></p>
        {success ? (
          <p style={{ color: 'var(--success)', textAlign: 'center', padding: '16px 0' }}>Password reset successfully!</p>
        ) : (
          <form className="modal-form" onSubmit={handleSubmit}>
            {error && <div className="login-error" style={{ marginBottom: '12px' }}>{error}</div>}
            <input
              className="login-input"
              type="password"
              placeholder="New password"
              value={newPw}
              onChange={function(e) { setNewPw(e.target.value); }}
              required
              autoFocus
              disabled={loading}
            />
            <input
              className="login-input"
              type="password"
              placeholder="Confirm new password"
              value={confirmPw}
              onChange={function(e) { setConfirmPw(e.target.value); }}
              required
              disabled={loading}
            />
            <div className="modal-actions">
              <button className="btn btn-secondary btn-sm" type="button" onClick={onClose}>CANCEL</button>
              <button className="btn btn-primary btn-sm" type="submit" disabled={loading}>
                {loading ? 'RESETTING...' : 'RESET PASSWORD'}
              </button>
            </div>
          </form>
        )}
      </div>
    </div>
  );
}

function AdminPanel({ currentUsername }) {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');

  // Create user form
  const [newUsername, setNewUsername] = useState('');
  const [newPassword, setNewPassword] = useState('');
  const [newRole, setNewRole] = useState('operator');
  const [newTier, setNewTier] = useState(1);
  const [createError, setCreateError] = useState('');
  const [createLoading, setCreateLoading] = useState(false);

  // Modals
  const [deleteTarget, setDeleteTarget] = useState(null);
  const [resetTarget, setResetTarget] = useState(null);

  async function loadUsers() {
    try {
      const data = await apiFetch('/admin/users');
      setUsers(data.users || []);
      setError('');
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }

  useEffect(() => { loadUsers(); }, []);

  async function handleCreateUser(e) {
    e.preventDefault();
    setCreateError('');
    if (!newUsername.trim() || !newPassword) {
      setCreateError('Username and password required');
      return;
    }
    if (newPassword.length < 8) {
      setCreateError('Password must be at least 8 characters');
      return;
    }
    setCreateLoading(true);
    try {
      await apiFetch('/auth/register', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username: newUsername.trim(), password: newPassword, role: newRole, tier: newTier }),
      });
      setNewUsername('');
      setNewPassword('');
      setNewRole('user');
      setNewTier(1);
      loadUsers();
    } catch (err) {
      setCreateError(err.message || 'Failed to create user');
    } finally {
      setCreateLoading(false);
    }
  }

  async function handleRoleChange(username, role) {
    try {
      await apiFetch('/admin/users/' + encodeURIComponent(username) + '/role', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ role: role }),
      });
      loadUsers();
    } catch (err) {
      setError(err.message);
    }
  }

  async function handleTierChange(username, tier) {
    try {
      await apiFetch('/admin/users/' + encodeURIComponent(username) + '/tier', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ tier: parseInt(tier, 10) }),
      });
      loadUsers();
    } catch (err) {
      setError(err.message);
    }
  }

  async function handleDeleteUser() {
    if (!deleteTarget) return;
    try {
      await apiFetch('/admin/users/' + encodeURIComponent(deleteTarget), {
        method: 'DELETE',
      });
      setDeleteTarget(null);
      loadUsers();
    } catch (err) {
      setError(err.message);
      setDeleteTarget(null);
    }
  }

  return (
    <section className="card admin-panel">
      <SectionHeader title="USER MANAGEMENT" />

      {/* Create User Form */}
      <div className="admin-form">
        <form className="admin-create-form" onSubmit={handleCreateUser}>
          <input
            className="admin-input"
            type="text"
            placeholder="Username"
            value={newUsername}
            onChange={function(e) { setNewUsername(e.target.value); }}
            spellCheck={false}
            disabled={createLoading}
          />
          <input
            className="admin-input"
            type="password"
            placeholder="Password"
            value={newPassword}
            onChange={function(e) { setNewPassword(e.target.value); }}
            disabled={createLoading}
          />
          <select
            className="admin-role-select"
            value={newRole}
            onChange={function(e) { setNewRole(e.target.value); }}
            disabled={createLoading}
          >
            <option value="admin">Admin</option>
            <option value="operator">Operator</option>
            <option value="user">User</option>
            <option value="viewer">Viewer</option>
          </select>
          <select
            className="admin-role-select"
            value={newTier}
            onChange={function(e) { setNewTier(parseInt(e.target.value, 10)); }}
            disabled={createLoading}
          >
            <option value={1}>BASIC</option>
            <option value={2}>STANDARD</option>
            <option value={3}>PREMIUM</option>
            <option value={4}>VIP</option>
          </select>
          <button className="btn btn-primary btn-sm" type="submit" disabled={createLoading}>
            {createLoading ? 'CREATING...' : 'CREATE USER'}
          </button>
        </form>
        {createError && <div className="admin-error">{createError}</div>}
      </div>

      <Divider />

      {/* Error display */}
      {error && <div className="admin-error" style={{ marginBottom: '12px' }}>{error}</div>}

      {/* User Table */}
      {loading ? (
        <div style={{ textAlign: 'center', color: 'var(--text-muted)', padding: '20px' }}>Loading users...</div>
      ) : (
        <div className="user-table-wrap">
          <table className="user-table">
            <thead>
              <tr>
                <th>USERNAME</th>
                <th>ROLE</th>
                <th>TIER</th>
                <th>CREATED</th>
                <th>ACTIONS</th>
              </tr>
            </thead>
            <tbody>
              {users.map(function(user) {
                const isSelf = user.username === currentUsername;
                return (
                  <tr key={user.username} className={isSelf ? 'user-row-self' : ''}>
                    <td className="user-table-username">
                      {user.username}
                      {isSelf && <span className="user-you-badge">YOU</span>}
                    </td>
                    <td>
                      {isSelf ? (
                        <RoleBadge role={user.role} />
                      ) : (
                        <select
                          className="admin-role-select-inline"
                          value={user.role}
                          onChange={function(e) { handleRoleChange(user.username, e.target.value); }}
                        >
                          <option value="admin">Admin</option>
                          <option value="operator">Operator</option>
                          <option value="user">User</option>
                          <option value="viewer">Viewer</option>
                        </select>
                      )}
                    </td>
                    <td>
                      {isSelf ? (
                        <TierBadge tier={user.tier || 1} />
                      ) : (
                        <select
                          className="admin-role-select-inline"
                          value={user.tier || 1}
                          onChange={function(e) { handleTierChange(user.username, e.target.value); }}
                        >
                          <option value={1}>BASIC</option>
                          <option value={2}>STANDARD</option>
                          <option value={3}>PREMIUM</option>
                          <option value={4}>VIP</option>
                        </select>
                      )}
                    </td>
                    <td className="user-table-date">{formatDate(user.created_at)}</td>
                    <td className="user-table-actions">
                      {!isSelf && (
                        <React.Fragment>
                          <button
                            className="btn btn-secondary btn-xs"
                            onClick={function() { setResetTarget(user.username); }}
                            title="Reset password"
                          >
                            RESET PW
                          </button>
                          <button
                            className="btn btn-danger-outline btn-xs"
                            onClick={function() { setDeleteTarget(user.username); }}
                            title="Delete user"
                          >
                            DELETE
                          </button>
                        </React.Fragment>
                      )}
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      )}

      {/* Delete Confirm Modal */}
      {deleteTarget && (
        <ConfirmModal
          title="DELETE USER"
          message={'Are you sure you want to delete user "' + deleteTarget + '"? This action cannot be undone.'}
          onConfirm={handleDeleteUser}
          onCancel={function() { setDeleteTarget(null); }}
        />
      )}

      {/* Reset Password Modal */}
      {resetTarget && (
        <ResetPasswordModal
          username={resetTarget}
          onClose={function() { setResetTarget(null); }}
        />
      )}
    </section>
  );
}

// ===== Main App =====

function App() {
  // --- Auth state ---
  const [authed, setAuthed] = useState(!!getToken());
  const [currentUsername, setCurrentUsername] = useState(getStoredUsername());
  const [currentRole, setCurrentRole] = useState(getStoredRole());
  const [currentTier, setCurrentTier] = useState(getStoredTier());
  const [mustChangePassword, setMustChangePassword] = useState(false);

  // Listen for auth expiration events (401 responses)
  useEffect(() => {
    function handleExpired() {
      setAuthed(false);
      setCurrentUsername('');
      setCurrentRole('viewer');
      setCurrentTier(1);
      setMustChangePassword(false);
    }
    window.addEventListener('unggoy-auth-expired', handleExpired);
    return () => window.removeEventListener('unggoy-auth-expired', handleExpired);
  }, []);

  // On mount, fetch fresh role and tier from /auth/me
  useEffect(() => {
    if (!authed) return;
    apiFetch('/auth/me').then(function(data) {
      setCurrentRole(data.role || 'viewer');
      setStoredRole(data.role || 'viewer');
      setCurrentTier(data.tier || 1);
      setStoredTier(data.tier || 1);
      if (data.must_change_password) setMustChangePassword(true);
    }).catch(function() {
      // ignore - token may be expired, handled by 401
    });
  }, [authed]);

  function handleLogin(username, role, tier, mustChange) {
    setCurrentUsername(username);
    setCurrentRole(role || 'viewer');
    setCurrentTier(tier || 1);
    setAuthed(true);
    if (mustChange) setMustChangePassword(true);
  }

  function handleLogout() {
    clearToken();
    setAuthed(false);
    setCurrentUsername('');
    setCurrentRole('viewer');
    setCurrentTier(1);
    setMustChangePassword(false);
  }

  if (!authed) {
    return <LoginScreen onLogin={handleLogin} />;
  }

  // Force password change before allowing access
  if (mustChangePassword) {
    return (
      <div className="login-backdrop">
        <div className="login-card" style={{maxWidth: '480px'}}>
          <div className="login-header">
            <h1 className="login-logo">UNGGOY LOADER</h1>
            <span className="login-subtitle">{'\u26A0'} PASSWORD CHANGE REQUIRED</span>
          </div>
          <div style={{padding: '0 24px 8px', color: '#ff9800', fontSize: '13px', textAlign: 'center'}}>
            You must change your default password before continuing.
          </div>
          <ChangePasswordModal forced={true} onClose={function() { setMustChangePassword(false); }} />
        </div>
      </div>
    );
  }

  return <MainApp username={currentUsername} role={currentRole} tier={currentTier} onLogout={handleLogout} />;
}

function ChangePasswordModal({ onClose, forced }) {
  const [oldPw, setOldPw] = useState('');
  const [newPw, setNewPw] = useState('');
  const [confirmPw, setConfirmPw] = useState('');
  const [error, setError] = useState('');
  const [success, setSuccess] = useState(false);

  async function handleSubmit(e) {
    e.preventDefault();
    setError('');
    if (newPw.length < 8) { setError('New password must be at least 8 characters'); return; }
    if (newPw !== confirmPw) { setError('Passwords do not match'); return; }
    try {
      var resp = await authFetch(API_BASE + '/auth/change-password', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ old_password: oldPw, new_password: newPw })
      });
      if (!resp.ok) {
        var data = await resp.json().catch(function() { return {}; });
        setError(data.detail?.error || data.detail || data.error || 'Failed to change password');
        return;
      }
      setSuccess(true);
      setTimeout(onClose, 1500);
    } catch (err) {
      setError('Network error');
    }
  }

  function handleBackdropClick(e) {
    if (!forced && e.target === e.currentTarget) onClose();
  }

  return (
    <div className={forced ? "login-form" : "modal-backdrop"} onClick={forced ? undefined : handleBackdropClick} style={forced ? {padding: '0 24px 24px'} : undefined}>
      <div className={forced ? undefined : "modal-card"} style={forced ? undefined : { maxWidth: '400px' }}>
        {!forced && <h3 className="modal-title">CHANGE PASSWORD</h3>}
        {success ? (
          <p style={{ color: 'var(--success)', textAlign: 'center', padding: '20px 0' }}>Password changed successfully!</p>
        ) : (
          <form className="modal-form" onSubmit={handleSubmit}>
            {error && <div className="login-error" style={{ marginBottom: '12px' }}>{error}</div>}
            <input className="login-input" type="password" placeholder="Current password" value={oldPw}
              onChange={function(e) { setOldPw(e.target.value); }} required autoFocus />
            <input className="login-input" type="password" placeholder="New password (min 8 characters)" value={newPw}
              onChange={function(e) { setNewPw(e.target.value); }} required />
            <input className="login-input" type="password" placeholder="Confirm new password" value={confirmPw}
              onChange={function(e) { setConfirmPw(e.target.value); }} required />
            <div className="modal-actions">
              {!forced && <button className="btn btn-secondary btn-sm" type="button" onClick={onClose}>CANCEL</button>}
              <button className="btn btn-primary btn-sm" type="submit" style={{ flex: 1 }}>CHANGE PASSWORD</button>
            </div>
          </form>
        )}
      </div>
    </div>
  );
}

function MainApp({ username, role, tier, onLogout }) {
  // --- Connection state ---
  const [status, setStatus] = useState('connecting');
  const [version, setVersion] = useState('v--');
  const [showChangePw, setShowChangePw] = useState(false);
  const [mingwAvailable, setMingwAvailable] = useState(false);

  // --- User menu ---
  const [showUserMenu, setShowUserMenu] = useState(false);
  const userMenuRef = useRef(null);

  // --- View routing ---
  const [currentView, setCurrentView] = useState('main');

  // --- Profile delete ---
  const [profileDeleteConfirm, setProfileDeleteConfirm] = useState(false);

  // --- Module definitions ---
  const [modules, setModules] = useState({});
  const [stealthRatings, setStealthRatings] = useState({});
  const [infoTexts, setInfoTexts] = useState({});
  const [exclusiveGroups, setExclusiveGroups] = useState({});
  const [checkboxGroups, setCheckboxGroups] = useState([]);
  const [paramsMap, setParamsMap] = useState({});
  const [techniqueTiers, setTechniqueTiers] = useState({});
  const [tierNames, setTierNames] = useState({});

  // --- User selections ---
  const [files, setFiles] = useState({
    payload: null, cover: null, icon: null, host_pe: null, sig_source: null
  });
  const [outputName, setOutputName] = useState('Unggoy.exe');
  const [overrideIcon, setOverrideIcon] = useState(false);
  const [debugMode, setDebugMode] = useState(true);
  const [dropdownSelections, setDropdownSelections] = useState({}); // groupName -> displayName
  const [checkboxStates, setCheckboxStates] = useState({}); // moduleKey -> boolean
  const [paramValues, setParamValues] = useState({}); // paramKey -> string value

  // --- Stealth ---
  const [stealthScore, setStealthScore] = useState(0);
  const [stealthStars, setStealthStars] = useState('');

  // --- Build ---
  const [buildStatus, setBuildStatus] = useState('idle');
  const [buildId, setBuildId] = useState(null);
  const [logs, setLogs] = useState([]);
  const [downloadFiles, setDownloadFiles] = useState([]);
  const buildWsRef = useRef(null);
  const consoleRef = useRef(null);

  // --- Profiles ---
  const [profiles, setProfiles] = useState([]);
  const [selectedProfile, setSelectedProfile] = useState('');

  // --- Server file defaults ---
  const [serverDefaults, setServerDefaults] = useState({});       // {category: file_id}
  const [serverDefaultInfo, setServerDefaultInfo] = useState({}); // {category: {id, original_name, ...}}

  // --- Tooltip ---
  const [tooltip, setTooltip] = useState({ visible: false, moduleKey: null, anchorRect: null });

  // --- Derived: which dropdown options map to which module keys ---
  const dropdownOptionsMap = useRef({}); // { groupName: { displayName: moduleKey } }

  // --- Role checks ---
  const userCanBuild = canBuild(role);
  const userIsAdmin = isAdmin(role);
  const isViewer = role === 'viewer';
  const userTier = tier || 1;

  function isTechniqueLocked(moduleKey) {
    // Only "user" role is restricted by tier. Admin and operator have full access.
    if (!isTierRestricted(role)) return false;
    var reqTier = techniqueTiers[moduleKey];
    if (reqTier === undefined) return false;
    return userTier < reqTier;
  }

  function getTechniqueRequiredTier(moduleKey) {
    return techniqueTiers[moduleKey] || 1;
  }

  // ===== INIT =====
  useEffect(() => {
    async function init() {
      try {
        const [health, modulesData] = await Promise.all([
          apiFetch('/health'),
          apiFetch('/modules'),
        ]);

        setVersion('v' + health.version);
        setMingwAvailable(health.mingw);
        setStatus(health.mingw ? 'online' : 'online');

        const mods = modulesData.modules || {};
        const sr = modulesData.stealth_ratings || {};
        const it = modulesData.info_texts || {};
        const eg = modulesData.exclusive_groups || {};
        const cg = modulesData.checkbox_groups || [];
        const pm = modulesData.params_map || {};
        const tt = modulesData.technique_tiers || {};
        const tn = modulesData.tier_names || {};

        setModules(mods);
        setStealthRatings(sr);
        setInfoTexts(it);
        setExclusiveGroups(eg);
        setCheckboxGroups(cg);
        setParamsMap(pm);
        setTechniqueTiers(tt);
        setTierNames(tn);

        // Init dropdown selections with defaults
        const dSel = {};
        const dOpts = {};
        Object.keys(eg).forEach((groupName) => {
          const options = eg[groupName];
          const optNames = Object.keys(options);
          dOpts[groupName] = options;

          let defaultName = optNames[0];
          for (let i = 0; i < optNames.length; i++) {
            const modKey = options[optNames[i]];
            if (mods[modKey] && mods[modKey].default) {
              defaultName = optNames[i];
              break;
            }
          }
          dSel[groupName] = defaultName;
        });
        dropdownOptionsMap.current = dOpts;
        setDropdownSelections(dSel);

        // Init checkbox states with defaults
        const cStates = {};
        cg.forEach((group) => {
          const keys = group[1];
          keys.forEach((key) => {
            if (mods[key]) {
              cStates[key] = !!mods[key].default;
            }
          });
        });
        setCheckboxStates(cStates);

        // Init param values with defaults
        const pVals = {};
        Object.keys(pm).forEach((moduleKey) => {
          pm[moduleKey].forEach((p) => {
            pVals[p[1]] = p[2]; // paramKey = default value
          });
        });
        setParamValues(pVals);

        // Load profiles and server file defaults
        loadProfileList();
        loadServerDefaults();
      } catch (err) {
        setStatus('offline');
        console.error('Init failed:', err);
      }
    }
    init();
  }, []);

  // ===== Auto-scroll console =====
  useEffect(() => {
    if (consoleRef.current) {
      consoleRef.current.scrollTop = consoleRef.current.scrollHeight;
    }
  }, [logs]);

  // ===== Stealth calculation =====
  useEffect(() => {
    if (Object.keys(modules).length === 0) return;
    computeStealth();
  }, [dropdownSelections, checkboxStates, modules, stealthRatings]);

  function computeStealth() {
    let totalScore = 0;
    let count = 0;

    const opts = dropdownOptionsMap.current;

    Object.keys(dropdownSelections).forEach((groupName) => {
      const displayName = dropdownSelections[groupName];
      const options = opts[groupName] || {};
      const moduleKey = options[displayName] || '';
      totalScore += stealthRatings[moduleKey] || 0;
      count += 1;
    });

    Object.keys(checkboxStates).forEach((key) => {
      if (checkboxStates[key]) {
        totalScore += stealthRatings[key] || 0;
        count += 1;
      }
    });

    const avg = count > 0 ? totalScore / count : 0;
    const rounded = Math.round(avg);
    let stars = '';
    for (let i = 0; i < 5; i++) {
      stars += i < rounded ? '\u2605' : '\u2606';
    }

    setStealthScore(avg);
    setStealthStars(stars);
  }

  // ===== Host PE logic =====
  const hasHostPe = files.host_pe !== null || !!serverDefaults['host_pe'];
  const iconDisabled = hasHostPe && !overrideIcon;

  function handleHostPeChange(file) {
    setFiles((prev) => ({ ...prev, host_pe: file }));
    if (file && !checkboxStates['pe_run']) {
      setCheckboxStates((prev) => ({ ...prev, pe_run: true }));
    }
  }

  // ===== Param activity: is a param's parent module active? =====
  function isParamActive(moduleKey) {
    // Check checkbox
    if (checkboxStates[moduleKey] !== undefined) {
      return checkboxStates[moduleKey];
    }
    // Check if selected in a dropdown
    const opts = dropdownOptionsMap.current;
    for (const groupName of Object.keys(opts)) {
      const displayName = dropdownSelections[groupName];
      const selectedKey = opts[groupName][displayName];
      if (selectedKey === moduleKey) return true;
    }
    return false;
  }

  // ===== Logging =====
  function addLog(message, tag) {
    setLogs((prev) => [...prev, { message, tag: tag || 'dim', time: timestamp() }]);
  }

  // ===== User menu: close on outside click =====
  useEffect(function() {
    if (!showUserMenu) return;
    function handleClickOutside(e) {
      if (userMenuRef.current && !userMenuRef.current.contains(e.target)) {
        setShowUserMenu(false);
      }
    }
    document.addEventListener('mousedown', handleClickOutside);
    return function() {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, [showUserMenu]);

  // ===== Profile management =====
  async function loadProfileList() {
    try {
      const data = await apiFetch('/profiles');
      setProfiles(data.profiles || []);
    } catch (err) {
      console.error('Failed to load profiles:', err);
    }
  }

  async function handleDeleteProfile() {
    if (!selectedProfile || isViewer) return;
    try {
      await apiFetch('/profiles/' + encodeURIComponent(selectedProfile), {
        method: 'DELETE',
      });
      addLog("Profile deleted: '" + selectedProfile + "'", 'info');
      setSelectedProfile('');
      setProfileDeleteConfirm(false);
      loadProfileList();
    } catch (err) {
      addLog('Failed to delete profile: ' + err.message, 'error');
      setProfileDeleteConfirm(false);
    }
  }

  async function loadServerDefaults() {
    try {
      var data = await apiFetch('/files/defaults/me');
      var defs = data.defaults || {};
      setServerDefaults(defs);
      // Fetch metadata for each default file
      var info = {};
      var promises = Object.keys(defs).map(async function(cat) {
        try {
          var meta = await apiFetch('/files/' + defs[cat]);
          info[cat] = meta;
        } catch (e) { /* ignore missing */ }
      });
      await Promise.all(promises);
      setServerDefaultInfo(info);
    } catch (err) {
      // ignore
    }
  }

  function handleServerDefaultsChanged(newDefaults) {
    setServerDefaults(newDefaults);
    // Re-fetch metadata
    var info = {};
    var promises = Object.keys(newDefaults).map(async function(cat) {
      try {
        var meta = await apiFetch('/files/' + newDefaults[cat]);
        info[cat] = meta;
      } catch (e) { /* ignore */ }
    });
    Promise.all(promises).then(function() {
      setServerDefaultInfo(info);
    });
    // Auto-enable pe_run when host_pe default is set, disable when cleared
    if (newDefaults['host_pe']) {
      setCheckboxStates(function(prev) { return Object.assign({}, prev, { pe_run: true }); });
    } else {
      // Only auto-disable if no local host_pe file either
      if (!files.host_pe) {
        setCheckboxStates(function(prev) { return Object.assign({}, prev, { pe_run: false }); });
      }
    }
  }

  function getFileDisplayName(category) {
    // If local file selected, show it
    if (files[category]) return files[category].name;
    // Map local state keys to server category keys
    var serverCat = category === 'sig_source' ? 'signature' : category;
    // If server default exists, show server file indicator
    var info = serverDefaultInfo[serverCat];
    if (info) return '\u2601 ' + info.original_name;
    return 'No file selected';
  }

  function hasFileForCategory(category) {
    return !!files[category] || !!serverDefaults[category];
  }

  function clearLocalFile(category) {
    setFiles(function(prev) {
      var next = Object.assign({}, prev);
      next[category] = null;
      return next;
    });
    // Also clear server default if one is set for this category
    var serverCat = category === 'sig_source' ? 'signature' : category;
    if (serverDefaults[serverCat]) {
      var body = {};
      body[serverCat] = null;
      apiFetch('/files/defaults', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      }).then(function(data) {
        handleServerDefaultsChanged(data.defaults || {});
      }).catch(function(err) {
        console.error('Failed to clear server default:', err);
      });
    }
    // If clearing host_pe, deactivate pe_run
    if (category === 'host_pe') {
      setCheckboxStates(function(prev) { return Object.assign({}, prev, { pe_run: false }); });
    }
  }

  async function handleLoadProfile() {
    if (!selectedProfile) {
      addLog('No profile selected.', 'error');
      return;
    }
    try {
      const config = await apiFetch('/profiles/' + encodeURIComponent(selectedProfile));
      applyProfile(config);
      addLog("Profile loaded: '" + selectedProfile + "'", 'info');
    } catch (err) {
      addLog('Failed to load profile: ' + err.message, 'error');
    }
  }

  function applyProfile(config) {
    const gs = config.global_settings || {};
    if (gs.out_name) setOutputName(gs.out_name);

    if (typeof config.build_mode === 'boolean') {
      setDebugMode(config.build_mode);
    }

    // Dropdowns
    const dropdowns = config.dropdowns || {};
    setDropdownSelections((prev) => {
      const next = { ...prev };
      Object.keys(dropdowns).forEach((groupName) => {
        if (next[groupName] !== undefined) {
          next[groupName] = dropdowns[groupName];
        }
      });
      return next;
    });

    // Checkboxes
    const checkboxes = config.checkboxes || {};
    setCheckboxStates((prev) => {
      const next = { ...prev };
      Object.keys(checkboxes).forEach((key) => {
        if (next[key] !== undefined) {
          next[key] = !!checkboxes[key];
        }
      });
      return next;
    });

    // Params
    const params = config.params || {};
    setParamValues((prev) => {
      const next = { ...prev };
      Object.keys(params).forEach((pk) => {
        if (next[pk] !== undefined) {
          next[pk] = params[pk];
        }
      });
      return next;
    });

    // Server files from profile
    if (config.server_files) {
      // Apply server_files as defaults
      apiFetch('/files/defaults', {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(config.server_files),
      }).then(function(data) {
        handleServerDefaultsChanged(data.defaults || {});
      }).catch(function() {});
    }
  }

  async function handleSaveProfile() {
    if (isViewer) return;
    const name = prompt('Enter profile name:');
    if (!name || !name.trim()) return;
    const trimmed = name.trim();

    const config = collectConfig();
    try {
      await apiFetch('/profiles/' + encodeURIComponent(trimmed), {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(config),
      });
      addLog("Profile saved: '" + trimmed + "'", 'info');
      loadProfileList();
    } catch (err) {
      addLog('Failed to save profile: ' + err.message, 'error');
    }
  }

  // ===== Reset Defaults =====
  function handleResetDefaults() {
    // Reset dropdowns
    const opts = dropdownOptionsMap.current;
    const dSel = {};
    Object.keys(opts).forEach((groupName) => {
      const options = opts[groupName];
      const optNames = Object.keys(options);
      let defaultName = optNames[0];
      for (let i = 0; i < optNames.length; i++) {
        const modKey = options[optNames[i]];
        if (modules[modKey] && modules[modKey].default) {
          defaultName = optNames[i];
          break;
        }
      }
      dSel[groupName] = defaultName;
    });
    setDropdownSelections(dSel);

    // Reset checkboxes
    const cStates = {};
    checkboxGroups.forEach((group) => {
      group[1].forEach((key) => {
        cStates[key] = modules[key] ? !!modules[key].default : false;
      });
    });
    setCheckboxStates(cStates);

    // Reset params
    const pVals = {};
    Object.keys(paramsMap).forEach((moduleKey) => {
      paramsMap[moduleKey].forEach((p) => {
        pVals[p[1]] = p[2];
      });
    });
    setParamValues(pVals);
  }

  // ===== Collect Config =====
  function collectConfig() {
    const opts = dropdownOptionsMap.current;

    // Dropdown selections as moduleKey
    const dSelKeys = {};
    Object.keys(dropdownSelections).forEach((groupName) => {
      const displayName = dropdownSelections[groupName];
      dSelKeys[groupName] = opts[groupName][displayName] || '';
    });

    // Active checkboxes
    const activeModules = [];
    Object.keys(checkboxStates).forEach((key) => {
      if (checkboxStates[key]) activeModules.push(key);
    });

    // Params with sleep_time/gargoyle_sleep conversion (sec -> ms)
    const buildParams = {};
    Object.keys(paramValues).forEach((pk) => {
      let val = paramValues[pk];
      if (pk === 'sleep_time') {
        try { val = String(Math.round(parseFloat(val) * 1000)); }
        catch (e) { val = '5000'; }
      }
      if (pk === 'gargoyle_sleep') {
        try { val = String(Math.round(parseFloat(val) * 1000)); }
        catch (e) { val = '30000'; }
      }
      buildParams[pk] = val;
    });

    // Build server_files mapping for categories without a local file
    var sfMap = {};
    var catKeys = ['payload', 'cover', 'icon', 'host_pe', 'signature'];
    var fileCatMap = { payload: 'payload', cover: 'cover', icon: 'icon', host_pe: 'host_pe', sig_source: 'signature' };
    catKeys.forEach(function(cat) {
      if (serverDefaults[cat]) {
        sfMap[cat] = serverDefaults[cat];
      }
    });

    return {
      global_settings: {
        payload_path: files.payload ? files.payload.name : '',
        cover_path: files.cover ? files.cover.name : '',
        icon_path: files.icon ? files.icon.name : '',
        host_pe_path: files.host_pe ? files.host_pe.name : '',
        sig_path: files.sig_source ? files.sig_source.name : '',
        out_name: outputName,
      },
      build_mode: debugMode,
      dropdowns: { ...dropdownSelections },
      checkboxes: { ...checkboxStates },
      params: { ...paramValues }, // raw values for profile save
      active_modules: activeModules,
      dropdown_selections: dSelKeys,
      debug_mode: debugMode,
      override_icon: overrideIcon,
      output_name: outputName,
      build_params: buildParams,
      server_files: sfMap,
    };
  }

  // ===== Build =====
  async function handleBuild() {
    if (!userCanBuild) return;
    if (!files.payload && !serverDefaults['payload']) {
      addLog('No payload file selected. Please select a .bin file or set a server default.', 'error');
      return;
    }

    // Tier validation: check all active techniques before building
    var _bOpts = dropdownOptionsMap.current;
    var _tierBlocked = false;
    Object.keys(dropdownSelections).forEach(function(_gn) {
      var _dn = dropdownSelections[_gn];
      var _gopts = _bOpts[_gn] || {};
      var _mk = _gopts[_dn] || '';
      if (isTechniqueLocked(_mk)) {
        var _rt = getTechniqueRequiredTier(_mk);
        addLog('Technique "' + _mk + '" requires ' + (TIER_BADGE_NAMES[_rt] || '') + ' tier', 'error');
        _tierBlocked = true;
      }
    });
    Object.keys(checkboxStates).forEach(function(_ck) {
      if (checkboxStates[_ck] && isTechniqueLocked(_ck)) {
        var _rt2 = getTechniqueRequiredTier(_ck);
        addLog('Technique "' + _ck + '" requires ' + (TIER_BADGE_NAMES[_rt2] || '') + ' tier', 'error');
        _tierBlocked = true;
      }
    });
    if (_tierBlocked) return;

    setBuildStatus('building');
    setLogs([]);
    setDownloadFiles([]);

    const config = collectConfig();

    const fd = new FormData();
    fd.append('config', JSON.stringify(config));
    if (files.payload) fd.append('payload', files.payload);
    if (files.cover) fd.append('cover', files.cover);
    if (files.icon && (!hasHostPe || overrideIcon)) {
      fd.append('icon', files.icon);
    }
    if (files.host_pe) fd.append('host_pe', files.host_pe);
    if (files.sig_source) fd.append('sig_source', files.sig_source);

    try {
      const resp = await authFetch(API_BASE + '/build', { method: 'POST', body: fd });
      if (!resp.ok) {
        const errData = await resp.json().catch(() => ({}));
        throw new Error(errData.detail?.error || 'Build request failed');
      }
      const data = await resp.json();
      setBuildId(data.build_id);
      addLog('Build started (ID: ' + data.build_id + ')', 'info');
      connectBuildWebSocket(data.build_id);
    } catch (err) {
      addLog('Build failed: ' + err.message, 'error');
      setBuildStatus('idle');
    }
  }

  function connectBuildWebSocket(id) {
    const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const token = getToken();
    const wsUrl = protocol + '//' + window.location.host + API_BASE + '/build/' + id + '/logs?token=' + encodeURIComponent(token || '');

    if (buildWsRef.current) {
      buildWsRef.current.close();
    }

    const ws = new WebSocket(wsUrl);
    buildWsRef.current = ws;

    ws.onmessage = function (event) {
      let entry;
      try {
        entry = JSON.parse(event.data);
      } catch (e) {
        return;
      }

      if (entry.done) {
        if (entry.status === 'success') {
          setLogs((prev) => [...prev, { message: 'Build completed successfully!', tag: 'ok', time: timestamp() }]);

          // Build download files list
          const dlFiles = [];
          const mainFilename = outputName.endsWith('.exe') ? outputName : outputName + '.exe';
          dlFiles.push({
            filename: mainFilename,
            downloadUrl: API_BASE + '/build/' + id + '/download',
            type: 'primary',
            subtitle: 'Main Executable',
          });

          if (entry.extra_files && entry.extra_files.length > 0) {
            entry.extra_files.forEach((filename) => {
              dlFiles.push({
                filename: filename,
                downloadUrl: API_BASE + '/build/' + id + '/download/' + encodeURIComponent(filename),
                type: 'secondary',
                subtitle: getFileSubtitle(filename),
              });
            });
          }

          setDownloadFiles(dlFiles);
          setBuildStatus('success');
        } else {
          setLogs((prev) => [...prev, { message: 'Build failed.', tag: 'error', time: timestamp() }]);
          setBuildStatus('error');
        }
        return;
      }

      // Regular log message
      setLogs((prev) => [...prev, {
        message: entry.message || '',
        tag: entry.tag || 'dim',
        time: timestamp(),
      }]);
    };

    ws.onerror = function () {
      setLogs((prev) => [...prev, { message: 'WebSocket connection error.', tag: 'error', time: timestamp() }]);
      setBuildStatus('error');
    };

    ws.onclose = function () {
      buildWsRef.current = null;
    };
  }

  function handleDownloadAll() {
    downloadFiles.forEach((file, i) => {
      setTimeout(() => {
        downloadWithAuth(file.downloadUrl, file.filename);
      }, i * 500);
    });
  }

  // ===== Tooltip =====
  function handleInfoClick(e, moduleKey) {
    e.stopPropagation();
    if (tooltip.visible && tooltip.moduleKey === moduleKey) {
      setTooltip({ visible: false, moduleKey: null, anchorRect: null });
    } else {
      const rect = e.currentTarget.getBoundingClientRect();
      setTooltip({ visible: true, moduleKey, anchorRect: rect });
    }
  }

  // ===== RENDER =====
  const statusClass = status === 'online' ? 'online' : status === 'offline' ? 'offline' : '';
  const statusLabel = status === 'online'
    ? (mingwAvailable ? 'SYS READY' : 'NO COMPILER')
    : status === 'offline' ? 'OFFLINE' : 'CONNECTING...';

  const isBuilding = buildStatus === 'building';

  // --- Shared header renderer ---
  function renderHeader(showBackButton) {
    return (
      <header className="app-header">
        <div className="header-inner">
          <div className="header-left">
            {showBackButton && (
              <button className="btn btn-secondary btn-sm admin-back-btn" onClick={function() { setCurrentView('main'); }}>
                {'\u2190'} Back to Builder
              </button>
            )}
            <h1 className="logo">UNGGOY LOADER</h1>
            <span className="subtitle">{showBackButton ? 'ADMIN PANEL' : 'WEB EDITION \u00B7 ' + version}</span>
          </div>
          {!showBackButton && (
            <div className="header-center">
              <div className="header-stealth">
                <span className="header-stealth-label">STEALTH</span>
                <div className="header-stealth-bar-track">
                  <div className="header-stealth-bar-fill" style={{ width: (stealthScore / 5 * 100) + '%' }}></div>
                </div>
                <span className="header-stealth-stars">{stealthStars}</span>
                <span className="header-stealth-score">{stealthScore.toFixed(1)}</span>
              </div>
            </div>
          )}
          <div className="header-right">
            <div className="user-menu-wrapper" ref={userMenuRef}>
              <div className="user-menu-trigger" onClick={function() { setShowUserMenu(!showUserMenu); }}>
                <span className="header-user-name">{username}</span>
                <TierBadge tier={userTier} />
                <span className="user-menu-arrow">{'\u25BE'}</span>
              </div>
              {showUserMenu && (
                <div className="user-menu-dropdown">
                  <div className="user-menu-header">
                    <span className="user-menu-username">{username}</span>
                    <div className="user-menu-badges">
                      <RoleBadge role={role} />
                      <TierBadge tier={userTier} />
                    </div>
                  </div>
                  <div className="user-menu-divider"></div>
                  <button className="user-menu-item" onClick={function() { setShowChangePw(true); setShowUserMenu(false); }}>
                    Change Password
                  </button>
                  {userIsAdmin && (
                    <button className="user-menu-item" onClick={function() { setCurrentView(currentView === 'admin' ? 'main' : 'admin'); setShowUserMenu(false); }}>
                      {currentView === 'admin' ? 'Back to Builder' : 'Admin Panel'}
                    </button>
                  )}
                  <div className="user-menu-divider"></div>
                  <button className="user-menu-item user-menu-logout" onClick={function() { setShowUserMenu(false); onLogout(); }}>
                    Logout
                  </button>
                </div>
              )}
            </div>
            <span className={'status-dot ' + statusClass}></span>
            <span className="status-text">{statusLabel}</span>
          </div>
        </div>
        <div className="header-accent-bar"></div>
      </header>
    );
  }

  // --- Admin view ---
  if (currentView === 'admin') {
    return (
      <React.Fragment>
        {renderHeader(true)}
        <main>
          <AdminPanel currentUsername={username} />
        </main>
        {showChangePw && <ChangePasswordModal onClose={function() { setShowChangePw(false); }} />}
      </React.Fragment>
    );
  }

  return (
    <React.Fragment>
      {/* HEADER */}
      {renderHeader(false)}

      <main>
        {/* GLOBAL SETTINGS */}
        <section className="card">
          <SectionHeader title="GLOBAL SETTINGS" />

          {/* Profiles */}
          <div className="profile-row">
            <select
              className="profile-select"
              value={selectedProfile}
              onChange={(e) => { setSelectedProfile(e.target.value); setProfileDeleteConfirm(false); }}
            >
              <option value="">{'-- Select Profile (' + profiles.length + ') --'}</option>
              {profiles.map((name) => (
                <option key={name} value={name}>{name}</option>
              ))}
            </select>
            <button className="btn btn-secondary btn-sm" onClick={handleLoadProfile}>LOAD</button>
            <button className="btn btn-secondary btn-sm" onClick={handleSaveProfile} disabled={isViewer}
              title={isViewer ? 'Viewer role \u2014 contact admin for write access' : 'Save profile'}>SAVE</button>
            {profileDeleteConfirm ? (
              <React.Fragment>
                <button className="btn btn-danger btn-sm" onClick={handleDeleteProfile}>CONFIRM</button>
                <button className="btn btn-secondary btn-sm" onClick={function() { setProfileDeleteConfirm(false); }}>CANCEL</button>
              </React.Fragment>
            ) : (
              <button className="btn btn-danger-outline btn-sm" onClick={function() { setProfileDeleteConfirm(true); }}
                disabled={!selectedProfile || isViewer}
                title={!selectedProfile ? 'Select a profile first' : isViewer ? 'Viewer role' : 'Delete profile'}>
                DELETE
              </button>
            )}
          </div>

          <Divider />

          {/* File Inputs */}
          <FileInput
            label="PAYLOAD (.bin)"
            accept=".bin"
            displayName={getFileDisplayName('payload')}
            onFileChange={(f) => setFiles((prev) => ({ ...prev, payload: f }))}
            disabled={isViewer}
          >
            {(files.payload || serverDefaults['payload']) && (
              <button className="btn btn-secondary btn-xs file-clear-btn" onClick={function() { clearLocalFile('payload'); }} title="Clear local override">
                {'\u2715'}
              </button>
            )}
          </FileInput>

          <FileInput
            label="COVER (.bmp)"
            accept=".bmp"
            displayName={getFileDisplayName('cover')}
            onFileChange={(f) => setFiles((prev) => ({ ...prev, cover: f }))}
            disabled={isViewer}
          >
            {(files.cover || serverDefaults['cover']) && (
              <button className="btn btn-secondary btn-xs file-clear-btn" onClick={function() { clearLocalFile('cover'); }} title="Clear local override">
                {'\u2715'}
              </button>
            )}
          </FileInput>

          <FileInput
            label="ICON (.ico)"
            accept=".ico"
            displayName={getFileDisplayName('icon')}
            onFileChange={(f) => setFiles((prev) => ({ ...prev, icon: f }))}
            disabled={isViewer || iconDisabled}
          >
            <label className="override-toggle" title="Override icon when Host PE is set">
              <input
                type="checkbox"
                checked={overrideIcon}
                onChange={(e) => setOverrideIcon(e.target.checked)}
                disabled={isViewer}
              />
              <span className="override-label">OVERRIDE</span>
            </label>
            {(files.icon || serverDefaults['icon']) && (
              <button className="btn btn-secondary btn-xs file-clear-btn" onClick={function() { clearLocalFile('icon'); }} title="Clear local override">
                {'\u2715'}
              </button>
            )}
          </FileInput>

          <FileInput
            label="HOST PE (.exe)"
            accept=".exe"
            displayName={getFileDisplayName('host_pe')}
            onFileChange={handleHostPeChange}
            disabled={isViewer}
          >
            {(files.host_pe || serverDefaults['host_pe']) && (
              <button className="btn btn-secondary btn-xs file-clear-btn" onClick={function() { clearLocalFile('host_pe'); }} title="Clear local override">
                {'\u2715'}
              </button>
            )}
          </FileInput>

          <FileInput
            label="SIGNATURE SRC"
            accept=".exe,.dll"
            displayName={getFileDisplayName('sig_source')}
            onFileChange={(f) => setFiles((prev) => ({ ...prev, sig_source: f }))}
            disabled={isViewer}
          >
            {(files.sig_source || serverDefaults['signature']) && (
              <button className="btn btn-secondary btn-xs file-clear-btn" onClick={function() { clearLocalFile('sig_source'); }} title="Clear local override">
                {'\u2715'}
              </button>
            )}
          </FileInput>

          <div className="file-row">
            <label className="file-label">OUTPUT NAME</label>
            <div className="file-input-group">
              <input
                type="text"
                className="text-input"
                value={outputName}
                onChange={(e) => setOutputName(e.target.value)}
                spellCheck={false}
                disabled={isViewer}
              />
            </div>
          </div>

          <Divider />
        </section>

        {/* FILE MANAGER */}
        <FileManager
          serverDefaults={serverDefaults}
          onDefaultsChanged={handleServerDefaultsChanged}
          disabled={isViewer}
        />

        {/* BUILD MODE */}
        <section className="card">
          <SectionHeader title="BUILD MODE" />
          <div className="mode-row">
            <label className={'mode-option mode-debug' + (debugMode ? '' : '')}>
              <input
                type="radio"
                name="build-mode"
                value="debug"
                checked={debugMode}
                onChange={() => setDebugMode(true)}
                disabled={isViewer}
              />
              <span className="mode-radio"></span>
              <span className="mode-text">DEBUG &mdash; Console visible</span>
            </label>
            <label className={'mode-option mode-release'}>
              <input
                type="radio"
                name="build-mode"
                value="release"
                checked={!debugMode}
                onChange={() => setDebugMode(false)}
                disabled={isViewer}
              />
              <span className="mode-radio"></span>
              <span className="mode-text">RELEASE &mdash; Silent deployment</span>
            </label>
          </div>
        </section>

        {/* TECHNIQUE SELECTION */}
        <section className="card">
          <SectionHeader title="TECHNIQUE SELECTION" />
          <div className="techniques-toolbar">
            <span className="techniques-subtitle">SELECT ACTIVE TECHNIQUES</span>
            <button className="btn btn-secondary btn-sm" onClick={handleResetDefaults} disabled={isViewer}>RESET DEFAULTS</button>
          </div>
          <Divider />

          {/* Dropdowns */}
          {Object.keys(exclusiveGroups).map((groupName) => {
            const options = exclusiveGroups[groupName];
            const optionNames = Object.keys(options);
            return (
              <div className="dropdown-group" key={groupName}>
                <div className="dropdown-label">{groupName}</div>
                <select
                  className="dropdown-select"
                  value={dropdownSelections[groupName] || ''}
                  onChange={(e) => {
                    var selectedOpt = e.target.value;
                    var selectedKey = options[selectedOpt];
                    if (isTechniqueLocked(selectedKey)) return;
                    setDropdownSelections((prev) => ({ ...prev, [groupName]: selectedOpt }));
                  }}
                  disabled={isViewer}
                >
                  {optionNames.map((optName) => {
                    var moduleKey = options[optName];
                    var locked = isTechniqueLocked(moduleKey);
                    var reqTier = getTechniqueRequiredTier(moduleKey);
                    var tierLabel = locked ? ' \uD83D\uDD12 [' + (TIER_BADGE_NAMES[reqTier] || '') + ']' : '';
                    return (
                      <option key={optName} value={optName} disabled={locked} className={locked ? 'dropdown-option-locked' : ''}>
                        {optName}{tierLabel}
                      </option>
                    );
                  })}
                </select>
              </div>
            );
          })}

          <Divider style={{ marginTop: '12px', marginBottom: '4px' }} />

          {/* Checkbox groups */}
          {checkboxGroups.map((group) => {
            const groupName = group[0];
            const keys = group[1];
            return (
              <React.Fragment key={groupName}>
                <div className="checkbox-group-label">{groupName}</div>
                {keys.map((key) => {
                  const mod = modules[key];
                  if (!mod) return null;
                  const checked = !!checkboxStates[key];
                  const params = paramsMap[key] || [];
                  const locked = isTechniqueLocked(key);
                  const reqTier = getTechniqueRequiredTier(key);

                  return (
                    <div className={'checkbox-row' + (locked ? ' technique-locked' : '')} key={key} data-module={key}
                      title={locked ? 'Requires ' + (TIER_BADGE_NAMES[reqTier] || '') + ' tier \u2014 contact admin to upgrade' : ''}>
                      <label className="toggle-wrap" role="switch" aria-checked={checked}>
                        <input
                          type="checkbox"
                          checked={locked ? false : checked}
                          onChange={(e) => {
                            if (!locked) {
                              setCheckboxStates((prev) => ({ ...prev, [key]: e.target.checked }));
                            }
                          }}
                          disabled={isViewer || locked}
                        />
                        <span className="toggle-slider"></span>
                      </label>
                      <span
                        className={'checkbox-label' + (checked && !locked ? '' : ' inactive')}
                        onClick={() => {
                          if (!isViewer && !locked) {
                            setCheckboxStates((prev) => ({ ...prev, [key]: !prev[key] }));
                          }
                        }}
                      >
                        {mod.name.toUpperCase()}
                      </span>

                      {locked && <TierRequiredTag tier={reqTier} />}

                      {params.length > 0 && !locked && (
                        <div className="param-group">
                          {params.map((p) => {
                            const paramLabel = p[0];
                            const paramKey = p[1];
                            const paramDefault = p[2];
                            const active = isParamActive(key);
                            const w = Math.max((paramValues[paramKey] || paramDefault).length + 3, 8);
                            return (
                              <React.Fragment key={paramKey}>
                                <span className="param-label">{paramLabel}</span>
                                <input
                                  type="text"
                                  className="param-input"
                                  value={paramValues[paramKey] || ''}
                                  onChange={(e) => {
                                    setParamValues((prev) => ({ ...prev, [paramKey]: e.target.value }));
                                  }}
                                  disabled={!active || isViewer}
                                  spellCheck={false}
                                  style={{ width: (w * 8) + 'px' }}
                                />
                              </React.Fragment>
                            );
                          })}
                        </div>
                      )}

                      <button
                        className="btn-info"
                        aria-label={'Show info for ' + mod.name}
                        onClick={(e) => handleInfoClick(e, key)}
                      >
                        i
                      </button>
                    </div>
                  );
                })}
              </React.Fragment>
            );
          })}
        </section>

        {/* BUILD SECTION */}
        <section className="card">
          <div className="build-btn-wrap" title={isViewer ? 'Viewer role \u2014 contact admin for build access' : ''}>
            <button
              className={'btn btn-primary btn-build' + (isBuilding ? ' btn-build--building' : '') + (isViewer ? ' btn-viewer-disabled' : '')}
              onClick={handleBuild}
              disabled={isBuilding || !userCanBuild}
            >
              <span className="build-btn-icon">{isBuilding ? '\u231B' : '\u25B6'}</span>
              {isBuilding ? ' BREATHING METHANE...' : isViewer ? ' VIEWER MODE \u2014 BUILD DISABLED' : ' GENERATE UNGGOY BINARY'}
            </button>
            {isViewer && (
              <div className="viewer-tooltip">Viewer role &mdash; contact admin for build access</div>
            )}
          </div>

          <SectionHeader title="OUTPUT LOG" style={{ marginTop: '16px' }} />

          <div className="build-console" ref={consoleRef} role="log" aria-live="polite" aria-relevant="additions">
            {logs.length === 0 ? (
              <span className="build-console-empty">Awaiting build command...</span>
            ) : (
              logs.map((entry, i) => <LogEntry key={i} entry={entry} />)
            )}
          </div>

          {/* Download Area */}
          {buildStatus === 'success' && downloadFiles.length > 0 && (
            <DownloadArea files={downloadFiles} onDownloadAll={handleDownloadAll} />
          )}
        </section>

      </main>

      {/* Info Tooltip Portal */}
      <InfoTooltip
        visible={tooltip.visible}
        moduleKey={tooltip.moduleKey}
        anchorRect={tooltip.anchorRect}
        modules={modules}
        infoTexts={infoTexts}
        stealthRatings={stealthRatings}
        onClose={() => setTooltip({ visible: false, moduleKey: null, anchorRect: null })}
      />
      {showChangePw && <ChangePasswordModal onClose={function() { setShowChangePw(false); }} />}
    </React.Fragment>
  );
}

// ===== Mount =====
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
