# JS Modularization Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Split the 6913-line `JS/bundle.js` into ~17 feature-based ES modules bundled with esbuild. Zero functional changes.

**Architecture:** Each logical section of the current IIFE becomes its own ES module under `src/`. A thin reactive store replaces closure globals. esbuild compiles `src/main.js` → `JS/bundle.js` as a single IIFE, identical in behavior to the current output.

**Tech Stack:** esbuild (bundler), ES2020 modules (source), IIFE (output)

---

### Task 1: Set Up Build System

**Files:**
- Create: `package.json`
- Create: `build.js`
- Create: `src/main.js` (minimal placeholder)
- Modify: `.gitignore`

**Step 1: Create package.json**

```json
{
  "name": "pulsejt",
  "version": "3.5.0",
  "private": true,
  "scripts": {
    "build": "node build.js",
    "watch": "node build.js --watch"
  },
  "devDependencies": {
    "esbuild": "^0.24.0"
  }
}
```

**Step 2: Create build.js**

```js
const esbuild = require('esbuild');
const watch = process.argv.includes('--watch');

const config = {
  entryPoints: ['src/main.js'],
  bundle: true,
  outfile: 'JS/bundle.js',
  format: 'iife',
  target: 'es2015',
  sourcemap: 'inline',
  logLevel: 'info',
};

if (watch) {
  esbuild.context(config).then(ctx => {
    ctx.watch();
    console.log('Watching for changes...');
  });
} else {
  esbuild.build(config);
}
```

**Step 3: Create minimal src/main.js placeholder**

```js
// PulseJT v3.5 — Modular Source
// This file will grow as modules are extracted from the original bundle.
console.log('PulseJT modular build loaded');
```

**Step 4: Install and verify build**

Run: `npm install`
Run: `npm run build`
Expected: `JS/bundle.js` is generated (small placeholder), no errors.

**Step 5: Backup the original bundle**

Run: `cp JS/bundle.js JS/bundle.original.js`

This preserves the original for diffing and rollback. Do NOT commit this file.

**Step 6: Commit**

```
git add package.json build.js src/main.js .gitignore
git commit -m "chore: add esbuild build system for JS modularization"
```

---

### Task 2: Extract Foundation Modules (store, storage-keys, logger, utils)

These four modules have zero inter-dependencies and form the foundation.

**Files:**
- Create: `src/store.js`
- Create: `src/storage-keys.js`
- Create: `src/logger.js`
- Create: `src/utils.js`

**Step 1: Create `src/store.js`**

Reactive state store. Replaces closure globals (panX, panY, scale, db, currentBlobUrl, etc.).

```js
// Reactive state store — observable key-value pairs
var listeners = new Map();
var state = {
  // IndexedDB connection
  db: null,
  // Pan/zoom
  panX: parseFloat(localStorage.getItem('gifPreview.panX') || '0'),
  panY: parseFloat(localStorage.getItem('gifPreview.panY') || '0'),
  scale: parseFloat(localStorage.getItem('gifPreview.scale') || '1'),
  // Drag state
  isDragging: false,
  hasMoved: false,
  // Media
  currentBlobUrl: null,
  currentVideoFile: null,
  conversionAttempted: false,
  // History
  historyLoaded: false,
  historyObserver: null,
  // License
  isLicenseValid: false,
  licenseData: null,
  cachedHardwareId: null,
  // DOM references
  dom: {
    app: null,
    drop: null,
    img: null,
    file: null,
    btnPlus: null,
    btnFit: null,
    btnHome: null,
    btnMinus: null,
    btnSettings: null
  }
};

export function get(key) {
  return state[key];
}

export function set(key, value) {
  if (state[key] === value) return;
  state[key] = value;
  var fns = listeners.get(key);
  if (fns) {
    fns.forEach(function(fn) { fn(value); });
  }
}

export function on(key, fn) {
  if (!listeners.has(key)) listeners.set(key, []);
  listeners.get(key).push(fn);
  return function unsubscribe() {
    var fns = listeners.get(key);
    if (fns) {
      var idx = fns.indexOf(fn);
      if (idx !== -1) fns.splice(idx, 1);
    }
  };
}

// Direct access to dom sub-object (frequently used)
export function getDom() {
  return state.dom;
}

export function initDOM() {
  state.dom.app = document.getElementById('app');
  state.dom.drop = document.getElementById('drop');
  state.dom.img = document.getElementById('gif');
  state.dom.file = document.getElementById('file');
  state.dom.btnPlus = document.getElementById('btnPlus');
  state.dom.btnFit = document.getElementById('btnFit');
  state.dom.btnHome = document.getElementById('btnHome');
  state.dom.btnMinus = document.getElementById('btnMinus');
  state.dom.btnSettings = document.getElementById('btnSettings');
}
```

