Refactor: optimize JavaScript performance and robustness (#37)

* refactor: optimize JavaScript performance and robustness

- Replace deprecated navigator.platform with userAgentData
- Add comprehensive null checks for all DOM operations (23+ locations)
- Cache frequently queried DOM elements to eliminate repeated queries
- Remove dead code (installCmds, osCmds objects)
- Improve clipboard copy with execCommand fallback and visual error feedback
- Fix Easter egg animation with null-safety wrapper
- Update font loading comment for clarity

Performance improvements:
- Eliminated 4 DOM queries per state update cycle
- Reduced bundle size by ~13 lines of dead code
- Added visual feedback for clipboard failures

See OPTIMIZATIONS_SUMMARY.md for detailed before/after comparisons

* chore: clean up redundant lock files

- Remove package-lock.json and pnpm-lock.yaml
- Keep bun.lock as primary lock file (per README)
- Update .gitignore to prevent future lock file conflicts

This prevents dependency version mismatches when different
contributors use different package managers.

* fix: always cleanup clipboard fallback textarea

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Hemant Sudarshan 2026-02-13 21:45:42 +05:30 committed by GitHub
parent 4eef8afaaf
commit 84114c5313
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 357 additions and 83 deletions

4
.gitignore vendored
View File

@ -1,6 +1,10 @@
# Dependencies
node_modules/
# Lock files (keep only bun.lock - see README)
package-lock.json
pnpm-lock.yaml
# Build output
dist/

229
OPTIMIZATIONS_SUMMARY.md Normal file
View File

@ -0,0 +1,229 @@
# Code Optimizations Summary
This document summarizes the technical improvements made to the OpenClaw landing page codebase.
## Changes Made
### ✅ 1. Fixed Deprecated API Usage
**File**: `src/pages/index.astro` (Line ~320)
**Before**:
```javascript
const isWindows = navigator.platform.toLowerCase().includes('win') ||
navigator.userAgent.toLowerCase().includes('windows');
```
**After**:
```javascript
const isWindows = navigator.userAgentData?.platform === 'Windows' ||
navigator.userAgent.toLowerCase().includes('windows');
```
**Impact**: `navigator.platform` is deprecated and will be removed from browsers. The new code uses the modern `navigator.userAgentData` API with a fallback.
---
### ✅ 2. Removed Dead Code
**File**: `src/pages/index.astro` (Lines 297-309)
**Removed**:
- `installCmds` object (unused, duplicated in `copyCommands`)
- `osCmds` object (unused)
**Impact**: Reduced bundle size by ~150 bytes and improved code clarity.
---
### ✅ 3. Cached DOM Queries for Performance
**File**: `src/pages/index.astro` (Lines 349-353)
**Added**:
```javascript
// Cached query selectors for frequently updated elements
const pmCmdElements = document.querySelectorAll('.pm-cmd');
const pmInstallElements = document.querySelectorAll('.pm-install');
const osCmdElements = document.querySelectorAll('.os-cmd');
const osCmdHackableElements = document.querySelectorAll('.os-cmd-hackable');
```
**Before** (in `updateCommands` function):
```javascript
document.querySelectorAll('.pm-cmd').forEach(...); // Called on every update
document.querySelectorAll('.pm-install').forEach(...);
document.querySelectorAll('.os-cmd').forEach(...);
document.querySelectorAll('.os-cmd-hackable').forEach(...);
```
**After**:
```javascript
pmCmdElements.forEach(...); // Uses cached reference
pmInstallElements.forEach(...);
osCmdElements.forEach(...);
osCmdHackableElements.forEach(...);
```
**Impact**: Eliminated 4 repeated DOM queries per state update. Improved performance, especially on slower devices.
---
### ✅ 4. Added Null-Safe Operations
**Files**: `src/pages/index.astro`
**Changed**: All DOM operations now include null checks using `if` statements:
```javascript
// Before
osDetected.textContent = osLabels[currentOs];
// After
if (osDetected) osDetected.textContent = osLabels[currentOs];
```
**Affected functions**:
- `updateCommands()` - 8 null checks added
- `updateVisibility()` - 11 null checks added
- Event listeners - 2 null checks added
- Easter egg animation - 1 null check added
**Impact**: Prevents runtime crashes if HTML elements are missing or renamed. More resilient code.
---
### ✅ 5. Improved Clipboard Copy Handler
**File**: `src/pages/index.astro` (Lines 531-578)
**Improvements**:
1. **Added fallback to `execCommand`** for older browsers or non-HTTPS contexts
2. **Added visual error feedback** - red flash on copy failure
3. **Added null checks** for icon elements
4. **Added validation** for command key existence
**Before**:
```javascript
try {
await navigator.clipboard.writeText(code);
// ... success handling
} catch (err) {
console.error('Failed to copy:', err); // Silent failure
}
```
**After**:
```javascript
let success = false;
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(code);
success = true;
} else {
// Fallback using textarea + execCommand
const textArea = document.createElement('textarea');
textArea.value = code;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.select();
success = document.execCommand('copy');
document.body.removeChild(textArea);
}
} catch (err) {
console.error('Failed to copy:', err);
success = false;
}
if (success) {
// Success feedback (green checkmark)
} else {
// Visual error feedback - brief red flash
btn.style.background = 'rgba(239, 68, 68, 0.3)';
setTimeout(() => {
btn.style.background = '';
}, 1000);
}
```
**Impact**: Copy works in more environments, users get visual feedback on failure.
---
### ✅ 6. Updated Font Loading Comment
**File**: `src/layouts/Layout.astro` (Line 40)
**Changed**: Updated comment to clarify that `display=swap` is already implemented for performance.
**Note**: The `&display=swap` parameter was already present in the URL. No functional change, just documentation improvement.
---
### ✅ 7. Fixed Easter Egg Null Safety
**File**: `src/pages/index.astro` (Lines 582-615)
**Before**:
```javascript
const lobsterIcon = document.querySelector('.lobster-icon');
const tagline = document.getElementById('tagline');
const originalTagline = tagline.textContent; // Could crash if null
lobsterIcon.addEventListener('mouseenter', () => { ... }); // Could crash if null
```
**After**:
```javascript
const lobsterIcon = document.querySelector('.lobster-icon');
const tagline = document.getElementById('tagline');
if (lobsterIcon && tagline) {
const originalTagline = tagline.textContent;
lobsterIcon.addEventListener('mouseenter', () => { ... });
}
```
**Impact**: Easter egg won't crash if elements are missing.
---
## Summary Statistics
| Metric | Before | After | Improvement |
|--------|--------|-------|-------------|
| Dead code lines | 13 | 0 | -13 lines |
| DOM queries per update | 4 | 0 | 100% cached |
| Null checks | 0 | ~23 | +∞ |
| Clipboard fallback | ❌ | ✅ | Works in more contexts |
| Deprecated APIs | 1 | 0 | Future-proof |
| Visual error feedback | ❌ | ✅ | Better UX |
---
## Remaining Recommendations
### Lock File Cleanup (Not Implemented)
The project currently has **3 lock files**:
- `bun.lock` (86KB)
- `package-lock.json` (188KB)
- `pnpm-lock.yaml` (107KB)
**Recommendation**: Choose one package manager and remove the other lock files. Based on the README using `bun install`, keep `bun.lock` and delete/gitignore the others.
**Why**: Different contributors using different package managers can lead to dependency version mismatches.
---
## Files Modified
1. ✅ `src/pages/index.astro` - Main JavaScript improvements
2. ✅ `src/layouts/Layout.astro` - Font loading comment
## Testing Recommendations
1. Test clipboard copy on different browsers (Chrome, Firefox, Safari)
2. Test clipboard copy in HTTP vs HTTPS contexts
3. Test with browser DevTools - delete elements and verify no console errors
4. Test OS detection on Windows, macOS, Linux
5. Test mode switching (One-liner, npm, Hackable, macOS)
6. Test beta toggle functionality
7. Hover over lobster icon to test Easter egg
---
**All critical technical flaws have been addressed.** The code is now more robust, performant, and future-proof.

View File

@ -43,7 +43,7 @@ const {
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content="https://openclaw.ai/og-image.png" />
<!-- Fonts: Clash Display + Satoshi from Fontshare -->
<!-- Fonts: Clash Display + Satoshi from Fontshare with font-display swap for performance -->
<link rel="preconnect" href="https://api.fontshare.com">
<link rel="preconnect" href="https://cdn.fontshare.com" crossorigin>
<link href="https://api.fontshare.com/v2/css?f[]=clash-display@700,600,500&f[]=satoshi@400,500,700&display=swap" rel="stylesheet">

View File

@ -293,25 +293,18 @@ const duration2 = (row2.length / 2 * pixelsPerItem) / pixelsPerSecond;
</section>
<script>
const installCmds = {
npm: 'npm i -g openclaw',
pnpm: 'pnpm add -g openclaw'
};
// Windows install commands
const windowsPsCmd = 'iwr -useb https://openclaw.ai/install.ps1 | iex';
const windowsPsBetaCmd = '& ([scriptblock]::Create((iwr -useb https://openclaw.ai/install.ps1))) -Tag beta';
const windowsCmdCmd = 'curl -fsSL https://openclaw.ai/install.cmd -o install.cmd && install.cmd && del install.cmd';
const windowsCmdBetaCmd = 'curl -fsSL https://openclaw.ai/install.cmd -o install.cmd && install.cmd --tag beta && del install.cmd';
const osCmds = {
unix: "curl -fsSL https://openclaw.ai/install.sh | bash",
windows: windowsPsCmd
};
const osLabels = {
unix: 'macOS/Linux',
windows: 'Windows'
};
// State
let currentPm = 'npm';
let currentMode = 'oneliner';
let currentHackable = 'installer';
@ -319,11 +312,12 @@ const duration2 = (row2.length / 2 * pixelsPerItem) / pixelsPerSecond;
let osPickerExpanded = false;
let currentWinShell = 'powershell';
// Auto-detect OS
const isWindows = navigator.platform.toLowerCase().includes('win') ||
// Auto-detect OS using modern API with fallback
const isWindows = navigator.userAgentData?.platform === 'Windows' ||
navigator.userAgent.toLowerCase().includes('windows');
let currentOs = isWindows ? 'windows' : 'unix';
// DOM Elements - cached once for performance
const pmBtns = document.querySelectorAll('.pm-btn');
const hackableBtns = document.querySelectorAll('.hackable-btn');
const osBtns = document.querySelectorAll('.os-btn');
@ -349,6 +343,12 @@ const duration2 = (row2.length / 2 * pixelsPerItem) / pixelsPerSecond;
const quickCommentInstall = document.getElementById('quick-comment-install');
const quickCommentOnboard = document.getElementById('quick-comment-onboard');
// Cached query selectors for frequently updated elements
const pmCmdElements = document.querySelectorAll('.pm-cmd');
const pmInstallElements = document.querySelectorAll('.pm-install');
const osCmdElements = document.querySelectorAll('.os-cmd');
const osCmdHackableElements = document.querySelectorAll('.os-cmd-hackable');
const comments = {
oneliner: {
stable: "# Works everywhere. Installs everything. You're welcome. 🦞",
@ -365,14 +365,14 @@ const duration2 = (row2.length / 2 * pixelsPerItem) / pixelsPerSecond;
};
function updateCommands() {
// Update hackable mode commands
document.querySelectorAll('.pm-cmd').forEach(cmd => cmd.textContent = currentPm);
// Update hackable mode commands using cached elements
pmCmdElements.forEach(cmd => cmd.textContent = currentPm);
// Update quick mode install command (with beta support)
const betaSuffix = currentBeta ? '@beta' : '';
const installCmd = currentPm === 'npm'
? `npm i -g openclaw${betaSuffix}`
: `pnpm add -g openclaw${betaSuffix}`;
document.querySelectorAll('.pm-install').forEach(cmd => cmd.textContent = installCmd);
pmInstallElements.forEach(cmd => cmd.textContent = installCmd);
// Update one-liner OS command (with beta and shell support)
let onelinerCmd;
if (currentOs === 'unix') {
@ -384,32 +384,35 @@ const duration2 = (row2.length / 2 * pixelsPerItem) / pixelsPerSecond;
} else {
onelinerCmd = currentBeta ? windowsPsBetaCmd : windowsPsCmd;
}
document.querySelectorAll('.os-cmd').forEach(cmd => cmd.textContent = onelinerCmd);
osCmdElements.forEach(cmd => cmd.textContent = onelinerCmd);
// Update hackable OS command for installer mode
const hackableOsCmd = "curl -fsSL https://openclaw.ai/install.sh | bash -s -- --install-method git";
document.querySelectorAll('.os-cmd-hackable').forEach(cmd => cmd.textContent = hackableOsCmd);
// Update OS indicator text
osDetected.textContent = osLabels[currentOs];
// Update hackable content visibility
osCmdHackableElements.forEach(cmd => cmd.textContent = hackableOsCmd);
// Update OS indicator text (null-safe)
if (osDetected) osDetected.textContent = osLabels[currentOs];
// Update hackable content visibility (null-safe)
if (currentMode === 'hackable') {
hackableInstallerContent.style.display = currentHackable === 'installer' ? 'block' : 'none';
hackablePnpmContent.style.display = currentHackable === 'pnpm' ? 'block' : 'none';
if (hackableInstallerContent) hackableInstallerContent.style.display = currentHackable === 'installer' ? 'block' : 'none';
if (hackablePnpmContent) hackablePnpmContent.style.display = currentHackable === 'pnpm' ? 'block' : 'none';
}
// Update beta button state
betaBtn.classList.toggle('active', currentBeta);
betaBtn.dataset.beta = currentBeta.toString();
// Update comment texts based on beta state
// Update beta button state (null-safe)
if (betaBtn) {
betaBtn.classList.toggle('active', currentBeta);
betaBtn.dataset.beta = currentBeta.toString();
}
// Update comment texts based on beta state (null-safe)
const mode = currentBeta ? 'beta' : 'stable';
onelinerComment.textContent = comments.oneliner[mode];
quickCommentInstall.textContent = comments.quickInstall[mode];
quickCommentOnboard.textContent = comments.quickOnboard[mode];
if (onelinerComment) onelinerComment.textContent = comments.oneliner[mode];
if (quickCommentInstall) quickCommentInstall.textContent = comments.quickInstall[mode];
if (quickCommentOnboard) quickCommentOnboard.textContent = comments.quickOnboard[mode];
}
function updateVisibility() {
codeOneliner.style.display = currentMode === 'oneliner' ? 'block' : 'none';
codeQuick.style.display = currentMode === 'quick' ? 'block' : 'none';
codeHackable.style.display = currentMode === 'hackable' ? 'block' : 'none';
codeMacos.style.display = currentMode === 'macos' ? 'block' : 'none';
// Null-safe visibility updates
if (codeOneliner) codeOneliner.style.display = currentMode === 'oneliner' ? 'block' : 'none';
if (codeQuick) codeQuick.style.display = currentMode === 'quick' ? 'block' : 'none';
if (codeHackable) codeHackable.style.display = currentMode === 'hackable' ? 'block' : 'none';
if (codeMacos) codeMacos.style.display = currentMode === 'macos' ? 'block' : 'none';
// Show OS indicator for one-liner, PM switch for quick, hackable switch for hackable, nothing for macos
const showOsControls = currentMode === 'oneliner';
@ -420,28 +423,32 @@ const duration2 = (row2.length / 2 * pixelsPerItem) / pixelsPerSecond;
// Show Windows shell toggle when Windows is selected in one-liner mode
const showWinShellControls = showOsControls && currentOs === 'windows';
osIndicator.style.display = showOsControls && !osPickerExpanded ? 'flex' : 'none';
osSwitch.style.display = showOsControls && osPickerExpanded ? 'flex' : 'none';
winShellSwitch.style.display = showWinShellControls ? 'flex' : 'none';
pmSwitch.style.display = showPmControls ? 'flex' : 'none';
hackableSwitch.style.display = showHackableControls ? 'flex' : 'none';
betaSwitch.style.display = showBetaControls ? 'flex' : 'none';
if (osIndicator) osIndicator.style.display = showOsControls && !osPickerExpanded ? 'flex' : 'none';
if (osSwitch) osSwitch.style.display = showOsControls && osPickerExpanded ? 'flex' : 'none';
if (winShellSwitch) winShellSwitch.style.display = showWinShellControls ? 'flex' : 'none';
if (pmSwitch) pmSwitch.style.display = showPmControls ? 'flex' : 'none';
if (hackableSwitch) hackableSwitch.style.display = showHackableControls ? 'flex' : 'none';
if (betaSwitch) betaSwitch.style.display = showBetaControls ? 'flex' : 'none';
// Show placeholder when no switches visible (macOS mode) to prevent layout shift
const noSwitchesVisible = !showOsControls && !showPmControls && !showHackableControls && !showBetaControls;
switchPlaceholder.style.display = noSwitchesVisible ? 'block' : 'none';
if (switchPlaceholder) switchPlaceholder.style.display = noSwitchesVisible ? 'block' : 'none';
}
// OS change toggle
osChangeBtn.addEventListener('click', () => {
osPickerExpanded = true;
updateVisibility();
});
// OS change toggle (null-safe)
if (osChangeBtn) {
osChangeBtn.addEventListener('click', () => {
osPickerExpanded = true;
updateVisibility();
});
}
// Beta toggle
betaBtn.addEventListener('click', () => {
currentBeta = !currentBeta;
updateCommands();
});
// Beta toggle (null-safe)
if (betaBtn) {
betaBtn.addEventListener('click', () => {
currentBeta = !currentBeta;
updateCommands();
});
}
pmBtns.forEach(btn => {
btn.addEventListener('click', () => {
@ -523,57 +530,91 @@ const duration2 = (row2.length / 2 * pixelsPerItem) / pixelsPerSecond;
document.querySelectorAll('.copy-line-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const cmdKey = btn.dataset.cmd;
const code = copyCommands[cmdKey]();
const cmdFn = copyCommands[cmdKey];
if (!cmdFn) return;
const code = cmdFn();
const copyIcon = btn.querySelector('.copy-icon');
const checkIcon = btn.querySelector('.check-icon');
// Try modern clipboard API first, fallback to execCommand
let success = false;
try {
await navigator.clipboard.writeText(code);
btn.classList.add('copied');
copyIcon.style.display = 'none';
checkIcon.style.display = 'block';
setTimeout(() => {
btn.classList.remove('copied');
copyIcon.style.display = 'block';
checkIcon.style.display = 'none';
}, 2000);
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(code);
success = true;
} else {
// Fallback for older browsers or non-HTTPS
const textArea = document.createElement('textarea');
textArea.value = code;
textArea.style.position = 'fixed';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
try {
textArea.select();
success = document.execCommand('copy');
} finally {
textArea.remove();
}
}
} catch (err) {
console.error('Failed to copy:', err);
success = false;
}
if (success) {
btn.classList.add('copied');
if (copyIcon) copyIcon.style.display = 'none';
if (checkIcon) checkIcon.style.display = 'block';
setTimeout(() => {
btn.classList.remove('copied');
if (copyIcon) copyIcon.style.display = 'block';
if (checkIcon) checkIcon.style.display = 'none';
}, 2000);
} else {
// Visual error feedback - brief red flash
btn.style.background = 'rgba(239, 68, 68, 0.3)';
setTimeout(() => {
btn.style.background = '';
}, 1000);
}
});
});
// Easter egg: Dalek mode on lobster hover
// Easter egg: Dalek mode on lobster hover (null-safe)
const lobsterIcon = document.querySelector('.lobster-icon');
const tagline = document.getElementById('tagline');
const originalTagline = tagline.textContent;
let isAnimating = false;
if (lobsterIcon && tagline) {
const originalTagline = tagline.textContent;
let isAnimating = false;
lobsterIcon.addEventListener('mouseenter', () => {
if (isAnimating) return;
isAnimating = true;
lobsterIcon.addEventListener('mouseenter', () => {
if (isAnimating) return;
isAnimating = true;
// Animate to Dalek text
tagline.style.transition = 'opacity 0.2s ease';
tagline.style.opacity = '0';
setTimeout(() => {
tagline.textContent = 'EXFOLIATE! EXFOLIATE!';
tagline.classList.add('dalek-mode');
tagline.style.opacity = '1';
}, 200);
// Revert after 2 seconds
setTimeout(() => {
// Animate to Dalek text
tagline.style.transition = 'opacity 0.2s ease';
tagline.style.opacity = '0';
setTimeout(() => {
tagline.textContent = originalTagline;
tagline.classList.remove('dalek-mode');
tagline.textContent = 'EXFOLIATE! EXFOLIATE!';
tagline.classList.add('dalek-mode');
tagline.style.opacity = '1';
isAnimating = false;
}, 200);
}, 2000);
});
// Revert after 2 seconds
setTimeout(() => {
tagline.style.opacity = '0';
setTimeout(() => {
tagline.textContent = originalTagline;
tagline.classList.remove('dalek-mode');
tagline.style.opacity = '1';
isAnimating = false;
}, 200);
}, 2000);
});
}
</script>
<!-- What It Does -->