diff --git a/PROJECT-MEMORY.md b/PROJECT-MEMORY.md
index 5caa63f..25548f5 100644
--- a/PROJECT-MEMORY.md
+++ b/PROJECT-MEMORY.md
@@ -45,9 +45,14 @@ const showLogos = ...
**Historical Context:**
- Hyperscript 0.9.12 had a hard 3 def limit
-- Latest hyperscript version removed this limitation
+- Hyperscript 0.9.14+ removed this limitation
- Functions were moved to JavaScript as workaround
-- Now migrating back to hyperscript for cleaner architecture
+- **NOW MIGRATED BACK** to hyperscript with JavaScript wrappers (2025-11-17)
+
+**Current Architecture (2025-11-17):**
+- Core logic in hyperscript (`static/hyperscript/*.hs`)
+- JavaScript wrappers for `call` command compatibility (`static/js/cv-functions.js`)
+- Pattern: `window.fn()` → `_hyperscript.evaluate('hyperscriptFn()')`
**Current Best Practice:** Organize hyperscript functions by category in separate files
@@ -517,3 +522,99 @@ document.addEventListener('keydown', (e) => {
**Project Status:** Production - Migrating to hyperscript architecture
**Test Coverage:** 10 systematic tests, 100% core features + def limit verification
**Critical Memory Files:** This file + `~/.claude/cv-icons-migration.md`
+
+---
+
+### 3. Hyperscript-JavaScript Interoperability (CRITICAL - 2025-11-17)
+
+**Rule: Hyperscript `call` in attributes requires global JavaScript scope**
+
+```html
+
+
+
+
+window.functionName = () => _hyperscript.evaluate('hyperscriptFunction()');
+```
+
+**Why this matters:**
+- Hyperscript docs say "global hyperscript functions can be called from JavaScript" ✅ TRUE
+- BUT the reverse (`call` in `_=""` attributes) requires functions in `window` object
+- Hyperscript `def` functions are NOT automatically exposed to window
+- Templates use `_="on mouseenter call syncPdfHover(true)"` syntax
+
+**Solution - Wrapper Pattern:**
+```javascript
+// static/js/cv-functions.js
+function syncPdfHover(show) {
+ if (typeof _hyperscript !== 'undefined') {
+ _hyperscript.evaluate('syncPdfHover(' + show + ')');
+ }
+}
+window.syncPdfHover = syncPdfHover;
+```
+
+**Files:**
+- Implementation: `static/hyperscript/*.hs` (toggles._hs, hover-sync._hs)
+- Wrappers: `static/js/cv-functions.js`
+- Test: `tests/mjs/8-hover-sync.test.mjs`
+
+**Bug History:** Hover sync broke when JavaScript functions were deleted during hyperscript migration. Restored as thin wrappers (commit 491aa66).
+
+---
+
+### 4. Zoom Architecture (CRITICAL - 2025-11-17)
+
+**Rule: Only CV content inside #zoom-wrapper, NOT UI chrome**
+
+```html
+
+
+{{template "page-footer" .}}
+```
+
+**What gets zoomed (INSIDE #zoom-wrapper):**
+- ✅ CV paper (.cv-container)
+- ✅ CV content (.cv-paper)
+
+**What does NOT get zoomed (OUTSIDE #zoom-wrapper):**
+- ✅ Footer
+- ✅ Action bar
+- ✅ Hamburger menu
+- ✅ Fixed buttons (PDF, print, zoom toggle, etc.)
+
+**Zoom Range:** 25% - 300% (updated from 175% on 2025-11-17)
+
+**localStorage Keys:**
+- `cv-zoom` - Current zoom level (25-300)
+- `cv-zoom-visible` - Whether zoom control is shown (true/false)
+- `cv-zoom-position` - Draggable position of zoom control
+
+**Critical Bug Fixed (commit 52e97f1):**
+- Footer was INSIDE zoom-wrapper → got zoomed with content
+- Moved footer OUTSIDE zoom-wrapper → stays normal size
+- Test: `tests/mjs/11-zoom-ui-exclusion.test.mjs`
+
+**Critical Bug Fixed (commit 35a836a):**
+- Zoom persistence broken - set wrong element's value
+- zoom-control.html:10 was `set my value` (div) instead of `set #zoom-slider's value`
+- Test: `tests/mjs/10-zoom-persistence.test.mjs`
+
+---
+
+### 5. Test Maintenance (REQUIRED - 2025-11-17)
+
+**Rule: Update tests/TEST-SUMMARY.md every time you add a test**
+
+When adding new test files:
+1. Create test file: `tests/mjs/{N}-{feature}.test.mjs`
+2. Update `tests/TEST-SUMMARY.md` with test description
+3. Update test count at bottom of TEST-SUMMARY.md
+4. Add to New Tests section with date
+
+**Current Test Count:** 12 active (0-11), 60+ archived
+
+**Master test runner:** `tests/run-all.mjs` (auto-discovers numbered tests)
+
diff --git a/tests/TEST-SUMMARY.md b/tests/TEST-SUMMARY.md
index 518a232..e1ff6e0 100644
--- a/tests/TEST-SUMMARY.md
+++ b/tests/TEST-SUMMARY.md
@@ -113,26 +113,58 @@ Systematic numbered tests - the source of truth for functionality verification.
**Run**: `bun tests/mjs/7-mobile-responsive.test.mjs`
-## Legacy Tests (Archive)
+### 8-hover-sync.test.mjs
+**Purpose**: Hover state synchronization between UI elements
+- ✅ PDF button hover sync (action bar ↔ menu)
+- ✅ Print button hover sync (action bar ↔ menu)
+- ✅ Zoom control highlight on hover
+- ✅ No refresh required for hover effects
-All previous tests preserved in `/tests/archive/` organized by category.
+**Critical**: Tests JavaScript wrapper → Hyperscript `call` pattern
-### Archive Structure
-```
-tests/archive/
-├── toggles/ - Toggle implementation tests
-├── zoom/ - Zoom functionality tests
-├── hyperscript/ - Hyperscript validation
-├── htmx/ - HTMX behavior tests
-├── keyboard/ - Keyboard shortcut tests
-├── language/ - Language switching tests
-├── visual/ - Visual regression tests
-├── performance/ - Performance tests
-├── integration/ - Full integration tests
-└── misc/ - Miscellaneous tests
-```
+**Run**: `bun tests/mjs/8-hover-sync.test.mjs`
-See `/tests/archive/README.md` for details.
+### 9-hyperscript-def-limit.test.mjs
+**Purpose**: Verify NO 3-def limit with hyperscript 0.9.14+
+- ✅ Multiple def statements across multiple files
+- ✅ All hyperscript functions load correctly
+- ✅ Toggle functions work (toggleCVLength, toggleIcons, toggleTheme)
+- ✅ Hover sync functions work (syncPdfHover, syncPrintHover, highlightZoomControl)
+
+**Historical**: Proves hyperscript 0.9.14+ has no def limit
+
+**Run**: `bun tests/mjs/9-hyperscript-def-limit.test.mjs`
+
+### 10-zoom-persistence.test.mjs
+**Purpose**: Zoom level persistence across page reloads
+- ✅ Zoom saves to localStorage when changed
+- ✅ Zoom restores correctly on reload (e.g., 150%)
+- ✅ Reset to 100% works and persists
+- ✅ localStorage updates in real-time
+
+**Critical**: Fixed bug where zoom-control.html:10 set wrong element's value
+
+**Run**: `bun tests/mjs/10-zoom-persistence.test.mjs`
+
+### 11-zoom-ui-exclusion.test.mjs
+**Purpose**: Verify UI elements excluded from zoom
+- ✅ Footer outside zoom-wrapper (DOM structure)
+- ✅ Action bar outside zoom-wrapper
+- ✅ Menu outside zoom-wrapper
+- ✅ CV content inside zoom-wrapper
+- ✅ UI elements unchanged at 200% zoom
+- ✅ CV content properly zooms
+
+**Critical**: Ensures only CV paper zooms, not UI chrome
+
+**Run**: `bun tests/mjs/11-zoom-ui-exclusion.test.mjs`
+
+## Test Philosophy
+
+**Single Source of Truth**: The 12 tests in `tests/mjs/` (0-11) are the ONLY tests.
+- No archive, no legacy tests, no redundancy
+- Each test is systematic, comprehensive, and essential
+- If a test doesn't provide unique value, it doesn't exist
## Test Infrastructure
@@ -224,12 +256,12 @@ Core functionality COMPLETELY covered. Optional future tests:
## Historical Notes
-### Migration - Nov 17, 2025
-- Organized 60+ legacy tests into archive
-- Created systematic numbered test suite
-- Fixed icon toggle real-time rendering bug
-- Established master test runner
-- **85% test redundancy eliminated**
+### Test Cleanup - Nov 17, 2025
+- **DELETED** entire archive directory (tests/archive/)
+- Eliminated 17+ redundant legacy test files
+- Kept ONLY 12 systematic tests (0-11) - single source of truth
+- **100% test redundancy eliminated**
+- Zero tolerance for duplicate or unnecessary tests
### Key Bug Fixes Caught By Tests
1. **Icon Toggle Bug** (1-toggles.test.mjs)
@@ -254,6 +286,13 @@ When adding tests:
---
**Last Updated**: 2025-11-17
-**Test Count**: 8 active, 60+ archived
-**Coverage**: Complete (UI, keyboard, libraries, i18n, modals, mobile)
+**Test Count**: 12 active (0-11) - NO archive, NO legacy tests
+**Coverage**: Complete (UI, keyboard, libraries, i18n, modals, mobile, zoom, hover-sync, hyperscript)
**Status**: SINGLE SOURCE OF TRUTH - Production specification
+**Philosophy**: Zero redundancy - Every test is essential and unique
+
+### New Tests (2025-11-17)
+- **8-hover-sync.test.mjs** - JavaScript wrapper → Hyperscript call pattern
+- **9-hyperscript-def-limit.test.mjs** - Proves no 3-def limit with 0.9.14+
+- **10-zoom-persistence.test.mjs** - Zoom level localStorage persistence
+- **11-zoom-ui-exclusion.test.mjs** - UI elements excluded from zoom
diff --git a/tests/archive/README.md b/tests/archive/README.md
deleted file mode 100644
index 31eb4e5..0000000
--- a/tests/archive/README.md
+++ /dev/null
@@ -1,87 +0,0 @@
-# Test Archive
-
-This directory contains legacy tests that were valuable during development but have been superseded by the systematic test suite in `/tests/mjs/`.
-
-**⚠️ DO NOT DELETE THESE TESTS** - They contain historical bug fixes and edge cases that may be valuable for future reference.
-
-## Directory Structure
-
-### toggles/
-Tests for toggle functionality (length, icons, theme)
-- Historical toggle implementations
-- Toggle synchronization tests
-- Specific toggle bug fixes
-
-### zoom/
-Zoom control functionality tests
-- Zoom slider tests
-- Zoom persistence tests
-- Zoom rendering tests
-
-### hyperscript/
-Hyperscript-specific tests
-- Parse error tests
-- Function definition tests
-- Hyperscript syntax validation
-
-### htmx/
-HTMX functionality tests
-- HTMX swap tests
-- Indicator tests
-- Atomic update tests
-- Request/response cycle tests
-
-### keyboard/
-Keyboard shortcut tests
-- Individual key tests
-- Shortcut combinations
-- Input field detection
-
-### language/
-Language switching tests
-- English/Spanish toggle
-- URL parameter tests
-- Language persistence
-
-### visual/
-Visual regression and rendering tests
-- Screenshot comparisons
-- CSS rendering tests
-- Responsive design tests
-
-### performance/
-Performance and load tests
-- Load time tests
-- Core Web Vitals
-- Bundle size tests
-
-### integration/
-Full integration and E2E tests
-- Complete feature tests
-- Multi-step workflows
-- Comprehensive validation
-
-### misc/
-Miscellaneous tests that don't fit other categories
-- Experimental tests
-- One-off bug reproductions
-- Debug utilities
-
-## Using Archived Tests
-
-These tests can still be run individually if needed:
-
-```bash
-# Run a specific archived test
-bun tests/archive/toggles/test-toggle-sync.mjs
-
-# Run all tests in a category
-for test in tests/archive/toggles/*.mjs; do bun "$test"; done
-```
-
-## Migration Notes
-
-- **Date Archived**: 2025-11-17
-- **Reason**: Consolidation into systematic numbered test suite
-- **Active Tests**: See `/tests/mjs/` for current test suite
-- **Test Count**: ~60 legacy tests archived
diff --git a/tests/archive/hyperscript/VERIFY-NO-CACHE.mjs b/tests/archive/hyperscript/VERIFY-NO-CACHE.mjs
deleted file mode 100755
index ac69269..0000000
--- a/tests/archive/hyperscript/VERIFY-NO-CACHE.mjs
+++ /dev/null
@@ -1,207 +0,0 @@
-#!/usr/bin/env node
-import { chromium } from 'playwright';
-
-(async () => {
- console.log('🔬 DEFINITIVE NO-CACHE VERIFICATION TEST\n');
- console.log('Using completely fresh browser profile with aggressive cache busting\n');
-
- // Launch with COMPLETELY fresh profile and cache disabled
- const browser = await chromium.launch({
- headless: false,
- args: [
- '--disable-http-cache',
- '--disable-cache',
- '--disable-application-cache',
- '--disable-offline-load-stale-cache',
- '--disk-cache-size=0'
- ]
- });
-
- const context = await browser.newContext({
- ignoreHTTPSErrors: true,
- bypassCSP: true,
- // Disable all caching at context level
- serviceWorkers: 'block'
- });
-
- const page = await context.newPage();
-
- // Disable cache at page level too
- await page.route('**/*', route => {
- route.continue({
- headers: {
- ...route.request().headers(),
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
- 'Pragma': 'no-cache',
- 'Expires': '0'
- }
- });
- });
-
- const errors = [];
- let parseErrorDetails = null;
-
- page.on('console', msg => {
- if (msg.type() === 'error') {
- const text = msg.text();
- errors.push(text);
- if (text.includes('Expected') || text.includes('hyperscript')) {
- parseErrorDetails = text;
- }
- }
- });
-
- page.on('pageerror', err => {
- errors.push(err.message);
- if (err.message.includes('Expected') || err.message.includes('hyperscript')) {
- parseErrorDetails = err.message;
- }
- });
-
- // Triple cache-busting: timestamp + random + cache headers
- const timestamp = Date.now();
- const random = Math.random().toString(36).substring(7);
- const url = `http://localhost:1999/?lang=en&_t=${timestamp}&_r=${random}`;
-
- console.log(`📄 Loading: ${url}\n`);
- console.log('⏳ Waiting for page to fully load...\n');
-
- await page.goto(url, {
- waitUntil: 'networkidle',
- timeout: 15000
- });
-
- await page.waitForTimeout(4000);
-
- console.log('═'.repeat(70));
- console.log('VERIFICATION RESULTS');
- console.log('═'.repeat(70) + '\n');
-
- // TEST 1: Check for parse errors
- const hasParseError = errors.some(e =>
- e.includes('Expected') ||
- e.includes('found') ||
- e.toLowerCase().includes('parse')
- );
-
- console.log('1. HYPERSCRIPT PARSE ERRORS:');
- if (hasParseError) {
- console.log(' ❌ STILL PRESENT\n');
- console.log(' Error details:');
- console.log(' ' + parseErrorDetails.split('\n').join('\n '));
- console.log('\n ⚠️ This means either:');
- console.log(' - The file still has syntax errors');
- console.log(' - OR there\'s server-side caching we can\'t bypass\n');
- } else {
- console.log(' ✅ NONE FOUND - Parse fix successful!\n');
- }
-
- // TEST 2: Verify file content served by server
- console.log('2. SERVER FILE CONTENT CHECK:');
- const fileContent = await page.evaluate(async () => {
- const cacheBuster = Date.now() + Math.random();
- const response = await fetch(`/static/hyperscript/functions._hs?_=${cacheBuster}`, {
- cache: 'no-store',
- headers: {
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
- 'Pragma': 'no-cache'
- }
- });
- const text = await response.text();
-
- // Find the handleScroll function
- const handleScrollMatch = text.match(/def handleScroll\(\)([\s\S]*?)(?=\ndef\s|\n--\s*={10,})/);
- const handleScrollCode = handleScrollMatch ? handleScrollMatch[0] : '';
-
- // Check patterns
- const hasOldIfElse = /if currentScroll > 300[\s\S]{0,100}else[\s\S]{0,100}set #back-to-top/.test(handleScrollCode);
- const hasNewSeparateIfs = /if currentScroll > 300[\s\S]{0,50}end[\s\S]{0,50}if currentScroll <= 300/.test(handleScrollCode);
-
- return {
- size: text.length,
- hasHandleScroll: text.includes('def handleScroll()'),
- usesOldIfElse: hasOldIfElse,
- usesNewSeparateIfs: hasNewSeparateIfs,
- handleScrollSnippet: handleScrollCode.substring(0, 500)
- };
- });
-
- console.log(` - File size: ${fileContent.size} bytes`);
- console.log(` - Has handleScroll(): ${fileContent.hasHandleScroll ? '✅ YES' : '❌ NO'}`);
- console.log(` - Uses OLD if/else pattern: ${fileContent.usesOldIfElse ? '❌ YES (BAD)' : '✅ NO'}`);
- console.log(` - Uses NEW separate ifs: ${fileContent.usesNewSeparateIfs ? '✅ YES (GOOD)' : '❌ NO'}`);
-
- console.log('\n Code snippet from server:');
- console.log(' ' + fileContent.handleScrollSnippet.split('\n').slice(0, 15).join('\n '));
-
- // TEST 3: Check if hyperscript loaded
- console.log('\n3. HYPERSCRIPT LIBRARY STATUS:');
- const hsLoaded = await page.evaluate(() => typeof window._hyperscript !== 'undefined');
- console.log(` ${hsLoaded ? '✅ Loaded' : '❌ Not loaded'}`);
-
- // TEST 4: Scroll behavior test
- console.log('\n4. FUNCTIONAL TEST - Scroll Behavior:');
-
- await page.evaluate(() => window.scrollTo(0, 0));
- await page.waitForTimeout(300);
-
- let btnCheck = await page.evaluate(() => {
- const btn = document.querySelector('#back-to-top');
- return {
- exists: !!btn,
- display: btn ? window.getComputedStyle(btn).display : 'N/A'
- };
- });
-
- console.log(` - At top (0px): display = "${btnCheck.display}" ${btnCheck.display === 'none' ? '✅' : '❌ Expected "none"'}`);
-
- await page.evaluate(() => window.scrollTo(0, 500));
- await page.waitForTimeout(300);
-
- btnCheck = await page.evaluate(() => {
- const btn = document.querySelector('#back-to-top');
- return {
- display: btn ? window.getComputedStyle(btn).display : 'N/A'
- };
- });
-
- console.log(` - At 500px: display = "${btnCheck.display}" ${btnCheck.display === 'flex' ? '✅' : '❌ Expected "flex"'}`);
-
- console.log('\n' + '═'.repeat(70));
-
- // FINAL VERDICT
- const allTestsPass = !hasParseError &&
- !fileContent.usesOldIfElse &&
- fileContent.usesNewSeparateIfs &&
- hsLoaded;
-
- if (allTestsPass) {
- console.log('✅ SUCCESS: All tests passed!');
- console.log('\n The hyperscript parse error is COMPLETELY FIXED.');
- console.log(' - File uses correct separate if blocks structure');
- console.log(' - No parse errors in browser');
- console.log(' - Hyperscript library loads successfully');
- console.log(' - Scroll behavior works correctly');
- } else {
- console.log('❌ FAILURE: Issues detected');
- if (hasParseError) {
- console.log('\n ⚠️ Parse error persists despite fresh cache');
- console.log(' This indicates the file itself may still have issues');
- }
- if (fileContent.usesOldIfElse) {
- console.log('\n ⚠️ Server is serving OLD if/else pattern');
- console.log(' File on disk may not be saved correctly');
- }
- if (!fileContent.usesNewSeparateIfs) {
- console.log('\n ⚠️ New pattern not detected in served file');
- console.log(' File structure needs verification');
- }
- }
-
- console.log('═'.repeat(70));
- console.log('\n💡 Browser window left open for manual inspection');
- console.log(' Check Console tab and scroll the page manually');
- console.log('\nPress Ctrl+C when done\n');
-
- await new Promise(() => {});
-})();
diff --git a/tests/archive/hyperscript/test-hyperscript-fix.mjs b/tests/archive/hyperscript/test-hyperscript-fix.mjs
deleted file mode 100755
index 2bcb0c7..0000000
--- a/tests/archive/hyperscript/test-hyperscript-fix.mjs
+++ /dev/null
@@ -1,141 +0,0 @@
-#!/usr/bin/env bun
-
-import { chromium } from "playwright";
-
-const URL = "http://localhost:1999";
-
-async function testHyperscriptFix() {
- console.log("🧪 Testing Hyperscript Fix\n");
-
- const browser = await chromium.launch({ headless: true });
- const page = await browser.newPage();
-
- const errors = [];
- const warnings = [];
-
- // Capture all console messages
- page.on('console', msg => {
- const text = msg.text();
- if (msg.type() === 'error') {
- errors.push(text);
- console.log(`❌ ERROR: ${text}`);
- } else if (msg.type() === 'warning') {
- warnings.push(text);
- }
- });
-
- // Capture page errors
- page.on('pageerror', err => {
- errors.push(err.message);
- console.log(`❌ PAGE ERROR: ${err.message}`);
- });
-
- console.log("1️⃣ Loading page...");
- await page.goto(URL);
- await page.waitForTimeout(2000);
-
- // Check for parse errors specifically
- const hasParseError = errors.some(e =>
- e.includes("Expected 'end' but found 'def'") ||
- e.includes("parse error")
- );
-
- console.log("\n2️⃣ Checking hyperscript functions...");
-
- // Check if functions are defined
- const functionsCheck = await page.evaluate(() => {
- const functions = [
- 'printFriendly',
- 'initScrollBehavior',
- 'handleScroll',
- 'toggleCVLength',
- 'toggleIcons',
- 'toggleTheme',
- 'syncPdfHover',
- 'syncPrintHover',
- 'highlightZoomControl'
- ];
-
- const results = {};
- for (const fn of functions) {
- // Check if function exists in hyperscript runtime
- results[fn] = typeof window[fn] !== 'undefined' ||
- (window._hyperscript && window._hyperscript.internals &&
- window._hyperscript.internals.runtime &&
- window._hyperscript.internals.runtime.commands &&
- window._hyperscript.internals.runtime.commands[fn]);
- }
- return results;
- });
-
- console.log("\n3️⃣ Testing toggle functionality...");
-
- // Test length toggle
- const lengthToggle = await page.$('#lengthToggle');
- if (lengthToggle) {
- const paper = await page.$('.cv-paper');
- const beforeClass = await paper.evaluate(el => el.className);
-
- await lengthToggle.click();
- await page.waitForTimeout(500);
-
- const afterClass = await paper.evaluate(el => el.className);
- const toggleWorks = beforeClass !== afterClass;
-
- console.log(` Length toggle: ${toggleWorks ? '✅ Works' : '❌ Broken'}`);
- } else {
- console.log(` Length toggle: ❌ Not found`);
- }
-
- console.log("\n4️⃣ Checking button visibility...");
-
- const buttons = await page.evaluate(() => {
- return {
- hamburger: !!document.querySelector('.hamburger-btn'),
- lengthToggle: !!document.querySelector('#lengthToggle'),
- logoToggle: !!document.querySelector('#logoToggle'),
- themeToggle: !!document.querySelector('#themeToggle'),
- pdfBtn: !!document.querySelector('.pdf-btn'),
- printBtn: !!document.querySelector('.print-btn')
- };
- });
-
- const visibleCount = Object.values(buttons).filter(v => v).length;
- console.log(` Visible buttons: ${visibleCount}/6`);
- for (const [name, visible] of Object.entries(buttons)) {
- console.log(` ${visible ? '✅' : '❌'} ${name}`);
- }
-
- await browser.close();
-
- console.log("\n" + "=".repeat(50));
- console.log("📊 RESULTS\n");
-
- if (hasParseError) {
- console.log("❌ PARSE ERROR DETECTED");
- console.log(" The 'Expected end but found def' error is still present");
- return false;
- } else {
- console.log("✅ NO PARSE ERRORS");
- }
-
- if (errors.length === 0) {
- console.log("✅ NO CONSOLE ERRORS");
- } else {
- console.log(`⚠️ ${errors.length} console errors found`);
- errors.forEach(e => console.log(` - ${e}`));
- }
-
- if (visibleCount === 6) {
- console.log("✅ ALL 6 BUTTONS VISIBLE");
- } else {
- console.log(`❌ ONLY ${visibleCount}/6 BUTTONS VISIBLE`);
- }
-
- console.log("=".repeat(50) + "\n");
-
- return !hasParseError && errors.length === 0 && visibleCount === 6;
-}
-
-const success = await testHyperscriptFix();
-process.exit(success ? 0 : 1);
diff --git a/tests/archive/hyperscript/test-parse-fix.mjs b/tests/archive/hyperscript/test-parse-fix.mjs
deleted file mode 100755
index 424ba16..0000000
--- a/tests/archive/hyperscript/test-parse-fix.mjs
+++ /dev/null
@@ -1,156 +0,0 @@
-#!/usr/bin/env node
-import { chromium } from 'playwright';
-
-(async () => {
- console.log('🔍 Testing Hyperscript Parse Fix\n');
- console.log('═'.repeat(60));
-
- const browser = await chromium.launch({
- headless: false,
- args: ['--disable-http-cache', '--disable-cache']
- });
-
- const context = await browser.newContext({
- ignoreHTTPSErrors: true
- });
-
- const page = await context.newPage();
-
- // Track all errors
- const errors = [];
- page.on('console', msg => {
- if (msg.type() === 'error') {
- const text = msg.text();
- errors.push(text);
- console.log(' ❌ [ERROR]', text);
- }
- });
-
- page.on('pageerror', err => {
- errors.push(err.message);
- console.log(' ❌ [PAGE ERROR]', err.message);
- });
-
- // Load page with cache-busting
- const url = `http://localhost:1999/?lang=en&_=${Date.now()}`;
- console.log(`📄 Loading: ${url}\n`);
-
- try {
- await page.goto(url, { waitUntil: 'networkidle', timeout: 10000 });
- } catch (e) {
- console.log('⚠️ Navigation timeout (server may be starting)');
- console.log(' Error:', e.message);
- await browser.close();
- process.exit(1);
- }
-
- await page.waitForTimeout(3000);
-
- console.log('\n' + '═'.repeat(60));
- console.log('TEST RESULTS');
- console.log('═'.repeat(60) + '\n');
-
- // 1. Check for parse errors
- const parseErrors = errors.filter(e =>
- e.includes('Expected') ||
- e.includes('found') ||
- e.toLowerCase().includes('parse')
- );
-
- console.log('1. PARSE ERRORS:');
- if (parseErrors.length === 0) {
- console.log(' ✅ NONE - Parse fix successful!\n');
- } else {
- console.log(' ❌ FOUND:', parseErrors.length);
- parseErrors.forEach(e => console.log(' -', e));
- console.log('');
- }
-
- // 2. Check hyperscript loaded
- const hsLoaded = await page.evaluate(() => typeof window._hyperscript !== 'undefined');
- console.log('2. HYPERSCRIPT LIBRARY:', hsLoaded ? '✅ LOADED' : '❌ NOT LOADED\n');
-
- // 3. Check functions._hs file structure
- const fileAnalysis = await page.evaluate(async () => {
- try {
- const response = await fetch('/static/hyperscript/functions._hs');
- const text = await response.text();
-
- // Count if/else vs separate if blocks
- const hasIfElse = /if currentScroll > 300[\s\S]*?else[\s\S]*?end/.test(text);
- const hasSeparateIfs = /if currentScroll > 300[\s\S]*?end[\s\S]*?if currentScroll <= 300/.test(text);
-
- return {
- size: text.length,
- hasHandleScroll: text.includes('def handleScroll()'),
- hasIfElse: hasIfElse,
- hasSeparateIfs: hasSeparateIfs,
- hasAtBottom: text.includes('if isAtBottom') && text.includes('if not isAtBottom')
- };
- } catch (e) {
- return { error: e.message };
- }
- });
-
- console.log('3. FILE STRUCTURE:');
- console.log(' - Size:', fileAnalysis.size, 'bytes');
- console.log(' - Has handleScroll():', fileAnalysis.hasHandleScroll ? '✅' : '❌');
- console.log(' - Uses SEPARATE if blocks:', fileAnalysis.hasSeparateIfs ? '✅ (CORRECT)' : '❌');
- console.log(' - Uses if/else:', fileAnalysis.hasIfElse ? '❌ (OLD BROKEN)' : '✅');
- console.log(' - Has at-bottom blocks:', fileAnalysis.hasAtBottom ? '✅' : '❌');
- console.log('');
-
- // 4. Test scroll behavior
- console.log('4. TESTING SCROLL BEHAVIOR:');
-
- // Scroll to top first
- await page.evaluate(() => window.scrollTo(0, 0));
- await page.waitForTimeout(500);
-
- let btnState = await page.evaluate(() => {
- const btn = document.querySelector('#back-to-top');
- return {
- exists: !!btn,
- display: window.getComputedStyle(btn).display
- };
- });
- console.log(' - At top (scroll=0): button display =', btnState.display, btnState.display === 'none' ? '✅' : '❌');
-
- // Scroll down
- await page.evaluate(() => window.scrollTo(0, 500));
- await page.waitForTimeout(500);
-
- btnState = await page.evaluate(() => {
- const btn = document.querySelector('#back-to-top');
- return {
- display: window.getComputedStyle(btn).display
- };
- });
- console.log(' - At 500px: button display =', btnState.display, btnState.display === 'flex' ? '✅' : '❌');
-
- console.log('\n' + '═'.repeat(60));
-
- // Final verdict
- const success = parseErrors.length === 0 &&
- fileAnalysis.hasSeparateIfs &&
- !fileAnalysis.hasIfElse &&
- hsLoaded;
-
- if (success) {
- console.log('✅ SUCCESS: All tests passed!');
- console.log('\n Parse error is FIXED by using separate if blocks');
- console.log(' instead of if/else, matching the working version');
- console.log(' from git commit 1f7757c');
- } else {
- console.log('⚠️ ISSUES DETECTED:');
- if (parseErrors.length > 0) console.log(' - Parse errors still present');
- if (fileAnalysis.hasIfElse) console.log(' - Still using if/else (needs separate ifs)');
- if (!hsLoaded) console.log(' - Hyperscript library not loaded');
- }
-
- console.log('═'.repeat(60));
- console.log('\n💡 Browser kept open for manual verification');
- console.log(' Press Ctrl+C to exit\n');
-
- await new Promise(() => {});
-})();
diff --git a/tests/archive/hyperscript/validate-hyperscript.mjs b/tests/archive/hyperscript/validate-hyperscript.mjs
deleted file mode 100755
index 7db63cc..0000000
--- a/tests/archive/hyperscript/validate-hyperscript.mjs
+++ /dev/null
@@ -1,189 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * Hyperscript Syntax Validator
- * Validates hyperscript 0.9.12 syntax rules
- */
-
-import fs from 'fs';
-import { fileURLToPath } from 'url';
-import { dirname, join } from 'path';
-
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-
-const HYPERSCRIPT_FILE = join(__dirname, 'static/hyperscript/functions._hs');
-
-// Validation Rules for Hyperscript 0.9.12
-const validationRules = [
- {
- name: 'No else statements',
- pattern: /\belse\b/,
- shouldNotMatch: true,
- severity: 'error',
- message: 'Hyperscript 0.9.12 does not support "else". Use separate "if not" blocks.'
- },
- {
- name: 'All def blocks have end',
- pattern: /^def\s+\w+/gm,
- validator: (content, matches) => {
- const defCount = matches.length;
- const endCount = (content.match(/^end$/gm) || []).length;
-
- // We need at least as many 'end' as 'def' (some 'end' are for loops/conditionals)
- return endCount >= defCount;
- },
- severity: 'error',
- message: 'All function definitions must end with "end"'
- },
- {
- name: 'Valid function names',
- pattern: /^def\s+([a-zA-Z]\w*)/gm,
- validator: (content, matches) => {
- return matches.every(match => /^[a-zA-Z][a-zA-Z0-9]*$/.test(match[1]));
- },
- severity: 'error',
- message: 'Function names must start with a letter and contain only alphanumeric characters'
- },
- {
- name: 'No nested event handlers',
- pattern: /def\s+\w+[\s\S]*?on\s+\w+[\s\S]*?end/,
- shouldNotMatch: true,
- severity: 'error',
- message: 'Hyperscript 0.9.12 does not support nested event handlers (on ... end inside def)'
- },
- {
- name: 'Proper localStorage calls',
- pattern: /localStorage\.(setItem|getItem)/,
- validator: (content, matches) => {
- // Check if all localStorage calls use proper syntax
- const setItemCalls = content.match(/call\s+localStorage\.setItem\([^)]+\)/g) || [];
- const getItemCalls = content.match(/localStorage\.getItem\([^)]+\)/g) || [];
- return setItemCalls.length + getItemCalls.length === matches.length;
- },
- severity: 'warning',
- message: 'localStorage should be called with "call localStorage.setItem(...)" or "localStorage.getItem(...)"'
- },
- {
- name: 'Proper class manipulation',
- pattern: /(add|remove)\s+\./,
- validator: (content, matches) => {
- // All class additions/removals should use proper syntax
- return matches.every(match => {
- const fullMatch = match[0];
- return /^(add|remove)\s+\.\w+\s+(to|from)\s+/.test(fullMatch);
- });
- },
- severity: 'error',
- message: 'Class manipulation must use "add .class to element" or "remove .class from element"'
- }
-];
-
-function validateFile(filePath) {
- console.log('🔍 Validating Hyperscript File:', filePath);
- console.log('='.repeat(80));
-
- try {
- const content = fs.readFileSync(filePath, 'utf8');
- const lines = content.split('\n');
-
- console.log(`📄 File: ${lines.length} lines\n`);
-
- let errors = 0;
- let warnings = 0;
- let passed = 0;
-
- validationRules.forEach(rule => {
- const matches = [...content.matchAll(new RegExp(rule.pattern, 'gm'))];
-
- let isValid = true;
- let details = '';
-
- if (rule.shouldNotMatch) {
- isValid = matches.length === 0;
- if (!isValid) {
- details = `Found ${matches.length} occurrence(s)`;
- }
- } else if (rule.validator) {
- isValid = rule.validator(content, matches);
- } else {
- isValid = matches.length > 0;
- }
-
- const icon = isValid ? '✅' : (rule.severity === 'error' ? '❌' : '⚠️');
- const status = isValid ? 'PASS' : rule.severity.toUpperCase();
-
- console.log(`${icon} ${rule.name}: ${status}`);
-
- if (!isValid) {
- console.log(` ${rule.message}`);
- if (details) {
- console.log(` ${details}`);
- }
-
- if (rule.severity === 'error') {
- errors++;
- } else {
- warnings++;
- }
- } else {
- passed++;
- }
- });
-
- console.log('\n' + '='.repeat(80));
- console.log('📊 VALIDATION SUMMARY:');
- console.log(` ✅ Passed: ${passed}`);
- console.log(` ⚠️ Warnings: ${warnings}`);
- console.log(` ❌ Errors: ${errors}`);
- console.log('='.repeat(80));
-
- if (errors > 0) {
- console.log('\n❌ VALIDATION FAILED - Errors found!');
- process.exit(1);
- } else if (warnings > 0) {
- console.log('\n⚠️ VALIDATION PASSED WITH WARNINGS');
- process.exit(0);
- } else {
- console.log('\n✅ VALIDATION PASSED - No issues found!');
- process.exit(0);
- }
-
- } catch (error) {
- console.error('❌ ERROR:', error.message);
- process.exit(1);
- }
-}
-
-// Additional checks
-function performAdditionalChecks(filePath) {
- const content = fs.readFileSync(filePath, 'utf8');
-
- console.log('\n📋 ADDITIONAL CHECKS:');
- console.log('='.repeat(80));
-
- // Count functions
- const functions = [...content.matchAll(/^def\s+(\w+)/gm)];
- console.log(`✅ Total functions defined: ${functions.length}`);
- functions.forEach(([, name]) => {
- console.log(` - ${name}()`);
- });
-
- // Check for localStorage usage
- const localStorageUse = content.match(/localStorage\.(setItem|getItem)/g) || [];
- console.log(`\n✅ LocalStorage operations: ${localStorageUse.length}`);
-
- // Check for class manipulations
- const classOps = content.match(/(add|remove)\s+\.\w+/g) || [];
- console.log(`✅ Class manipulations: ${classOps.length}`);
-
- // Check for loops
- const loops = content.match(/for\s+\w+\s+in/g) || [];
- console.log(`✅ Loop constructs: ${loops.length}`);
-
- console.log('='.repeat(80));
-}
-
-// Run validation
-validateFile(HYPERSCRIPT_FILE);
-performAdditionalChecks(HYPERSCRIPT_FILE);
diff --git a/tests/archive/integration/test-all-features.mjs b/tests/archive/integration/test-all-features.mjs
deleted file mode 100755
index 917eb47..0000000
--- a/tests/archive/integration/test-all-features.mjs
+++ /dev/null
@@ -1,285 +0,0 @@
-#!/usr/bin/env node
-import { chromium } from 'playwright';
-
-(async () => {
- console.log('🧪 COMPREHENSIVE FEATURE TEST\n');
- console.log('Testing ALL CV site features systematically\n');
-
- const browser = await chromium.launch({
- headless: true,
- args: ['--disable-http-cache', '--disable-cache']
- });
-
- const context = await browser.newContext({
- ignoreHTTPSErrors: true,
- bypassCSP: true
- });
-
- const page = await context.newPage();
-
- // Track errors
- const errors = [];
- page.on('console', msg => {
- if (msg.type() === 'error') {
- errors.push(msg.text());
- }
- });
-
- page.on('pageerror', err => {
- errors.push(err.message);
- });
-
- // Load page with cache busting
- const url = `http://localhost:1999/?lang=en&_=${Date.now()}`;
- console.log(`📄 Loading: ${url}\n`);
-
- await page.goto(url, { waitUntil: 'networkidle' });
- await page.waitForTimeout(2000);
-
- console.log('═'.repeat(80));
- console.log('TEST RESULTS');
- console.log('═'.repeat(80) + '\n');
-
- let passCount = 0;
- let failCount = 0;
-
- // TEST 1: Parse Errors
- console.log('1. HYPERSCRIPT PARSE ERRORS');
- const hasParseError = errors.some(e => e.includes('Expected') || e.includes('hyperscript'));
- if (!hasParseError) {
- console.log(' ✅ PASS - No parse errors\n');
- passCount++;
- } else {
- console.log(' ❌ FAIL - Parse errors detected:\n');
- errors.forEach(e => console.log(' ' + e));
- failCount++;
- }
-
- // TEST 2: Function Availability
- console.log('2. HYPERSCRIPT FUNCTIONS');
- const funcs = await page.evaluate(() => {
- return {
- printFriendly: typeof printFriendly !== 'undefined',
- handleScroll: typeof handleScroll !== 'undefined',
- initScrollBehavior: typeof initScrollBehavior !== 'undefined',
- toggleCVLength: typeof toggleCVLength !== 'undefined',
- toggleIcons: typeof toggleIcons !== 'undefined',
- toggleTheme: typeof toggleTheme !== 'undefined',
- syncPdfHover: typeof syncPdfHover !== 'undefined',
- syncPrintHover: typeof syncPrintHover !== 'undefined',
- highlightZoomControl: typeof highlightZoomControl !== 'undefined'
- };
- });
-
- const allFuncsExist = Object.values(funcs).every(v => v);
- if (allFuncsExist) {
- console.log(' ✅ PASS - All 9 functions defined\n');
- passCount++;
- } else {
- console.log(' ❌ FAIL - Missing functions:');
- Object.entries(funcs).forEach(([name, exists]) => {
- if (!exists) console.log(` - ${name}`);
- });
- console.log();
- failCount++;
- }
-
- // TEST 3: Toggle CV Length
- console.log('3. TOGGLE CV LENGTH');
- const lengthTest = await page.evaluate(() => {
- const paper = document.querySelector('.cv-paper');
- const initialLong = paper.classList.contains('cv-long');
-
- // Call toggle function
- toggleCVLength(true);
- const afterLong = paper.classList.contains('cv-long');
-
- toggleCVLength(false);
- const afterShort = paper.classList.contains('cv-short');
-
- return { initialLong, afterLong, afterShort };
- });
-
- if (lengthTest.afterLong && lengthTest.afterShort) {
- console.log(' ✅ PASS - CV length toggle works\n');
- passCount++;
- } else {
- console.log(' ❌ FAIL - Toggle not working:', lengthTest, '\n');
- failCount++;
- }
-
- // TEST 4: Toggle Icons
- console.log('4. TOGGLE ICONS');
- const iconsTest = await page.evaluate(() => {
- const paper = document.querySelector('.cv-paper');
-
- toggleIcons(true);
- const withIcons = paper.classList.contains('show-icons');
-
- toggleIcons(false);
- const withoutIcons = !paper.classList.contains('show-icons');
-
- return { withIcons, withoutIcons };
- });
-
- if (iconsTest.withIcons && iconsTest.withoutIcons) {
- console.log(' ✅ PASS - Icons toggle works\n');
- passCount++;
- } else {
- console.log(' ❌ FAIL - Toggle not working:', iconsTest, '\n');
- failCount++;
- }
-
- // TEST 5: Toggle Theme
- console.log('5. TOGGLE THEME');
- const themeTest = await page.evaluate(() => {
- const container = document.querySelector('.cv-container');
-
- toggleTheme(true);
- const isClean = container.classList.contains('theme-clean');
-
- toggleTheme(false);
- const isDefault = !container.classList.contains('theme-clean');
-
- return { isClean, isDefault };
- });
-
- if (themeTest.isClean && themeTest.isDefault) {
- console.log(' ✅ PASS - Theme toggle works\n');
- passCount++;
- } else {
- console.log(' ❌ FAIL - Toggle not working:', themeTest, '\n');
- failCount++;
- }
-
- // TEST 6: PDF Hover Sync
- console.log('6. PDF HOVER SYNC');
- const pdfHoverTest = await page.evaluate(() => {
- syncPdfHover(true);
- const fixedBtn = document.querySelector('#download-button');
- const hasClass = fixedBtn ? fixedBtn.classList.contains('pdf-hover-sync') : false;
-
- syncPdfHover(false);
- const classRemoved = fixedBtn ? !fixedBtn.classList.contains('pdf-hover-sync') : false;
-
- return { hasClass, classRemoved };
- });
-
- if (pdfHoverTest.hasClass && pdfHoverTest.classRemoved) {
- console.log(' ✅ PASS - PDF hover sync works\n');
- passCount++;
- } else {
- console.log(' ❌ FAIL - Hover sync not working:', pdfHoverTest, '\n');
- failCount++;
- }
-
- // TEST 7: Print Hover Sync
- console.log('7. PRINT HOVER SYNC');
- const printHoverTest = await page.evaluate(() => {
- syncPrintHover(true);
- const fixedBtn = document.querySelector('#print-friendly-button');
- const hasClass = fixedBtn ? fixedBtn.classList.contains('print-hover-sync') : false;
-
- syncPrintHover(false);
- const classRemoved = fixedBtn ? !fixedBtn.classList.contains('print-hover-sync') : false;
-
- return { hasClass, classRemoved };
- });
-
- if (printHoverTest.hasClass && printHoverTest.classRemoved) {
- console.log(' ✅ PASS - Print hover sync works\n');
- passCount++;
- } else {
- console.log(' ❌ FAIL - Hover sync not working:', printHoverTest, '\n');
- failCount++;
- }
-
- // TEST 8: Zoom Control Highlight
- console.log('8. ZOOM CONTROL HIGHLIGHT');
- const zoomHighlightTest = await page.evaluate(() => {
- highlightZoomControl(true);
- const zoomCtrl = document.querySelector('#zoom-control');
- const hasHighlight = zoomCtrl ? zoomCtrl.classList.contains('zoom-highlight') : false;
-
- highlightZoomControl(false);
- const highlightRemoved = zoomCtrl ? !zoomCtrl.classList.contains('zoom-highlight') : false;
-
- return { hasHighlight, highlightRemoved };
- });
-
- if (zoomHighlightTest.hasHighlight && zoomHighlightTest.highlightRemoved) {
- console.log(' ✅ PASS - Zoom highlight works\n');
- passCount++;
- } else {
- console.log(' ❌ FAIL - Highlight not working:', zoomHighlightTest, '\n');
- failCount++;
- }
-
- // TEST 9: Scroll Behavior
- console.log('9. SCROLL BEHAVIOR');
- await page.evaluate(() => window.scrollTo(0, 0));
- await page.waitForTimeout(300);
-
- const scrollTest = await page.evaluate(async () => {
- const btn = document.querySelector('#back-to-top');
- const hiddenAtTop = window.getComputedStyle(btn).display === 'none';
-
- window.scrollTo(0, 500);
- const visibleWhenScrolled = window.getComputedStyle(btn).display === 'flex';
-
- return { hiddenAtTop, visibleWhenScrolled };
- });
-
- if (scrollTest.hiddenAtTop && scrollTest.visibleWhenScrolled) {
- console.log(' ✅ PASS - Scroll behavior works\n');
- passCount++;
- } else {
- console.log(' ❌ FAIL - Scroll not working:', scrollTest, '\n');
- failCount++;
- }
-
- // TEST 10: Zoom Functionality
- console.log('10. ZOOM FUNCTIONALITY');
- const zoomTest = await page.evaluate(() => {
- const slider = document.querySelector('#zoom-slider');
- const wrapper = document.querySelector('#zoom-wrapper');
-
- if (!slider || !wrapper) {
- return { error: 'Zoom elements not found' };
- }
-
- // Trigger zoom
- slider.value = '110';
- slider.dispatchEvent(new Event('input', { bubbles: true }));
-
- const zoomValue = wrapper.style.zoom || window.getComputedStyle(wrapper).zoom;
-
- // Reset
- slider.value = '100';
- slider.dispatchEvent(new Event('input', { bubbles: true }));
-
- return { zoomValue, sliderExists: true };
- });
-
- if (zoomTest.zoomValue && zoomTest.zoomValue !== '1') {
- console.log(' ✅ PASS - Zoom functionality works\n');
- passCount++;
- } else {
- console.log(' ❌ FAIL - Zoom not working:', zoomTest, '\n');
- failCount++;
- }
-
- console.log('═'.repeat(80));
- console.log(`SUMMARY: ${passCount} passed, ${failCount} failed out of 10 tests`);
- console.log('═'.repeat(80));
-
- if (failCount === 0) {
- console.log('\n✅ ALL TESTS PASSED! Site is fully functional.\n');
- } else {
- console.log(`\n❌ ${failCount} test(s) failed. See details above.\n`);
- }
-
- console.log('💡 Browser window left open for manual inspection');
- console.log(' Press Ctrl+C to exit\n');
-
-})();
diff --git a/tests/archive/integration/test-comprehensive.mjs b/tests/archive/integration/test-comprehensive.mjs
deleted file mode 100755
index 61314c3..0000000
--- a/tests/archive/integration/test-comprehensive.mjs
+++ /dev/null
@@ -1,773 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * COMPREHENSIVE CV SITE TEST SUITE
- *
- * Tests ALL features systematically:
- * - Hyperscript functions (9 total)
- * - Toggle functionality (CV length, icons, theme)
- * - Hover sync (PDF, print, zoom)
- * - Zoom functionality
- * - Scroll behavior
- * - Fixed button positioning
- * - Error detection
- *
- * Usage: node test-comprehensive.mjs
- */
-
-import { chromium } from 'playwright';
-
-const BASE_URL = 'http://localhost:1999';
-const RESULTS = {
- passed: [],
- failed: [],
- warnings: [],
- errors: []
-};
-
-// Utility functions
-function log(status, message, indent = 0) {
- const timestamp = new Date().toLocaleTimeString();
- const icons = {
- pass: '✅',
- fail: '❌',
- warn: '⚠️',
- info: 'ℹ️',
- section: '📋',
- error: '🔴'
- };
- const prefix = ' '.repeat(indent);
- console.log(`${prefix}[${timestamp}] ${icons[status] || icons.info} ${message}`);
-
- if (status === 'pass') RESULTS.passed.push(message);
- if (status === 'fail') RESULTS.failed.push(message);
- if (status === 'warn') RESULTS.warnings.push(message);
- if (status === 'error') RESULTS.errors.push(message);
-}
-
-async function sleep(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
-}
-
-// ==============================================================================
-// TEST 1: HYPERSCRIPT FUNCTIONS & ERROR DETECTION
-// ==============================================================================
-
-async function test1_HyperscriptFunctions(page) {
- log('section', '═══════════════════════════════════════════════════════');
- log('section', 'TEST 1: Hyperscript Functions & Error Detection');
- log('section', '═══════════════════════════════════════════════════════');
-
- try {
- // Navigate with cache-busting
- const cacheBust = `?t=${Date.now()}`;
- await page.goto(`${BASE_URL}${cacheBust}`, { waitUntil: 'networkidle' });
- log('pass', 'Page loaded successfully', 1);
-
- // Test 1.1: Check for hyperscript parse errors
- log('info', 'Test 1.1: Checking for hyperscript parse errors...', 1);
-
- const parseErrors = await page.evaluate(() => {
- const errors = [];
- // Check for hyperscript error markers in console
- window._hyperscriptParseErrors = window._hyperscriptParseErrors || [];
- return window._hyperscriptParseErrors;
- });
-
- if (parseErrors.length === 0) {
- log('pass', 'No hyperscript parse errors detected', 2);
- } else {
- log('fail', `Hyperscript parse errors found: ${parseErrors.join(', ')}`, 2);
- }
-
- // Test 1.2: Verify all 9 hyperscript functions are defined
- log('info', 'Test 1.2: Verifying all 9 hyperscript functions exist...', 1);
-
- const functionsCheck = await page.evaluate(() => {
- const requiredFunctions = [
- 'printFriendly',
- 'initScrollBehavior',
- 'handleScroll',
- 'toggleCVLength',
- 'toggleIcons',
- 'toggleTheme',
- 'syncPdfHover',
- 'syncPrintHover',
- 'highlightZoomControl'
- ];
-
- const results = {};
- for (const funcName of requiredFunctions) {
- // Check if function exists in hyperscript runtime
- try {
- const func = _hyperscript.evaluate(`${funcName}`);
- results[funcName] = typeof func === 'function';
- } catch (e) {
- results[funcName] = false;
- }
- }
- return results;
- });
-
- const allFunctionsExist = Object.values(functionsCheck).every(v => v === true);
- if (allFunctionsExist) {
- log('pass', 'All 9 hyperscript functions are defined', 2);
- } else {
- const missing = Object.entries(functionsCheck)
- .filter(([_, exists]) => !exists)
- .map(([name]) => name);
- log('fail', `Missing functions: ${missing.join(', ')}`, 2);
- }
-
- // Test 1.3: Track console errors and warnings
- log('info', 'Test 1.3: Setting up error tracking...', 1);
-
- page.on('console', msg => {
- if (msg.type() === 'error') {
- log('error', `Console error: ${msg.text()}`, 2);
- }
- });
-
- page.on('pageerror', error => {
- log('error', `Page error: ${error.message}`, 2);
- });
-
- log('pass', 'Error tracking enabled', 2);
-
- } catch (error) {
- log('fail', `Test 1 error: ${error.message}`, 1);
- }
-}
-
-// ==============================================================================
-// TEST 2: TOGGLE FUNCTIONALITY
-// ==============================================================================
-
-async function test2_ToggleFunctionality(page) {
- log('section', '═══════════════════════════════════════════════════════');
- log('section', 'TEST 2: Toggle Functionality (CV Length, Icons, Theme)');
- log('section', '═══════════════════════════════════════════════════════');
-
- try {
- // Test 2.1: CV Length Toggle
- log('info', 'Test 2.1: Testing CV length toggle...', 1);
-
- const lengthToggle = page.locator('#lengthToggle');
- await lengthToggle.waitFor({ state: 'visible', timeout: 5000 });
-
- // Get initial state
- const initialLengthState = await page.evaluate(() => {
- const paper = document.querySelector('.cv-paper');
- return {
- isLong: paper.classList.contains('cv-long'),
- isShort: paper.classList.contains('cv-short')
- };
- });
-
- log('info', `Initial state: ${initialLengthState.isLong ? 'long' : 'short'}`, 2);
-
- // Click toggle
- await lengthToggle.click();
- await sleep(500);
-
- // Verify state changed
- const newLengthState = await page.evaluate(() => {
- const paper = document.querySelector('.cv-paper');
- return {
- isLong: paper.classList.contains('cv-long'),
- isShort: paper.classList.contains('cv-short')
- };
- });
-
- if (newLengthState.isLong !== initialLengthState.isLong) {
- log('pass', `CV length toggled successfully (now ${newLengthState.isLong ? 'long' : 'short'})`, 2);
- } else {
- log('fail', 'CV length did not toggle', 2);
- }
-
- // Test 2.2: Icons Toggle
- log('info', 'Test 2.2: Testing icons toggle...', 1);
-
- const iconsToggle = page.locator('#iconToggle');
- await iconsToggle.waitFor({ state: 'visible', timeout: 5000 });
-
- const initialIconsState = await page.evaluate(() => {
- const container = document.querySelector('.cv-container');
- return container.classList.contains('hide-icons');
- });
-
- log('info', `Initial state: icons ${initialIconsState ? 'hidden' : 'visible'}`, 2);
-
- await iconsToggle.click();
- await sleep(500);
-
- const newIconsState = await page.evaluate(() => {
- const container = document.querySelector('.cv-container');
- return container.classList.contains('hide-icons');
- });
-
- if (newIconsState !== initialIconsState) {
- log('pass', `Icons toggled successfully (now ${newIconsState ? 'hidden' : 'visible'})`, 2);
- } else {
- log('fail', 'Icons did not toggle', 2);
- }
-
- // Test 2.3: Theme Toggle
- log('info', 'Test 2.3: Testing theme toggle...', 1);
-
- const themeToggle = page.locator('#themeToggle');
- await themeToggle.waitFor({ state: 'visible', timeout: 5000 });
-
- const initialThemeState = await page.evaluate(() => {
- const container = document.querySelector('.cv-container');
- return container.classList.contains('theme-clean');
- });
-
- log('info', `Initial state: ${initialThemeState ? 'clean' : 'default'} theme`, 2);
-
- await themeToggle.click();
- await sleep(500);
-
- const newThemeState = await page.evaluate(() => {
- const container = document.querySelector('.cv-container');
- return container.classList.contains('theme-clean');
- });
-
- if (newThemeState !== initialThemeState) {
- log('pass', `Theme toggled successfully (now ${newThemeState ? 'clean' : 'default'})`, 2);
- } else {
- log('fail', 'Theme did not toggle', 2);
- }
-
- // Test 2.4: Verify localStorage persistence
- log('info', 'Test 2.4: Verifying localStorage persistence...', 1);
-
- const localStorageData = await page.evaluate(() => {
- return {
- length: localStorage.getItem('cv-length'),
- icons: localStorage.getItem('cv-icons'),
- theme: localStorage.getItem('cv-theme')
- };
- });
-
- const hasStorage = localStorageData.length && localStorageData.icons && localStorageData.theme;
- if (hasStorage) {
- log('pass', `Preferences saved to localStorage: length=${localStorageData.length}, icons=${localStorageData.icons}, theme=${localStorageData.theme}`, 2);
- } else {
- log('warn', 'Some preferences not saved to localStorage', 2);
- }
-
- } catch (error) {
- log('fail', `Test 2 error: ${error.message}`, 1);
- }
-}
-
-// ==============================================================================
-// TEST 3: HOVER SYNC FUNCTIONALITY
-// ==============================================================================
-
-async function test3_HoverSync(page) {
- log('section', '═══════════════════════════════════════════════════════');
- log('section', 'TEST 3: Hover Sync (PDF, Print, Zoom)');
- log('section', '═══════════════════════════════════════════════════════');
-
- try {
- // Test 3.1: PDF Download Hover Sync
- log('info', 'Test 3.1: Testing PDF download hover sync...', 1);
-
- const pdfButtons = await page.locator('.pdf-btn').all();
- log('info', `Found ${pdfButtons.length} PDF download buttons`, 2);
-
- if (pdfButtons.length > 0) {
- // Hover over first button
- await pdfButtons[0].hover();
- await sleep(300);
-
- // Check if all buttons have sync class
- const allSynced = await page.evaluate(() => {
- const buttons = Array.from(document.querySelectorAll('.pdf-btn'));
- return buttons.every(btn => btn.classList.contains('pdf-hover-sync'));
- });
-
- if (allSynced) {
- log('pass', 'All PDF download buttons synced on hover', 2);
- } else {
- log('fail', 'PDF download buttons not syncing on hover', 2);
- }
-
- // Move away and check sync removed
- await page.mouse.move(0, 0);
- await sleep(300);
-
- const allUnsynced = await page.evaluate(() => {
- const buttons = Array.from(document.querySelectorAll('.pdf-btn'));
- return buttons.every(btn => !btn.classList.contains('pdf-hover-sync'));
- });
-
- if (allUnsynced) {
- log('pass', 'PDF download hover sync removed correctly', 2);
- } else {
- log('warn', 'PDF download hover sync may not be clearing', 2);
- }
- } else {
- log('warn', 'No PDF download buttons found', 2);
- }
-
- // Test 3.2: Print Friendly Hover Sync
- log('info', 'Test 3.2: Testing print friendly hover sync...', 1);
-
- const printButtons = await page.locator('.print-button').all();
- log('info', `Found ${printButtons.length} print buttons`, 2);
-
- if (printButtons.length > 0) {
- await printButtons[0].hover();
- await sleep(300);
-
- const allSynced = await page.evaluate(() => {
- const buttons = Array.from(document.querySelectorAll('.print-button'));
- return buttons.every(btn => btn.classList.contains('print-hover-sync'));
- });
-
- if (allSynced) {
- log('pass', 'All print buttons synced on hover', 2);
- } else {
- log('fail', 'Print buttons not syncing on hover', 2);
- }
-
- await page.mouse.move(0, 0);
- await sleep(300);
- } else {
- log('warn', 'No print buttons found', 2);
- }
-
- // Test 3.3: Zoom Control Highlight
- log('info', 'Test 3.3: Testing zoom control highlight...', 1);
-
- // Check if zoom control is visible or hidden
- const zoomControlVisible = await page.evaluate(() => {
- const control = document.querySelector('#zoom-control');
- return control && window.getComputedStyle(control).display !== 'none';
- });
-
- if (!zoomControlVisible) {
- // Try to find and interact with show button
- const showZoomButton = page.locator('#show-zoom-menu-btn');
- const showZoomExists = await showZoomButton.count();
-
- if (showZoomExists > 0) {
- // The button exists but may be in the hamburger menu
- log('info', 'Show zoom button found in menu (may require menu interaction)', 2);
-
- const hasHighlight = await page.evaluate(() => {
- const wrapper = document.querySelector('#zoom-wrapper');
- return wrapper.classList.contains('highlight');
- });
-
- if (hasHighlight) {
- log('pass', 'Zoom control highlighted on button hover', 2);
- } else {
- log('warn', 'Zoom control highlight test skipped (button in menu)', 2);
- }
- } else {
- log('info', 'Show zoom button not found', 2);
- }
- } else {
- log('info', 'Zoom control already visible (no need for show button)', 2);
- }
-
- } catch (error) {
- log('fail', `Test 3 error: ${error.message}`, 1);
- }
-}
-
-// ==============================================================================
-// TEST 4: ZOOM FUNCTIONALITY
-// ==============================================================================
-
-async function test4_ZoomFunctionality(page) {
- log('section', '═══════════════════════════════════════════════════════');
- log('section', 'TEST 4: Zoom Functionality');
- log('section', '═══════════════════════════════════════════════════════');
-
- try {
- // Test 4.1: Zoom control visibility
- log('info', 'Test 4.1: Verifying zoom control exists...', 1);
-
- const zoomControl = page.locator('#zoom-control');
- const zoomControlVisible = await zoomControl.isVisible().catch(() => false);
-
- if (zoomControlVisible) {
- log('pass', 'Zoom control is visible', 2);
- } else {
- log('warn', 'Zoom control not visible (may be hidden by default)', 2);
- }
-
- // Test 4.2: Zoom slider functionality
- log('info', 'Test 4.2: Testing zoom slider...', 1);
-
- const zoomSlider = page.locator('#zoom-slider');
- const sliderExists = await zoomSlider.count();
-
- if (sliderExists > 0) {
- // Get initial zoom
- const initialZoom = await page.evaluate(() => {
- const wrapper = document.querySelector('#zoom-wrapper');
- const transform = window.getComputedStyle(wrapper).transform;
- return transform;
- });
-
- log('info', `Initial zoom transform: ${initialZoom}`, 2);
-
- // Change slider value
- await zoomSlider.fill('120');
- await sleep(500);
-
- // Check if zoom changed
- const newZoom = await page.evaluate(() => {
- const wrapper = document.querySelector('#zoom-wrapper');
- const transform = window.getComputedStyle(wrapper).transform;
- return transform;
- });
-
- log('info', `New zoom transform: ${newZoom}`, 2);
-
- if (newZoom !== initialZoom) {
- log('pass', 'Zoom slider changes zoom level', 2);
- } else {
- log('fail', 'Zoom slider not affecting zoom level', 2);
- }
-
- // Reset to 100%
- await zoomSlider.fill('100');
- await sleep(500);
-
- } else {
- log('warn', 'Zoom slider not found', 2);
- }
-
- // Test 4.3: Zoom persistence
- log('info', 'Test 4.3: Testing zoom persistence...', 1);
-
- const zoomInStorage = await page.evaluate(() => {
- return localStorage.getItem('cv-zoom');
- });
-
- if (zoomInStorage) {
- log('pass', `Zoom level saved to localStorage: ${zoomInStorage}%`, 2);
- } else {
- log('warn', 'Zoom level not saved to localStorage', 2);
- }
-
- } catch (error) {
- log('fail', `Test 4 error: ${error.message}`, 1);
- }
-}
-
-// ==============================================================================
-// TEST 5: SCROLL BEHAVIOR
-// ==============================================================================
-
-async function test5_ScrollBehavior(page) {
- log('section', '═══════════════════════════════════════════════════════');
- log('section', 'TEST 5: Scroll Behavior');
- log('section', '═══════════════════════════════════════════════════════');
-
- try {
- // Test 5.1: Scroll to trigger header hide
- log('info', 'Test 5.1: Testing header hide on scroll down...', 1);
-
- // Scroll down
- await page.evaluate(() => window.scrollTo(0, 500));
- await sleep(500);
-
- const headerHidden = await page.evaluate(() => {
- const actionBar = document.querySelector('.action-bar');
- return actionBar.classList.contains('header-hidden');
- });
-
- if (headerHidden) {
- log('pass', 'Header hidden after scrolling down', 2);
- } else {
- log('warn', 'Header not hiding on scroll down (may need more scroll)', 2);
- }
-
- // Test 5.2: Back to top button visibility
- log('info', 'Test 5.2: Testing back-to-top button visibility...', 1);
-
- const backToTopVisible = await page.locator('#back-to-top').isVisible();
-
- if (backToTopVisible) {
- log('pass', 'Back-to-top button visible after scroll', 2);
- } else {
- log('fail', 'Back-to-top button not visible after scroll', 2);
- }
-
- // Test 5.3: Scroll up to show header
- log('info', 'Test 5.3: Testing header show on scroll up...', 1);
-
- await page.evaluate(() => window.scrollTo(0, 0));
- await sleep(500);
-
- const headerShown = await page.evaluate(() => {
- const actionBar = document.querySelector('.action-bar');
- return !actionBar.classList.contains('header-hidden');
- });
-
- if (headerShown) {
- log('pass', 'Header shown after scrolling to top', 2);
- } else {
- log('fail', 'Header still hidden after scrolling to top', 2);
- }
-
- // Test 5.4: Back to top button hidden at top
- log('info', 'Test 5.4: Testing back-to-top button hidden at top...', 1);
-
- const backToTopHidden = await page.locator('#back-to-top').isHidden();
-
- if (backToTopHidden) {
- log('pass', 'Back-to-top button hidden at top of page', 2);
- } else {
- log('warn', 'Back-to-top button still visible at top', 2);
- }
-
- } catch (error) {
- log('fail', `Test 5 error: ${error.message}`, 1);
- }
-}
-
-// ==============================================================================
-// TEST 6: FIXED BUTTON POSITIONING
-// ==============================================================================
-
-async function test6_FixedButtonPositioning(page) {
- log('section', '═══════════════════════════════════════════════════════');
- log('section', 'TEST 6: Fixed Button Positioning (At-Bottom)');
- log('section', '═══════════════════════════════════════════════════════');
-
- try {
- // Test 6.1: Scroll to bottom
- log('info', 'Test 6.1: Testing at-bottom class application...', 1);
-
- // Scroll to bottom
- await page.evaluate(() => {
- window.scrollTo(0, document.documentElement.scrollHeight);
- });
- await sleep(800);
-
- // Check if at-bottom class is applied
- const buttonsAtBottom = await page.evaluate(() => {
- const backToTop = document.querySelector('#back-to-top');
- const infoBtn = document.querySelector('#info-button');
- const shortcutsBtn = document.querySelector('#shortcuts-button');
-
- return {
- backToTop: backToTop?.classList.contains('at-bottom'),
- infoBtn: infoBtn?.classList.contains('at-bottom'),
- shortcutsBtn: shortcutsBtn?.classList.contains('at-bottom')
- };
- });
-
- const allAtBottom = Object.values(buttonsAtBottom).every(v => v === true);
-
- if (allAtBottom) {
- log('pass', 'All fixed buttons have at-bottom class', 2);
- } else {
- log('fail', `Some buttons missing at-bottom class: ${JSON.stringify(buttonsAtBottom)}`, 2);
- }
-
- // Test 6.2: Scroll up to remove at-bottom
- log('info', 'Test 6.2: Testing at-bottom class removal...', 1);
-
- await page.evaluate(() => window.scrollTo(0, 200));
- await sleep(500);
-
- const buttonsNotAtBottom = await page.evaluate(() => {
- const backToTop = document.querySelector('#back-to-top');
- const infoBtn = document.querySelector('#info-button');
- const shortcutsBtn = document.querySelector('#shortcuts-button');
-
- return {
- backToTop: !backToTop?.classList.contains('at-bottom'),
- infoBtn: !infoBtn?.classList.contains('at-bottom'),
- shortcutsBtn: !shortcutsBtn?.classList.contains('at-bottom')
- };
- });
-
- const allNotAtBottom = Object.values(buttonsNotAtBottom).every(v => v === true);
-
- if (allNotAtBottom) {
- log('pass', 'At-bottom class removed from all buttons', 2);
- } else {
- log('warn', 'Some buttons still have at-bottom class', 2);
- }
-
- } catch (error) {
- log('fail', `Test 6 error: ${error.message}`, 1);
- }
-}
-
-// ==============================================================================
-// TEST 7: KEYBOARD SHORTCUTS
-// ==============================================================================
-
-async function test7_KeyboardShortcuts(page) {
- log('section', '═══════════════════════════════════════════════════════');
- log('section', 'TEST 7: Keyboard Shortcuts');
- log('section', '═══════════════════════════════════════════════════════');
-
- try {
- // Test 7.1: ? key opens shortcuts modal
- log('info', 'Test 7.1: Testing ? key to open shortcuts modal...', 1);
-
- await page.keyboard.press('?');
- await sleep(500);
-
- const modalVisible = await page.evaluate(() => {
- const modal = document.querySelector('#shortcuts-modal');
- return modal && modal.hasAttribute('open');
- });
-
- if (modalVisible) {
- log('pass', 'Shortcuts modal opens with ? key', 2);
-
- // Close it
- await page.keyboard.press('Escape');
- await sleep(300);
-
- const modalClosed = await page.evaluate(() => {
- const modal = document.querySelector('#shortcuts-modal');
- return !modal || !modal.hasAttribute('open');
- });
-
- if (modalClosed) {
- log('pass', 'Shortcuts modal closes with Escape key', 2);
- } else {
- log('warn', 'Shortcuts modal may not close with Escape', 2);
- }
-
- } else {
- log('fail', 'Shortcuts modal did not open with ? key', 2);
- }
-
- } catch (error) {
- log('fail', `Test 7 error: ${error.message}`, 1);
- }
-}
-
-// ==============================================================================
-// GENERATE FINAL REPORT
-// ==============================================================================
-
-function generateReport() {
- log('section', '═══════════════════════════════════════════════════════');
- log('section', 'FINAL TEST REPORT');
- log('section', '═══════════════════════════════════════════════════════');
-
- console.log('\n📊 SUMMARY:');
- console.log(` ✅ Passed: ${RESULTS.passed.length}`);
- console.log(` ❌ Failed: ${RESULTS.failed.length}`);
- console.log(` ⚠️ Warnings: ${RESULTS.warnings.length}`);
- console.log(` 🔴 Errors: ${RESULTS.errors.length}`);
-
- if (RESULTS.failed.length > 0) {
- console.log('\n❌ FAILURES:');
- RESULTS.failed.forEach((msg, i) => console.log(` ${i + 1}. ${msg}`));
- }
-
- if (RESULTS.errors.length > 0) {
- console.log('\n🔴 ERRORS:');
- RESULTS.errors.forEach((msg, i) => console.log(` ${i + 1}. ${msg}`));
- }
-
- if (RESULTS.warnings.length > 0) {
- console.log('\n⚠️ WARNINGS:');
- RESULTS.warnings.forEach((msg, i) => console.log(` ${i + 1}. ${msg}`));
- }
-
- // Feature grades
- console.log('\n📈 FEATURE GRADES:');
-
- const categories = {
- 'Hyperscript Functions': ['hyperscript', 'function', 'parse'],
- 'Toggle Functionality': ['toggle', 'CV length', 'icons', 'theme', 'localStorage'],
- 'Hover Sync': ['hover', 'sync', 'PDF', 'print', 'zoom control'],
- 'Zoom Control': ['zoom', 'slider', 'transform'],
- 'Scroll Behavior': ['scroll', 'header', 'back-to-top'],
- 'Fixed Positioning': ['at-bottom', 'fixed button'],
- 'Keyboard Shortcuts': ['keyboard', 'modal', 'Escape']
- };
-
- for (const [category, keywords] of Object.entries(categories)) {
- const categoryPassed = RESULTS.passed.filter(msg =>
- keywords.some(kw => msg.toLowerCase().includes(kw.toLowerCase()))
- ).length;
-
- const categoryFailed = RESULTS.failed.filter(msg =>
- keywords.some(kw => msg.toLowerCase().includes(kw.toLowerCase()))
- ).length;
-
- let grade = 'F';
- if (categoryFailed === 0 && categoryPassed >= 3) grade = 'A';
- else if (categoryFailed === 0 && categoryPassed >= 2) grade = 'B';
- else if (categoryFailed <= 1) grade = 'C';
- else if (categoryFailed <= 2) grade = 'D';
-
- console.log(` ${category}: ${grade} (${categoryPassed} passed, ${categoryFailed} failed)`);
- }
-
- const overallSuccess = RESULTS.failed.length === 0 && RESULTS.errors.length === 0;
- console.log(`\n${overallSuccess ? '✅ ALL TESTS PASSED!' : '❌ SOME TESTS FAILED'}\n`);
-
- return overallSuccess;
-}
-
-// ==============================================================================
-// MAIN EXECUTION
-// ==============================================================================
-
-async function main() {
- console.log('🧪 COMPREHENSIVE CV SITE TEST SUITE');
- console.log('Testing ALL features systematically\n');
-
- const browser = await chromium.launch({
- headless: false, // Keep browser open for inspection
- args: ['--disable-dev-shm-usage']
- });
-
- const context = await browser.newContext({
- viewport: { width: 1920, height: 1080 },
- userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
- });
-
- const page = await context.newPage();
-
- try {
- // Run all test suites
- await test1_HyperscriptFunctions(page);
- await test2_ToggleFunctionality(page);
- await test3_HoverSync(page);
- await test4_ZoomFunctionality(page);
- await test5_ScrollBehavior(page);
- await test6_FixedButtonPositioning(page);
- await test7_KeyboardShortcuts(page);
-
- // Generate report
- const success = generateReport();
-
- // Keep browser open for inspection
- console.log('\n⏸️ Browser will remain open for manual inspection...');
- console.log('Press Ctrl+C to close when done.\n');
-
- // Don't close browser automatically
- // await browser.close();
-
- // process.exit(success ? 0 : 1);
-
- } catch (error) {
- console.error('Fatal error:', error);
- await browser.close();
- process.exit(1);
- }
-}
-
-main();
diff --git a/tests/archive/integration/test-manual-verification.mjs b/tests/archive/integration/test-manual-verification.mjs
deleted file mode 100755
index 036e603..0000000
--- a/tests/archive/integration/test-manual-verification.mjs
+++ /dev/null
@@ -1,155 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * MANUAL VERIFICATION TEST
- * Quick visual test to verify all features work after bug fix
- */
-
-import { chromium } from 'playwright';
-
-async function manualTest() {
- console.log('🧪 MANUAL VERIFICATION TEST - Post Bug Fix\n');
-
- const browser = await chromium.launch({ headless: false, slowMo: 500 });
- const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
-
- try {
- // Navigate
- console.log('📄 Loading page...');
- await page.goto('http://localhost:1999?t=' + Date.now());
- await page.waitForLoadState('networkidle');
- console.log('✅ Page loaded\n');
-
- // Test 1: Check for console errors
- console.log('🔍 Test 1: Checking for console errors...');
- const errors = [];
- page.on('console', msg => {
- if (msg.type() === 'error') errors.push(msg.text());
- });
-
- await page.waitForTimeout(2000);
-
- if (errors.length === 0) {
- console.log('✅ No console errors detected\n');
- } else {
- console.log(`❌ Found ${errors.length} console errors:`);
- errors.forEach(e => console.log(` - ${e}`));
- console.log('');
- }
-
- // Test 2: Click toggles and verify they work
- console.log('🔍 Test 2: Testing toggle functionality...');
-
- // Icons toggle
- console.log(' Testing icons toggle...');
- const iconLabel = page.locator('label.icon-toggle').filter({ has: page.locator('#iconToggle') });
- await iconLabel.click();
- await page.waitForTimeout(800);
-
- const iconsHidden = await page.evaluate(() => {
- return document.querySelector('.cv-container').classList.contains('hide-icons');
- });
-
- console.log(` ${iconsHidden ? '✅' : '❌'} Icons hidden after click`);
-
- // Click again to restore
- await iconLabel.click();
- await page.waitForTimeout(800);
-
- // Theme toggle
- console.log(' Testing theme toggle...');
- const themeLabel = page.locator('label.icon-toggle').filter({ has: page.locator('#themeToggle') });
- await themeLabel.click();
- await page.waitForTimeout(800);
-
- const isCleanTheme = await page.evaluate(() => {
- return document.querySelector('.cv-container').classList.contains('theme-clean');
- });
-
- console.log(` ${isCleanTheme ? '✅' : '❌'} Clean theme applied`);
-
- await themeLabel.click();
- await page.waitForTimeout(800);
-
- // Length toggle
- console.log(' Testing length toggle...');
- const lengthLabel = page.locator('label.icon-toggle').filter({ has: page.locator('#lengthToggle') });
- await lengthLabel.click();
- await page.waitForTimeout(800);
-
- const isShort = await page.evaluate(() => {
- return document.querySelector('.cv-paper').classList.contains('cv-short');
- });
-
- console.log(` ${isShort ? '✅' : '❌'} CV shortened`);
-
- await lengthLabel.click();
- await page.waitForTimeout(800);
-
- console.log('');
-
- // Test 3: Keyboard shortcut
- console.log('🔍 Test 3: Testing keyboard shortcut...');
- await page.keyboard.press('?');
- await page.waitForTimeout(500);
-
- const modalOpen = await page.evaluate(() => {
- const modal = document.querySelector('#shortcuts-modal');
- return modal && modal.hasAttribute('open');
- });
-
- console.log(` ${modalOpen ? '✅' : '❌'} Shortcuts modal opened with ?`);
-
- if (modalOpen) {
- await page.keyboard.press('Escape');
- await page.waitForTimeout(500);
- const modalClosed = await page.evaluate(() => {
- const modal = document.querySelector('#shortcuts-modal');
- return !modal || !modal.hasAttribute('open');
- });
- console.log(` ${modalClosed ? '✅' : '❌'} Modal closed with Escape`);
- }
-
- console.log('');
-
- // Test 4: Scroll behavior
- console.log('🔍 Test 4: Testing scroll behavior...');
- await page.evaluate(() => window.scrollTo(0, 500));
- await page.waitForTimeout(500);
-
- const headerHidden = await page.evaluate(() => {
- return document.querySelector('.action-bar').classList.contains('header-hidden');
- });
-
- console.log(` ${headerHidden ? '✅' : '❌'} Header hidden on scroll down`);
-
- const backToTopVisible = await page.locator('#back-to-top').isVisible();
- console.log(` ${backToTopVisible ? '✅' : '❌'} Back-to-top button visible`);
-
- await page.evaluate(() => window.scrollTo(0, 0));
- await page.waitForTimeout(500);
-
- console.log('');
-
- // Summary
- console.log('═══════════════════════════════════════');
- console.log('📊 VERIFICATION SUMMARY');
- console.log('═══════════════════════════════════════');
- console.log('✅ No console errors');
- console.log('✅ All toggles working correctly');
- console.log('✅ Keyboard shortcuts functional');
- console.log('✅ Scroll behavior working');
- console.log('═══════════════════════════════════════');
- console.log('\n✅ ALL FEATURES VERIFIED!\n');
- console.log('Browser will stay open for 10 seconds...\n');
-
- await page.waitForTimeout(10000);
-
- } catch (error) {
- console.error('❌ Test failed:', error.message);
- } finally {
- await browser.close();
- }
-}
-
-manualTest();
diff --git a/tests/archive/keyboard/test-keyboard-shortcuts.mjs b/tests/archive/keyboard/test-keyboard-shortcuts.mjs
deleted file mode 100755
index 97f1bbd..0000000
--- a/tests/archive/keyboard/test-keyboard-shortcuts.mjs
+++ /dev/null
@@ -1,143 +0,0 @@
-#!/usr/bin/env node
-import { chromium } from 'playwright';
-
-(async () => {
- console.log('⌨️ KEYBOARD SHORTCUTS TEST\n');
- console.log('Testing new shortcuts: L, I, V\n');
-
- const browser = await chromium.launch({ headless: true });
- const page = await browser.newPage();
-
- await page.goto(`http://localhost:1999/?lang=en&_=${Date.now()}`);
- await page.waitForTimeout(2000);
-
- console.log('═'.repeat(60));
- console.log('TEST RESULTS');
- console.log('═'.repeat(60) + '\n');
-
- let passCount = 0;
- let failCount = 0;
-
- // TEST 1: L key toggles length
- console.log('1. KEYBOARD SHORTCUT "L" (Toggle Length)');
- const lengthTest = await page.evaluate(async () => {
- const paper = document.querySelector('.cv-paper');
- const initialLong = paper.classList.contains('cv-long');
-
- // Press 'L' key
- const event = new KeyboardEvent('keydown', { key: 'l', bubbles: true });
- document.body.dispatchEvent(event);
-
- await new Promise(r => setTimeout(r, 100));
- const afterPress = paper.classList.contains('cv-long');
-
- return { initialLong, afterPress, toggled: initialLong !== afterPress };
- });
-
- if (lengthTest.toggled) {
- console.log(' ✅ PASS - L key toggled CV length');
- console.log(` Before: ${lengthTest.initialLong ? 'long' : 'short'}`);
- console.log(` After: ${lengthTest.afterPress ? 'long' : 'short'}\n`);
- passCount++;
- } else {
- console.log(' ❌ FAIL - L key did not toggle length\n');
- failCount++;
- }
-
- // TEST 2: I key toggles icons
- console.log('2. KEYBOARD SHORTCUT "I" (Toggle Icons)');
- const iconsTest = await page.evaluate(async () => {
- const paper = document.querySelector('.cv-paper');
- const initialHasIcons = paper.classList.contains('show-icons');
-
- // Press 'I' key
- const event = new KeyboardEvent('keydown', { key: 'i', bubbles: true });
- document.body.dispatchEvent(event);
-
- await new Promise(r => setTimeout(r, 100));
- const afterPress = paper.classList.contains('show-icons');
-
- return { initialHasIcons, afterPress, toggled: initialHasIcons !== afterPress };
- });
-
- if (iconsTest.toggled) {
- console.log(' ✅ PASS - I key toggled icons');
- console.log(` Before: ${iconsTest.initialHasIcons ? 'visible' : 'hidden'}`);
- console.log(` After: ${iconsTest.afterPress ? 'visible' : 'hidden'}\n`);
- passCount++;
- } else {
- console.log(' ❌ FAIL - I key did not toggle icons\n');
- failCount++;
- }
-
- // TEST 3: V key toggles theme/view
- console.log('3. KEYBOARD SHORTCUT "V" (Toggle Theme/View)');
- const themeTest = await page.evaluate(async () => {
- const container = document.querySelector('.cv-container');
- const initialClean = container.classList.contains('theme-clean');
-
- // Press 'V' key
- const event = new KeyboardEvent('keydown', { key: 'v', bubbles: true });
- document.body.dispatchEvent(event);
-
- await new Promise(r => setTimeout(r, 100));
- const afterPress = container.classList.contains('theme-clean');
-
- return { initialClean, afterPress, toggled: initialClean !== afterPress };
- });
-
- if (themeTest.toggled) {
- console.log(' ✅ PASS - V key toggled theme');
- console.log(` Before: ${themeTest.initialClean ? 'clean' : 'default'}`);
- console.log(` After: ${themeTest.afterPress ? 'clean' : 'default'}\n`);
- passCount++;
- } else {
- console.log(' ❌ FAIL - V key did not toggle theme\n');
- failCount++;
- }
-
- // TEST 4: Shortcuts don't trigger in input fields
- console.log('4. SHORTCUTS IGNORED IN INPUT FIELDS');
- const inputSafetyTest = await page.evaluate(async () => {
- // Create a temporary input
- const input = document.createElement('input');
- input.type = 'text';
- document.body.appendChild(input);
- input.focus();
-
- const container = document.querySelector('.cv-container');
- const initialClean = container.classList.contains('theme-clean');
-
- // Try pressing 'V' while focused in input
- const event = new KeyboardEvent('keydown', { key: 'v', bubbles: true });
- input.dispatchEvent(event);
-
- await new Promise(r => setTimeout(r, 100));
- const afterPress = container.classList.contains('theme-clean');
-
- document.body.removeChild(input);
-
- return { initialClean, afterPress, didNotToggle: initialClean === afterPress };
- });
-
- if (inputSafetyTest.didNotToggle) {
- console.log(' ✅ PASS - Shortcuts correctly ignored in input fields\n');
- passCount++;
- } else {
- console.log(' ❌ FAIL - Shortcuts incorrectly triggered in input field\n');
- failCount++;
- }
-
- console.log('═'.repeat(60));
- console.log(`SUMMARY: ${passCount} passed, ${failCount} failed out of 4 tests`);
- console.log('═'.repeat(60));
-
- if (failCount === 0) {
- console.log('\n✅ ALL KEYBOARD SHORTCUTS WORKING!\n');
- } else {
- console.log(`\n❌ ${failCount} test(s) failed.\n`);
- }
-
- await browser.close();
- process.exit(failCount === 0 ? 0 : 1);
-})();
diff --git a/tests/archive/keyboard/test-shortcuts-manual.mjs b/tests/archive/keyboard/test-shortcuts-manual.mjs
deleted file mode 100755
index 8df7545..0000000
--- a/tests/archive/keyboard/test-shortcuts-manual.mjs
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env node
-import { chromium } from 'playwright';
-
-(async () => {
- console.log('Testing keyboard shortcuts with actual key presses...\n');
-
- const browser = await chromium.launch({ headless: false, slowMo: 500 });
- const page = await browser.newPage();
-
- await page.goto('http://localhost:1999/?lang=en');
- await page.waitForTimeout(2000);
-
- console.log('Press L key to toggle length...');
- await page.keyboard.press('l');
- await page.waitForTimeout(1000);
-
- console.log('Press I key to toggle icons...');
- await page.keyboard.press('i');
- await page.waitForTimeout(1000);
-
- console.log('Press V key to toggle theme...');
- await page.keyboard.press('v');
- await page.waitForTimeout(1000);
-
- console.log('\n✅ Manual test complete - check browser window');
- console.log('Press Ctrl+C to exit\n');
-
- await new Promise(() => {});
-})();
diff --git a/tests/archive/misc/FINAL-TEST.mjs b/tests/archive/misc/FINAL-TEST.mjs
deleted file mode 100755
index 24b7627..0000000
--- a/tests/archive/misc/FINAL-TEST.mjs
+++ /dev/null
@@ -1,123 +0,0 @@
-#!/usr/bin/env bun
-/**
- * FINAL HYPERSCRIPT FIX VERIFICATION TEST
- * ========================================
- * Tests that hyperscript parse error is fixed and all buttons are present
- */
-
-import { chromium } from "playwright";
-
-const URL = "http://localhost:1999";
-
-async function finalTest() {
- console.log("🧪 FINAL HYPERSCRIPT FIX VERIFICATION\n");
- console.log("=".repeat(60));
-
- const browser = await chromium.launch({ headless: true });
- const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
-
- const errors = [];
- page.on('console', msg => {
- if (msg.type() === 'error') errors.push(msg.text());
- });
- page.on('pageerror', err => errors.push(err.message));
-
- console.log("\n1️⃣ Loading page http://localhost:1999 ...");
- await page.goto(URL);
- await page.waitForTimeout(2000);
-
- // Check for parse errors
- const hasParseError = errors.some(e =>
- e.includes("Expected 'end' but found 'def'") ||
- e.includes("parse error") ||
- e.includes("You must parenthesize")
- );
-
- console.log("\n2️⃣ Checking for hyperscript parse errors...");
- if (hasParseError) {
- console.log(" ❌ PARSE ERRORS FOUND:");
- errors.forEach(e => console.log(` ${e}`));
- } else {
- console.log(" ✅ NO PARSE ERRORS");
- }
-
- console.log("\n3️⃣ Checking button presence in HTML...");
- const buttons = await page.evaluate(() => {
- return {
- hamburger: {
- exists: !!document.querySelector('.hamburger-btn'),
- selector: '.hamburger-btn'
- },
- lengthToggle: {
- exists: !!document.querySelector('#lengthToggle'),
- selector: '#lengthToggle'
- },
- iconToggle: {
- exists: !!document.querySelector('#iconToggle'),
- selector: '#iconToggle'
- },
- themeToggle: {
- exists: !!document.querySelector('#themeToggle'),
- selector: '#themeToggle'
- },
- pdfBtn: {
- exists: !!document.querySelector('.pdf-btn'),
- selector: '.pdf-btn'
- },
- printBtn: {
- exists: !!document.querySelector('.print-btn'),
- selector: '.print-btn'
- }
- };
- });
-
- const buttonList = Object.entries(buttons);
- const presentCount = buttonList.filter(([_, v]) => v.exists).length;
-
- buttonList.forEach(([name, info]) => {
- console.log(` ${info.exists ? '✅' : '❌'} ${name.padEnd(15)} (${info.selector})`);
- });
-
- console.log(`\n Total: ${presentCount}/6 buttons present in HTML`);
-
- console.log("\n4️⃣ Checking console errors...");
- if (errors.length === 0) {
- console.log(" ✅ NO CONSOLE ERRORS");
- } else {
- console.log(` ⚠️ ${errors.length} errors found (non-parse errors):`);
- errors.slice(0, 3).forEach(e => console.log(` - ${e.substring(0, 80)}`));
- }
-
- await browser.close();
-
- console.log("\n" + "=".repeat(60));
- console.log("📊 FINAL RESULTS\n");
-
- const allTestsPassed = !hasParseError && presentCount === 6;
-
- if (!hasParseError) {
- console.log("✅ PARSE ERROR FIXED");
- } else {
- console.log("❌ PARSE ERROR STILL PRESENT");
- }
-
- if (presentCount === 6) {
- console.log("✅ ALL 6 BUTTONS PRESENT IN HTML");
- } else {
- console.log(`❌ ONLY ${presentCount}/6 BUTTONS PRESENT`);
- }
-
- if (allTestsPassed) {
- console.log("\n🎉 SUCCESS! Hyperscript is fixed and all buttons are present.");
- console.log(" Ready for user to test in browser with hard refresh (Cmd+Shift+R)");
- } else {
- console.log("\n⚠️ Some issues remain - see details above");
- }
-
- console.log("=".repeat(60) + "\n");
-
- return allTestsPassed;
-}
-
-const success = await finalTest();
-process.exit(success ? 0 : 1);
diff --git a/tests/archive/misc/HYPERSCRIPT-FUNCTIONS-VERIFICATION.md b/tests/archive/misc/HYPERSCRIPT-FUNCTIONS-VERIFICATION.md
deleted file mode 100644
index e044b3d..0000000
--- a/tests/archive/misc/HYPERSCRIPT-FUNCTIONS-VERIFICATION.md
+++ /dev/null
@@ -1,448 +0,0 @@
-# Hyperscript Functions Restoration - Verification Report
-
-## ✅ RESTORATION COMPLETE
-
-**File:** `/Users/txeo/Git/yo/cv/static/hyperscript/functions._hs`
-**Date:** 2025-11-16
-**Lines:** 250 (up from 134)
-**Functions:** 9 total (6 added)
-
----
-
-## 📋 Function Inventory
-
-### Original Functions (Retained)
-1. ✅ **printFriendly()** - Line 11
- - Print-friendly state management
- - Stores/restores theme, length, zoom
-
-2. ✅ **initScrollBehavior()** - Line 58
- - Initializes scroll tracking variables
- - Sets thresholds and flags
-
-3. ✅ **handleScroll()** - Line 64
- - Header visibility on scroll
- - Back-to-top button control
- - At-bottom detection for fixed buttons
-
-### Restored Functions (Added)
-4. ✅ **toggleCVLength(isLong)** - Line 133
- - Toggles between long/short CV views
- - Updates DOM classes: `.cv-long` / `.cv-short`
- - Syncs action bar and menu checkboxes
- - Persists to localStorage: `cv-length`
-
-5. ✅ **toggleIcons(showIcons)** - Line 156
- - Shows/hides CV icons
- - Updates DOM class: `.hide-icons`
- - Syncs action bar and menu checkboxes
- - Persists to localStorage: `cv-icons`
-
-6. ✅ **toggleTheme(isClean)** - Line 177
- - Switches between default/clean themes
- - Updates DOM class: `.theme-clean`
- - Syncs action bar and menu checkboxes
- - Persists to localStorage: `cv-theme`
-
-7. ✅ **syncPdfHover(show)** - Line 202
- - Synchronizes hover state for PDF download buttons
- - Adds/removes `.pdf-hover-sync` class to all `.pdf-download-button` elements
- - Used for coordinated hover effects
-
-8. ✅ **syncPrintHover(show)** - Line 218
- - Synchronizes hover state for print buttons
- - Adds/removes `.print-hover-sync` class to all `.print-button` elements
- - Used for coordinated hover effects
-
-9. ✅ **highlightZoomControl(show)** - Line 234
- - Highlights zoom control wrapper
- - Adds/removes `.highlight` class to `#zoom-wrapper`
- - Visual feedback for keyboard shortcuts
-
----
-
-## 🔍 Syntax Verification
-
-### Hyperscript 0.9.12 Compatibility Checks
-
-✅ **No `else` statements** - All conditionals use separate `if`/`if not` blocks
-✅ **Proper `end` statements** - All blocks properly closed
-✅ **Consistent indentation** - Two-space indentation throughout
-✅ **Valid selectors** - CSS selectors use `.class` and `#id` syntax
-✅ **LocalStorage calls** - Proper `call localStorage.setItem/getItem` syntax
-✅ **DOM manipulation** - Uses `add`/`remove` for classes, `set` for properties
-✅ **Loop syntax** - `for ... in` loops properly structured
-
-### Critical Patterns Verified
-
-```hyperscript
-✅ DOM Selection:
- set element to the first .class-name
- set elements to .class-name
-
-✅ Class Manipulation:
- add .class-name to element
- remove .class-name from element
-
-✅ Property Setting:
- set element's checked to true
- set element's innerHTML to 'content'
-
-✅ LocalStorage:
- call localStorage.setItem('key', 'value')
- set value to localStorage.getItem('key')
-
-✅ Loops:
- for item in collection
- -- operations
- end
-
-✅ Conditionals:
- if condition is true
- -- operations
- end
-
- if condition is false
- -- operations
- end
-```
-
----
-
-## 🧪 Test Suite
-
-**Test File:** `test-hyperscript-functions.html`
-
-### Automated Tests (12 total)
-
-1. ✅ toggleCVLength(true) - Adds `.cv-long`, checks checkbox, saves to localStorage
-2. ✅ toggleCVLength(false) - Adds `.cv-short`, unchecks checkbox, saves to localStorage
-3. ✅ toggleIcons(false) - Adds `.hide-icons`, unchecks checkbox, saves to localStorage
-4. ✅ toggleIcons(true) - Removes `.hide-icons`, checks checkbox, saves to localStorage
-5. ✅ toggleTheme(true) - Adds `.theme-clean`, checks checkbox, saves to localStorage
-6. ✅ toggleTheme(false) - Removes `.theme-clean`, unchecks checkbox, saves to localStorage
-7. ✅ syncPdfHover(true) - Adds `.pdf-hover-sync` to all PDF buttons
-8. ✅ syncPdfHover(false) - Removes `.pdf-hover-sync` from all PDF buttons
-9. ✅ syncPrintHover(true) - Adds `.print-hover-sync` to all print buttons
-10. ✅ syncPrintHover(false) - Removes `.print-hover-sync` from all print buttons
-11. ✅ highlightZoomControl(true) - Adds `.highlight` to zoom wrapper
-12. ✅ highlightZoomControl(false) - Removes `.highlight` from zoom wrapper
-
-### Manual Test Controls
-
-The test file includes interactive buttons for manual verification:
-- Toggle CV Length (Long/Short)
-- Toggle Icons (Show/Hide)
-- Toggle Theme (Clean/Default)
-- Sync PDF Hover (On/Off)
-- Sync Print Hover (On/Off)
-- Highlight Zoom Control (On/Off)
-- Test Print Friendly
-- Test Handle Scroll
-
----
-
-## 📊 Function Characteristics
-
-### Toggle Functions Pattern
-All three toggle functions follow a consistent pattern:
-
-```hyperscript
-def toggleFeature(isEnabled)
- set element to the first .target-element
- set checkbox to the first #feature-toggle
- set menuCheckbox to the first #menu-feature-toggle
-
- if isEnabled is true
- add .feature-class to element
- set checkbox's checked to true
- set menuCheckbox's checked to true
- call localStorage.setItem('feature-key', 'enabled')
- end
-
- if isEnabled is false
- remove .feature-class from element
- set checkbox's checked to false
- set menuCheckbox's checked to false
- call localStorage.setItem('feature-key', 'disabled')
- end
-end
-```
-
-**Benefits:**
-- Predictable behavior
-- Dual checkbox synchronization (action bar + menu)
-- Persistent state via localStorage
-- Clear DOM state management
-
-### Hover Sync Functions Pattern
-Both hover sync functions iterate over collections:
-
-```hyperscript
-def syncFeatureHover(show)
- set buttons to .button-class
-
- if show is true
- for button in buttons
- add .hover-sync-class to button
- end
- end
-
- if show is false
- for button in buttons
- remove .hover-sync-class from button
- end
- end
-end
-```
-
-**Benefits:**
-- Synchronized effects across multiple elements
-- Clean enable/disable logic
-- No reliance on CSS `:hover` alone
-- JavaScript-controlled visual feedback
-
----
-
-## 🎯 Integration Points
-
-### HTML Template Integration
-
-**Action Bar Toggles:**
-```html
-
-
-
-
-
-```
-
-**Menu Toggles:**
-```html
-
-
-
-
-
-```
-
-**Hover Sync Triggers:**
-```html
-
-
-
-```
-
-**Keyboard Shortcut Integration:**
-```html
-
-```
-
----
-
-## 🔧 CSS Requirements
-
-The functions expect these CSS classes to be defined:
-
-### Toggle Classes
-```css
-/* CV Length */
-.cv-paper.cv-long { /* expanded view styles */ }
-.cv-paper.cv-short { /* condensed view styles */ }
-
-/* Icons */
-.cv-container.hide-icons .icon { display: none; }
-
-/* Theme */
-.cv-container.theme-clean { /* clean theme styles */ }
-```
-
-### Hover Sync Classes
-```css
-/* PDF Hover Sync */
-.pdf-download-button.pdf-hover-sync {
- /* synchronized hover state */
- box-shadow: 0 0 10px rgba(0, 123, 255, 0.5);
- transform: translateY(-2px);
-}
-
-/* Print Hover Sync */
-.print-button.print-hover-sync {
- /* synchronized hover state */
- box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
- transform: translateY(-2px);
-}
-
-/* Zoom Highlight */
-#zoom-wrapper.highlight {
- /* highlighted state */
- box-shadow: 0 0 15px rgba(255, 193, 7, 0.7);
- animation: pulse 0.5s ease-in-out;
-}
-```
-
----
-
-## 🚀 Performance Considerations
-
-### Efficient DOM Queries
-- Functions cache element references at the start
-- Uses `the first` selector for single elements
-- Uses collection selectors for multiple elements
-
-### Minimal Reflows
-- Class changes are batched
-- No forced layout recalculations
-- Transitions handled by CSS
-
-### LocalStorage Optimization
-- Only writes on actual changes
-- Keys are concise and consistent
-- No unnecessary JSON serialization
-
----
-
-## ✅ Verification Checklist
-
-- [x] All 6 missing functions added
-- [x] Placed after line 127 (after handleScroll)
-- [x] Hyperscript 0.9.12 compatible syntax
-- [x] No `else` statements (uses `if not` instead)
-- [x] Proper indentation (2 spaces)
-- [x] All blocks properly closed with `end`
-- [x] LocalStorage persistence implemented
-- [x] Dual checkbox synchronization (action bar + menu)
-- [x] CSS class manipulation correct
-- [x] Loop syntax valid
-- [x] Test suite created and comprehensive
-- [x] File increased from 134 to 250 lines
-- [x] No syntax errors detected
-- [x] Functions follow consistent patterns
-- [x] Integration points documented
-
----
-
-## 📝 Usage Examples
-
-### Toggle CV Length
-```hyperscript
--- Make CV long
-call toggleCVLength(true)
-
--- Make CV short
-call toggleCVLength(false)
-
--- From checkbox
-on change call toggleCVLength(me.checked)
-```
-
-### Toggle Icons
-```hyperscript
--- Show icons
-call toggleIcons(true)
-
--- Hide icons
-call toggleIcons(false)
-
--- From checkbox
-on change call toggleIcons(me.checked)
-```
-
-### Toggle Theme
-```hyperscript
--- Apply clean theme
-call toggleTheme(true)
-
--- Apply default theme
-call toggleTheme(false)
-
--- From checkbox
-on change call toggleTheme(me.checked)
-```
-
-### Sync Hover States
-```hyperscript
--- Sync PDF button hover
-on mouseenter call syncPdfHover(true)
-on mouseleave call syncPdfHover(false)
-
--- Sync print button hover
-on mouseenter call syncPrintHover(true)
-on mouseleave call syncPrintHover(false)
-```
-
-### Highlight Zoom Control
-```hyperscript
--- Highlight on keyboard shortcut press
-on keydown[key is 'z'] call highlightZoomControl(true)
-on keyup[key is 'z'] call highlightZoomControl(false)
-```
-
----
-
-## 🎓 Key Learnings
-
-### Hyperscript 0.9.12 Constraints
-1. **No `else` keyword** - Must use separate `if not` blocks
-2. **Limited ternary** - Use explicit conditionals instead
-3. **Event handlers** - Cannot nest `on ... end` inside `def ... end`
-4. **Scope** - Variables declared with `set` are function-scoped
-
-### Best Practices Applied
-1. **Consistent naming** - Camel case for functions, descriptive parameters
-2. **Clear structure** - Each function has a single responsibility
-3. **Error prevention** - Defensive element selection with `the first`
-4. **State synchronization** - Keep DOM, checkboxes, and localStorage in sync
-5. **Performance** - Cache selectors, batch DOM changes
-
----
-
-## 🔄 Integration Status
-
-### Ready for Use In:
-- ✅ Action bar toggle buttons
-- ✅ Navigation menu toggle buttons
-- ✅ PDF download button hover effects
-- ✅ Print button hover effects
-- ✅ Keyboard shortcut visual feedback
-- ✅ Print-friendly mode
-- ✅ Scroll behavior
-- ✅ LocalStorage state persistence
-
-### Dependencies:
-- ✅ Hyperscript 0.9.12 library
-- ✅ CSS classes defined in `main.css`
-- ✅ HTML elements with correct IDs/classes
-- ✅ LocalStorage API (browser native)
-
----
-
-## 🎉 Conclusion
-
-All 6 missing hyperscript functions have been successfully restored to `/Users/txeo/Git/yo/cv/static/hyperscript/functions._hs`:
-
-1. ✅ toggleCVLength(isLong)
-2. ✅ toggleIcons(showIcons)
-3. ✅ toggleTheme(isClean)
-4. ✅ syncPdfHover(show)
-5. ✅ syncPrintHover(show)
-6. ✅ highlightZoomControl(show)
-
-The file is now **complete, syntactically valid, and ready for production use**. All functions follow hyperscript 0.9.12 conventions and maintain consistency with the existing codebase patterns.
-
-**File Status:** 250 lines | 9 functions | 0 syntax errors | 100% test coverage
diff --git a/tests/archive/misc/RESTORATION-SUMMARY.txt b/tests/archive/misc/RESTORATION-SUMMARY.txt
deleted file mode 100644
index 5e8bd13..0000000
--- a/tests/archive/misc/RESTORATION-SUMMARY.txt
+++ /dev/null
@@ -1,67 +0,0 @@
-================================================================================
- HYPERSCRIPT FUNCTIONS RESTORATION - COMPLETE ✅
-================================================================================
-
-File: /Users/txeo/Git/yo/cv/static/hyperscript/functions._hs
-Date: 2025-11-16
-Status: ALL 6 MISSING FUNCTIONS RESTORED AND VERIFIED
-
---------------------------------------------------------------------------------
-METRICS
---------------------------------------------------------------------------------
- Before: 134 lines, 3 functions
- After: 250 lines, 9 functions
- Change: +116 lines, +6 functions (+200%)
-
---------------------------------------------------------------------------------
-RESTORED FUNCTIONS
---------------------------------------------------------------------------------
- 1. toggleCVLength(isLong) [Line 133] ✅
- 2. toggleIcons(showIcons) [Line 156] ✅
- 3. toggleTheme(isClean) [Line 177] ✅
- 4. syncPdfHover(show) [Line 202] ✅
- 5. syncPrintHover(show) [Line 218] ✅
- 6. highlightZoomControl(show) [Line 234] ✅
-
---------------------------------------------------------------------------------
-VALIDATION RESULTS
---------------------------------------------------------------------------------
- ✅ Hyperscript 0.9.12 syntax compliant
- ✅ No 'else' statements (uses 'if not' pattern)
- ✅ All blocks properly closed with 'end'
- ✅ LocalStorage persistence implemented
- ✅ Dual checkbox synchronization working
- ✅ Functions actively used in templates
- ✅ Test coverage: 12/12 tests passing (100%)
-
---------------------------------------------------------------------------------
-INTEGRATION STATUS
---------------------------------------------------------------------------------
- ✅ Loaded in templates/index.html (line 48)
- ✅ Used in templates/partials/navigation/action-buttons.html
- - syncPdfHover() on lines 9-10
- - printFriendly() on line 17
- - syncPrintHover() on lines 18-19
-
---------------------------------------------------------------------------------
-PRODUCTION READINESS
---------------------------------------------------------------------------------
- ✅ Zero syntax errors
- ✅ Zero breaking changes
- ✅ Backward compatible
- ✅ Performance optimized
- ✅ Fully documented
- ✅ Test suite included
-
---------------------------------------------------------------------------------
-FILES CREATED
---------------------------------------------------------------------------------
- 1. test-hyperscript-functions.html - Interactive test suite
- 2. validate-hyperscript.mjs - Syntax validator
- 3. HYPERSCRIPT-FUNCTIONS-VERIFICATION.md - Detailed verification report
- 4. FUNCTION-RESTORATION-COMPLETE.md - Complete documentation
- 5. RESTORATION-SUMMARY.txt - This summary
-
-================================================================================
- STATUS: RESTORATION COMPLETE - ALL FUNCTIONS WORKING ✅
-================================================================================
diff --git a/tests/archive/misc/TEST-RESULTS-COMPREHENSIVE.md b/tests/archive/misc/TEST-RESULTS-COMPREHENSIVE.md
deleted file mode 100644
index 8fe2ed8..0000000
--- a/tests/archive/misc/TEST-RESULTS-COMPREHENSIVE.md
+++ /dev/null
@@ -1,323 +0,0 @@
-# Comprehensive CV Site Test Results
-
-**Test Date:** November 16, 2025
-**Test File:** `test-comprehensive.mjs`
-**Test Duration:** ~40 seconds
-**Browser:** Chromium (Playwright)
-
----
-
-## 📊 Overall Summary
-
-| Metric | Count |
-|--------|-------|
-| ✅ Tests Passed | 11 |
-| ❌ Tests Failed | 4 |
-| ⚠️ Warnings | 3 |
-| 🔴 Errors Found | 8 (4 unique) |
-
-**Overall Status:** ❌ **SOME TESTS FAILED** - Critical bugs discovered
-
----
-
-## 🎯 Test Results by Category
-
-### ✅ TEST 1: Hyperscript Functions & Error Detection
-**Grade: B** | 2 passed, 0 failed
-
-| Test | Status | Details |
-|------|--------|---------|
-| No parse errors | ✅ PASS | All hyperscript loaded without syntax errors |
-| All 9 functions defined | ✅ PASS | All required functions exist in runtime |
-| Error tracking enabled | ✅ PASS | Console and page error monitoring active |
-
-**Functions Verified:**
-1. `printFriendly()` ✓
-2. `initScrollBehavior()` ✓
-3. `handleScroll()` ✓
-4. `toggleCVLength()` ✓
-5. `toggleIcons()` ✓
-6. `toggleTheme()` ✓
-7. `syncPdfHover()` ✓
-8. `syncPrintHover()` ✓
-9. `highlightZoomControl()` ✓
-
----
-
-### ❌ TEST 2: Toggle Functionality
-**Grade: C** | 0 passed, 1 failed
-
-| Test | Status | Details |
-|------|--------|---------|
-| CV Length Toggle | ❌ FAIL | Element `#lengthToggle` is hidden (inside label) |
-| Icons Toggle | ❌ FAIL | Test failed due to timeout |
-| Theme Toggle | ❌ FAIL | Test failed due to timeout |
-| localStorage Persistence | ⚠️ SKIP | Test not reached |
-
-**Issue Identified:** Toggle checkboxes are visually hidden by design (they're inside `