**Step 2: Create `src/storage-keys.js`**

Extract the STORAGE_KEYS object verbatim from bundle.js lines 1815-1849.

```js
export var STORAGE_KEYS = {
  LAST_MEDIA_PATH: 'gifPreview.lastMediaPath',
  LAST_MEDIA_THUMB: 'gifPreview.lastMediaThumb',
  LAST_MEDIA_DATA: 'gifPreview.lastMediaData',
  LAST_MEDIA_TYPE: 'gifPreview.lastMediaType',
  LAST_MEDIA_NAME: 'gifPreview.lastMediaName',
  FIT_MODE: 'gifPreview.fitMode',
  UI_HIDDEN: 'gifPreview.uiHidden',
  PAN_X: 'gifPreview.panX',
  PAN_Y: 'gifPreview.panY',
  SCALE: 'gifPreview.scale',
  THEME: 'gifPreview.theme',
  AUDIO_MUTED: 'gifPreview.audioMuted',
  VOLUME: 'gifPreview.volume',
  LAST_VERSION: 'gifPreview.lastSeenVersion',
  CORNERS_ENABLED: 'gifPreview.cornersEnabled',
  BEVEL_ENABLED: 'gifPreview.bevelEnabled',
  HISTORY_LIMIT: 'gifPreview.historyLimit',
  ERRORS_DISABLED: 'gifPreview.errorsDisabled',
  PERFORMANCE_MODE: 'gifPreview.performanceMode',
  PLAYBACK_SPEED: 'gifPreview.playbackSpeed',
  LOOP_ENABLED: 'gifPreview.loopEnabled',
  AUTOPLAY: 'gifPreview.autoplay',
  BG_TYPE: 'gifPreview.bgType',
  BG_COLOR: 'gifPreview.bgColor',
  BG_CHECKER_SIZE: 'gifPreview.bgCheckerSize',
  BG_CHECKER_VARIANT: 'gifPreview.bgCheckerVariant',
  BG_IMAGE: 'gifPreview.bgImage',
  BG_IMAGE_FIT: 'gifPreview.bgImageFit',
  BG_IMAGE_OPACITY: 'gifPreview.bgImageOpacity',
  TIMELINE_ENABLED: 'gifPreview.timelineEnabled',
  SHORTCUTS_ENABLED: 'gifPreview.shortcutsEnabled',
  BG_DIM: 'gifPreview.bgDim',
  BG_BLUR: 'gifPreview.bgBlur',
  BG_THEME_VARIANT: 'gifPreview.bgThemeVariant'
};
```

**Step 3: Create `src/logger.js`**

Extract the Enhanced Logger System from bundle.js lines 568-1008. Key changes:
- Export `showToast` (used by many modules), `exportLogs`, `copyLogsToClipboard`, `clearLogs`, `getLogCount`, `getErrorCount`, `getWarningCount`, `getFormattedLogs`, `getSystemInfo`, `getStateSnapshot`, `logDiagnostic`, `LOG_CATEGORIES`.
- Import `STORAGE_KEYS` from storage-keys.js (used in `getStateSnapshot`).
- Import `get` from store.js (for reading state in getStateSnapshot instead of closure vars).
- Keep `window.PulseJTLogger` assignment for backward compat.
- Keep `CURRENT_VERSION` and `VERSION_HISTORY` in this module (used by logger and modals).

**Step 4: Create `src/utils.js`**

Extract utility functions from bundle.js lines 1010-1064. These are pure functions with no dependencies.

```js
export function debounce(func, wait) {
  wait = wait || 300;
  var timeout;
  return function() {
    var context = this;
    var args = arguments;
    clearTimeout(timeout);
    timeout = setTimeout(function() {
      func.apply(context, args);
    }, wait);
  };
}

export function throttle(func, limit) {
  limit = limit || 100;
  var inThrottle;
  return function() {
    var context = this;
    var args = arguments;
    if (!inThrottle) {
      func.apply(context, args);
      inThrottle = true;
      setTimeout(function() { inThrottle = false; }, limit);
    }
  };
}

export function formatFileSize(bytes, decimals) {
  decimals = decimals || 2;
  if (bytes === 0) return '0 Bytes';
  if (!bytes || isNaN(bytes)) return 'Unknown';
  var k = 1024;
  var sizes = ['Bytes', 'KB', 'MB', 'GB'];
  var i = Math.floor(Math.log(bytes) / Math.log(k));
  return (bytes / Math.pow(k, i)).toFixed(decimals) + ' ' + sizes[i];
}

export function formatRelativeTime(timestamp) {
  var now = Date.now();
  var diff = now - timestamp;
  var seconds = Math.floor(diff / 1000);
  var minutes = Math.floor(seconds / 60);
  var hours = Math.floor(minutes / 60);
  var days = Math.floor(hours / 24);
  if (seconds < 10) return 'Just now';
  if (seconds < 60) return seconds + 's ago';
  if (minutes < 60) return minutes + 'm ago';
  if (hours < 24) return hours + 'h ago';
  return days + 'd ago';
}
```

**Step 5: Update src/main.js to import all four modules**

```js
import { get, set, initDOM } from './store.js';
import { STORAGE_KEYS } from './storage-keys.js';
import { showToast, CURRENT_VERSION } from './logger.js';
import { debounce, throttle } from './utils.js';

// Verify imports work
console.log('PulseJT v' + CURRENT_VERSION + ' modular build');
```

**Step 6: Build and verify**

Run: `npm run build`
Expected: Builds without errors. `JS/bundle.js` contains an IIFE wrapping all four modules.

**Step 7: Commit**

```
git add src/store.js src/storage-keys.js src/logger.js src/utils.js src/main.js
git commit -m "feat: extract foundation modules (store, storage-keys, logger, utils)"
```

---

### Task 3: Extract Audio System

**Files:**
- Create: `src/audio/bus.js`
- Create: `src/audio/state.js`
- Create: `src/audio/waveform.js`
- Create: `src/audio/unified-bar.js`
- Create: `src/audio/dock-control.js`
- Create: `src/audio/settings-volume.js`

**Step 1: Create `src/audio/bus.js`**

Extract AudioBus from bundle.js lines 1150-1170. Verbatim copy, add export.

```js
// Lightweight pub/sub event emitter for audio events
var listeners = {};

export var AudioBus = {
  on: function(event, callback) {
    if (!listeners[event]) listeners[event] = [];
    listeners[event].push(callback);
  },
  off: function(event, callback) {
    if (!listeners[event]) return;
    listeners[event] = listeners[event].filter(function(cb) { return cb !== callback; });
  },
  emit: function(event, data) {
    if (!listeners[event]) return;
    var cbs = listeners[event].slice();
    for (var i = 0; i < cbs.length; i++) {
      try { cbs[i](data || {}); } catch (e) { console.error('[AudioBus] Error in ' + event + ' handler:', e); }
    }
  }
};
```

**Step 2: Create `src/audio/state.js`**

Extract AudioState from bundle.js lines 1172-1361. Changes:
- `import { AudioBus } from './bus.js';`
- Export the AudioState object.
- Keep all internal logic identical.

**Step 3: Create `src/audio/waveform.js`**

Extract WaveformRenderer from bundle.js lines 1363-1485. Self-contained, just add export.

**Step 4: Create `src/audio/unified-bar.js`**

Extract UnifiedBar from bundle.js lines 1487-1649. Changes:
- `import { AudioBus } from './bus.js';`
- `import { AudioState } from './state.js';`
- `import { WaveformRenderer } from './waveform.js';`
- `import { STORAGE_KEYS } from '../storage-keys.js';`

**Step 5: Create `src/audio/dock-control.js`**

Extract DockAudioControl from bundle.js lines 1652-1746. Changes:
- `import { AudioBus } from './bus.js';`
- `import { AudioState } from './state.js';`

**Step 6: Create `src/audio/settings-volume.js`**

Extract initSettingsVolumeControls from bundle.js lines 1748-1809. Changes:
- `import { AudioBus } from './bus.js';`
- `import { AudioState } from './state.js';`

**Step 7: Update src/main.js to import audio modules and verify build**

Run: `npm run build`

**Step 8: Commit**

```
git add src/audio/
git commit -m "feat: extract audio system into modules (bus, state, waveform, unified-bar, dock-control)"
```

---

### Task 4: Extract Storage Module

**Files:**
- Create: `src/storage.js`

**Step 1: Create `src/storage.js`**

Extract from bundle.js lines 1852-2602. This combines:
- IndexedDB service (initIndexedDB, generateThumbnail, detectMediaType, saveMediaToDB, getAllMedia, getMediaById, deleteMediaById, clearAllMedia, enforceHistoryLimit, getStorageInfo)
- Blob storage (isSupportedVideoFormat, saveFullMediaToIndexedDB, getLastViewedMedia, updateLastViewed, enforceStorageQuota, cleanupBlobUrl)
- localStorage service (initStorage, saveMediaToStorage)

Changes:
- `import { get, set } from './store.js';`
- `import { STORAGE_KEYS } from './storage-keys.js';`
- Replace `db` global with `get('db')` / `set('db', value)`.
- Replace `currentBlobUrl` global with `get('currentBlobUrl')` / `set('currentBlobUrl', value)`.
- Export all public functions.

Constants stay in this module:
```js
var DB_NAME = 'GifPreviewDB';
var DB_VERSION = 4;
var STORE_NAME = 'mediaFiles';
var MAX_HISTORY_ITEMS = 50;
var MAX_BLOB_SIZE = 200 * 1024 * 1024;
var MAX_TOTAL_BLOB_STORAGE = 500 * 1024 * 1024;
```

**Step 2: Build and verify**

Run: `npm run build`

**Step 3: Commit**

```
git add src/storage.js
git commit -m "feat: extract storage module (IndexedDB, blob storage, localStorage)"
```

---

### Task 5: Extract Media Loading Module

**Files:**
- Create: `src/media.js`

**Step 1: Create `src/media.js`**

Extract from bundle.js lines 2603-3432. This includes:
- Pan/zoom constants (MIN_SCALE, MAX_SCALE, SCALE_STEP)
- Loading popup (showLoader, hideLoader, updateLoader)
- Transform functions (applyTransform, applyTransformImmediate)
- Video conversion (FFmpeg integration — fs, path, childProcess, os, getTempDir, getExtensionPath, getBundledFFmpegPath, checkFFmpeg, convertVideoToMP4, blobUrlToTempFile)
- Media loading (loadImage, loadVideo, loadFile, loadFileStandard, tryConvertAndLoad)

Changes:
- `import { get, set, getDom } from './store.js';`
- `import { STORAGE_KEYS } from './storage-keys.js';`
- `import { showToast } from './logger.js';`
- `import { AudioState } from './audio/state.js';`
- `import { saveMediaToStorage, cleanupBlobUrl } from './storage.js';`
- Replace `panX`, `panY`, `scale` globals with `get('panX')`, `set('panX', value)` etc.
- Replace `dom` with `getDom()`.
- Replace `currentVideoFile`, `conversionAttempted` with store get/set.
- Export: `loadImage`, `loadVideo`, `loadFile`, `showLoader`, `hideLoader`, `applyTransform`, `applyTransformImmediate`, `MIN_SCALE`, `MAX_SCALE`, `SCALE_STEP`.

**Step 2: Build and verify**

Run: `npm run build`

**Step 3: Commit**

```
git add src/media.js
git commit -m "feat: extract media loading module (load, transform, FFmpeg conversion)"
```

---

### Task 6: Extract History Module

**Files:**
- Create: `src/history.js`

**Step 1: Create `src/history.js`**

Extract from bundle.js lines 3433-3963. Includes:
- createPlaceholderThumbnail, isMediaInHistory, addToHistory
- loadFromHistory, truncateFilename, createHistoryItem
- toggleFavorite, updateHistoryItemUI, updateHistoryStats
- loadHistoryLazy, renderHistoryList, setupLazyLoadingForThumbnails, loadThumbnail

Changes:
- `import { get, set, getDom } from './store.js';`
- `import { STORAGE_KEYS } from './storage-keys.js';`
- `import { loadImage, loadVideo, showLoader, hideLoader } from './media.js';`
- `import { getAllMedia, getMediaById, deleteMediaById, saveMediaToDB, generateThumbnail, updateLastViewed, cleanupBlobUrl } from './storage.js';`
- Replace `db` with `get('db')`, `historyLoaded`/`historyObserver` with store.
- Export: `addToHistory`, `loadFromHistory`, `renderHistoryList`, `loadHistoryLazy`, `createPlaceholderThumbnail`.

**Step 2: Build and verify**

Run: `npm run build`

**Step 3: Commit**

```
git add src/history.js
git commit -m "feat: extract history module (CRUD, thumbnails, favorites, lazy loading)"
```

---

### Task 7: Extract Themes Module

**Files:**
- Create: `src/themes.js`

**Step 1: Create `src/themes.js`**

Extract from bundle.js lines 3965-4394. Includes:
- hexToRgb, PREMIUM_THEMES, isThemeUnlocked, isThemeLocked
- THEMES object (12 theme definitions)
- initThemes, initEnhancedThemeSelector, applyTheme, showNotification

Changes:
- `import { get, getDom } from './store.js';`
- Replace `isLicenseValid`/`licenseData` closures with `get('isLicenseValid')`, `get('licenseData')`.
- Export: `THEMES`, `applyTheme`, `initThemes`, `isThemeUnlocked`, `isThemeLocked`, `hexToRgb`, `PREMIUM_THEMES`.

**Step 2: Build and verify**

Run: `npm run build`

**Step 3: Commit**

```
git add src/themes.js
git commit -m "feat: extract themes module (12 themes, premium gating, CSS variable injection)"
```

---

### Task 8: Extract Controls Module

**Files:**
- Create: `src/controls.js`

**Step 1: Create `src/controls.js`**

Extract from bundle.js lines 4396-4632+. Includes:
- initControls (button handlers, ripple effects, keyboard shortcuts)
- initPanZoom (wheel zoom, mouse drag, momentum physics, touch support, double-click)
- initDragDrop (file drop handling)

Changes:
- `import { get, set, getDom } from './store.js';`
- `import { STORAGE_KEYS } from './storage-keys.js';`
- `import { loadFile, applyTransform, applyTransformImmediate, MIN_SCALE, MAX_SCALE, SCALE_STEP } from './media.js';`
- `import { AudioState } from './audio/state.js';`
- `import { UnifiedBar } from './audio/unified-bar.js';`
- `import { DockAudioControl } from './audio/dock-control.js';`
- `import { initSettingsVolumeControls } from './audio/settings-volume.js';`
- Replace all `panX`, `panY`, `scale`, `isDragging`, `hasMoved` with store get/set.
- Replace `dom` with `getDom()`.
- Export: `initControls`.

Note: `initSettingsModal` call stays in controls but will be extracted to `src/settings.js` in the next task. For now, keep it inline or forward-declare the import.

**Step 2: Build and verify**

Run: `npm run build`

**Step 3: Commit**

```
git add src/controls.js
git commit -m "feat: extract controls module (pan/zoom, keyboard, drag-drop, fit modes)"
```

---

### Task 9: Extract Settings Module

**Files:**
- Create: `src/settings.js`

**Step 1: Create `src/settings.js`**

Extract initSettingsModal and all background settings logic from bundle.js. This is the settings modal tab initialization including:
- General tab (corners, errors, performance mode)
- Playback tab (speed, loop, autoplay, timeline toggle)
- Background tab (color, checker, image, theme variants, dim/blur)
- History tab (calls loadHistoryLazy)

Changes:
- `import { get, set } from './store.js';`
- `import { STORAGE_KEYS } from './storage-keys.js';`
- `import { AudioState } from './audio/state.js';`
- `import { applyTheme, THEMES } from './themes.js';`
- `import { loadHistoryLazy } from './history.js';`
- Export: `initSettingsModal`.

**Step 2: Build and verify**

Run: `npm run build`

**Step 3: Commit**

```
git add src/settings.js
git commit -m "feat: extract settings module (modal tabs, background config)"
```

---

### Task 10: Extract License Module

**Files:**
- Create: `src/license.js`

**Step 1: Create `src/license.js`**

Extract from bundle.js lines 5675-6866. This is the largest and most sensitive section. Includes:
- LICENSE_API_URL, FALLBACK_URL, VIP_MEMBERS
- getHardwareId, generateRequestSignature
- verifyLicenseIntegrity, verifyLicense
- activateLicense, deactivateLicense, transferLicense
- checkStoredLicense, revalidateLicenseIfNeeded
- License modal UI (initLicenseModal, showLicenseModal, hideLicenseModal)
- updateLicenseStatusDisplay, initLicenseDeactivation
- startLicenseWatchdog, protectLicenseStorage, startRuntimeIntegrityCheck
- startApp function (the post-license init flow)

Changes:
- `import { get, set, getDom } from './store.js';`
- `import { STORAGE_KEYS } from './storage-keys.js';`
- `import { showToast } from './logger.js';`
- `import { initStorage } from './storage.js';`
- `import { AudioState } from './audio/state.js';`
- `import { initThemes, initEnhancedThemeSelector } from './themes.js';`
- `import { initControls } from './controls.js';`
- Replace `isLicenseValid`/`licenseData`/`cachedHardwareId` with store get/set.
- Export: `initLicenseModal`, `checkStoredLicense`, `revalidateLicenseIfNeeded`, `startLicenseWatchdog`, `protectLicenseStorage`, `startRuntimeIntegrityCheck`, `updateLicenseStatusDisplay`, `initLicenseDeactivation`, `showLicenseModal`, `hideLicenseModal`, `startApp`.

**Step 2: Build and verify**

Run: `npm run build`

**Step 3: Commit**

```
git add src/license.js
git commit -m "feat: extract license module (activation, verification, watchdog)"
```

---

### Task 11: Extract CEP Integration + Version/Modals

**Files:**
- Create: `src/cep.js`
- Create: `src/modals.js`

**Step 1: Create `src/cep.js`**

Extract from bundle.js lines 5614-5631. Small module:
- initCEP (applicationBeforeDeactivate listener for state persistence)

Changes:
- `import { get } from './store.js';`
- `import { STORAGE_KEYS } from './storage-keys.js';`
- Export: `initCEP`.

**Step 2: Create `src/modals.js`**

Extract version management (lines 29-566) and theme style injection (lines 5633-5673):
- CURRENT_VERSION, VERSION_HISTORY
- initModals (update modal with What's New)
- initLogButtons
- injectThemeStyles
- restoreState

Changes:
- `import { get, set, getDom } from './store.js';`
- `import { STORAGE_KEYS } from './storage-keys.js';`
- `import { loadImage, loadVideo } from './media.js';`
- `import { getLastViewedMedia } from './storage.js';`
- Export: `CURRENT_VERSION`, `VERSION_HISTORY`, `initModals`, `initLogButtons`, `injectThemeStyles`, `restoreState`.

**Step 3: Build and verify**

Run: `npm run build`

**Step 4: Commit**

```
git add src/cep.js src/modals.js
git commit -m "feat: extract CEP integration and version/modal modules"
```

---

### Task 12: Wire Up main.js Entry Point

**Files:**
- Modify: `src/main.js` (complete rewrite to orchestrate all modules)

**Step 1: Write the full entry point**

```js
// PulseJT v3.5 — Modular Entry Point
import { initDOM } from './store.js';
import { CURRENT_VERSION } from './modals.js';
import {
  initLicenseModal, checkStoredLicense, showLicenseModal, hideLicenseModal,
  startLicenseWatchdog, protectLicenseStorage, startApp
} from './license.js';

console.log('Starting PulseJT v' + CURRENT_VERSION + '...');

// Apply performance mode early
(function() {
  try {
    if (localStorage.getItem('gifPreview.performanceMode') === 'true') {
      document.body.classList.add('performance-mode');
    }
  } catch (e) {}
})();

// Hardware ID pre-cache
import { getHardwareId } from './license.js';
try { getHardwareId(); } catch (e) {}

function initApp() {
  initDOM();
  initLicenseModal();
  startLicenseWatchdog();
  protectLicenseStorage();

  if (checkStoredLicense()) {
    hideLicenseModal();
    startApp();
  } else {
    showLicenseModal();
  }
}

if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', initApp);
} else {
  initApp();
}
```

**Step 2: Build the final bundle**

Run: `npm run build`
Expected: `JS/bundle.js` is a single IIFE containing all modules.

**Step 3: Verify bundle size is reasonable**

Run: `wc -c JS/bundle.js` or `ls -la JS/bundle.js`
Expected: Should be similar size to original (within 10-20% — some reduction from removing dead code, some growth from module wrappers).

**Step 4: Commit**

```
git add src/main.js
git commit -m "feat: wire up main.js entry point — all modules connected"
```

---

### Task 13: Manual Smoke Test in After Effects

This is a non-code verification task.

**Step 1: Build the final bundle**

Run: `npm run build`

**Step 2: Copy extension to CEP folder and restart AE**

Test the following in After Effects:
- [ ] Panel loads without errors
- [ ] License modal shows (or skips if licensed)
- [ ] Theme selection works (all 12 themes)
- [ ] Drag-and-drop GIF loads and animates
- [ ] Drag-and-drop video loads with audio
- [ ] Waveform bar displays and is scrubbable
- [ ] Volume control works (dock slider + settings)
- [ ] Space bar play/pause works
- [ ] Pan/zoom with mouse wheel and drag
- [ ] Settings modal opens with all 5 tabs
- [ ] History shows previous items
- [ ] Fit mode cycles (fill/contain/stretch)
- [ ] Background settings apply (color, checker, theme variants)
- [ ] State persists across panel reopen

**Step 3: Check Chrome DevTools at localhost:8092**

- [ ] No JS errors in console
- [ ] Source maps work (can see individual src/ files)

**Step 4: If issues found, fix and re-test. Then commit.**

```
git add -A
git commit -m "fix: address smoke test issues from modularization"
```

---

### Task 14: Clean Up ChatGPT-isms

Now that the code is split into manageable files, do a cleanup pass across all modules.

**Step 1: Remove excessive logging**

In each src/ file, remove diagnostic console.log calls that exist only for debugging ChatGPT's output. Keep:
- Init messages (one per module)
- Error messages
- User-facing events (media loaded, theme changed)

Remove:
- Verbose state dumps (`console.log('[MEDIA] Image element state before:', {...})`)
- Redundant success messages (`console.log('✅ GIF diagnostic passed')`)
- Intermediate progress logs (`console.log('[MEDIA] Routing to loader:', {...})`)

**Step 2: Remove redundant null checks**

Look for patterns like:
```js
if (!dataUrl) { ... }
if (dataUrl) { ... }
// Second check is redundant after first
```

**Step 3: Remove dead code**

- `LAST_MEDIA_DATA` key in storage-keys (marked deprecated)
- Any functions that are defined but never called

**Step 4: Build and smoke test**

Run: `npm run build`
Verify panel still works.

**Step 5: Commit**

```
git add src/
git commit -m "chore: clean up verbose logging, redundant checks, and dead code"
```

---

### Task 15: Remove Original Bundle Backup

**Step 1: Delete the backup**

Run: `rm JS/bundle.original.js`

**Step 2: Final build**

Run: `npm run build`

**Step 3: Commit**

```
git add -A
git commit -m "chore: remove original bundle backup — modularization complete"
```
