resume-flock
Dark, chaotic, and deep
resume-flock is an interactive resume explorer. Upload a DOCX or PDF resume and it is parsed into your employment experience and technical skills. You explore them as business cards (one per job) and skill cards (one per skill) in 3D or in a linear list. Add details, dates, and skills for each job, then print your revised resume as a new HTML file.
High-level functionality
- resume-parser — Parses uploaded DOCX/PDF resumes at runtime into jobs and skills (via the resume-parser package). Optional LLM-based skill merging can normalize and merge skill names during or after parsing.
- resume-selector — Choose which resume to view when you have multiple resumes; switch between them from the header dropdown.
- resume-details-editor — Modal to edit resume-level data: metadata (Meta tab), other sections (summary, contact, certifications, websites; Other tab), and skill categories (Skills tab).
- job-details-editor — Edit each job’s employer, title, dates, and description from the resume-details-editor Jobs tab.
- job-skills-selector — Assign or change skills per job (JobSkillEditor); skills are merged with parser-extracted job-skills and shown on the resume list.
- resume-print — Build a printable HTML resume from the current in-memory data and open it in a new tab (Print button in the resume header).
📚 Framework Documentation
Vue 3 Composition API Architecture
The modern reactive state management and component composition system:
- 📖 Vue 3 Composition API - Official Vue 3 documentation
- 🚀 Composables Guide - Reusable composition functions
- 💡 Reactivity in Depth - Understanding reactive systems
- 🏗️ Script Setup - Modern component authoring
- 🎯 Template Refs - DOM element access patterns
- 🔄 Watchers - Reactive data watching
- 🔧 TypeScript Support - Type-safe Vue development
🧬 Critical Inter-Relationships & System Dependencies
These critical inter-relationships are essential for understanding how the application functions. Preserve them during modifications.
🎨 Color Palette System Dependencies
The palette system handles cards (CSS variables), clones (inline selected state), badges (SELECTED state colors to match clones), and timeline (normal state). Palette or clone changes require cache clearing (clearAllCache()) so the registry discovers new elements. Badges must update on color-palette-changed, job-selected, and badge visibility toggle.
State System Coordination
The resize handle must update both persistence and reactive UI: updateAppState(...) for persistence and storeActions.setScenePercentage(...) for labels. Scene and Resume viewer percentages use the same mapping logic and must total 100%.
🎯 Template Ref Injection Dependencies
DOM Element Availability Chain
Parent-to-child template ref injection: AppContent provides sceneContainerRef, bullsEyeRef, and focalPointRef to SceneContainer, bullsEye, and focal point systems; SceneContainer provides scenePlaneRef to CardsController and sceneContentRef to Timeline. Refs are passed into managers via setXxxElement() so DOM is never queried by id.
Initialization Order Dependency
Critical timing: (1) Vue mount makes template refs available; (2) watchers on those refs inject elements into systems; (3) systems then initialize using the injected elements; (4) event binding attaches to the real DOM. Initialization code runs only after refs are set.
⚡ Event System Coordination
Debounced Event Architecture
Performance dependency: Resize events are debounced (e.g. 150ms) during drag to avoid cascading updates; on drag end or for step operations events dispatch immediately.
Cross-Component Event Chain
Selection sync: A cDiv click calls selectionManager.selectJobNumber() and fires 'job-selected'. Listeners then create the clone (hide original, show clone), show badges relative to the clone, clear the element registry cache, apply palette (selected state colors), and scroll into view (rDiv and cDiv in sync).
🏗️ System Architecture Dependencies
Global Element Registry Pattern
Performance optimization: The element registry caches DOM queries (e.g. getAllBizCardDivs). After DOM changes (clone creation, palette updates), call clearAllCache() so the next query returns fresh results.
Composable Singleton Pattern
State management: Composables like useBadgeToggle hold shared refs at module scope so every component gets the same reactive instance (e.g. isBadgesVisible, toggleBadges) without prop drilling.
🔧 Critical Maintenance Guidelines
When Modifying Color System
- ✅ Update all four element types (cards, clones, badges, timeline)
- ✅ Clear registry cache after DOM changes
- ✅ Use selected state colors for badges/clones
- ✅ Test palette changes with clones visible
When Modifying Layout System
- ✅ Keep percentage calculations synchronized between components
- ✅ Update both state systems (persistence + reactive)
- ✅ Verify totals equal 100%
- ✅ Test debouncing behavior during drag operations
When Adding New Components
- ✅ Follow template ref injection pattern
- ✅ Register in global element registry if searchable
- ✅ Clear cache when adding/removing DOM elements
- ✅ Use composable singletons for shared state
When Debugging Issues
- 🔍 Check element registry cache with
printRegistryStats() - 🔍 Verify percentage totals with
getCurrentViewerPercentages() - 🔍 Test clone visibility with
testCloneVisibility(jobNumber) - 🔍 Validate badge colors with
testCloneAndBadgePaletteUpdate(jobNumber)
⚠️ Breaking Change Warning
These inter-relationships are fragile and interdependent. Changes to one system often affect multiple others. The debug tools created during modernization (debug-*.mjs files) should be used to verify system integrity after any modifications.
Key insight: This application's complexity emerges from the interaction between systems, not individual components. Understanding these critical dependencies is essential for maintaining system stability and implementing new features successfully.
🚨 Fail-Fast Dependency Initialization Strategies
The application implements a multi-layered "Fail Fast" approach with different strategies for different types of dependencies, ensuring that dependency problems are caught early in development while maintaining robustness in production.
🚨 1. Immediate Fail-Fast Validation (Development Safety)
Strategy: Throw errors immediately when critical dependencies are missing (e.g. bullsEye, timeline, colorPalette, sceneContainer in useCardsController). No fallbacks—missing Provide/Inject services surface at once with clear error messages.
Benefits:
- ✅ Immediate detection of missing Vue Provide/Inject services
- ✅ Clear error messages indicating exactly what's missing
- ✅ No silent failures - problems surface immediately during development
- ✅ Forces proper dependency setup in parent components
🎯 2. Reactive Readiness Tracking (Smart Waiting)
Strategy: A reactive computed tracks when all required services report ready (isReady). Logging shows which dependency is not ready so initialization proceeds only when the full set is available.
Benefits:
- ✅ Real-time readiness monitoring with detailed logging
- ✅ Granular visibility into which specific dependencies are/aren't ready
- ✅ Reactive coordination - automatically proceeds when all deps are ready
- ✅ Debug-friendly with comprehensive logging
🏗️ 3. Service Readiness Declaration (Provider Contracts)
Strategy: Each provided service exposes a standard isReady (computed or ref). Parents in AppContent declare when bullsEye, timeline, colorPalette, and sceneContainer are ready so consumers can wait on a single contract.
Benefits:
- ✅ Standardized readiness interface across all services
- ✅ Provider responsibility - services control their own readiness declaration
- ✅ Vue reactivity integration - uses computed/ref for automatic updates
- ✅ Consumer simplicity - consumers just check
service.isReady.value
⏰ 4. Idempotent Initialization with Promises (Async Coordination)
Strategy: State initialization runs once; the same promise is returned for every caller so multiple consumers can await without duplicate work or races (e.g. in stateManager).
Benefits:
- ✅ Single initialization guarantee - prevents double-initialization bugs
- ✅ Promise-based coordination - multiple consumers can await readiness
- ✅ Race condition protection - safe for concurrent access
- ✅ Predictable timing - always returns the same promise
🔄 5. Retry with Timeout (Graceful Recovery)
Strategy: If a required DOM element (e.g. scene-plane) is not yet available, the controller schedules a retry after a short delay instead of failing permanently, so mount-order edge cases recover automatically.
Benefits:
- ✅ Handles timing edge cases - DOM elements that aren't ready yet
- ✅ Automatic recovery - doesn't require manual intervention
- ✅ Bounded retries - prevents infinite loops
- ✅ Visible debugging - logs retry attempts
🛡️ 6. Safe Access Patterns (Runtime Protection)
Strategy: Optional chaining and existence checks before calling registry or service methods (e.g. getScenePlane, clearAllCache) so timing or missing deps do not cause runtime crashes.
Benefits:
- ✅ Runtime robustness - prevents crashes from timing issues
- ✅ Graceful degradation - continues functioning with reduced capabilities
- ✅ Development safety - won't crash during rapid development changes
- ✅ Production reliability - handles edge cases in production
👁️ 7. Template Ref Watchers (DOM Readiness)
Strategy: Watchers on template refs (e.g. focalPointRef) run when refs become available; they inject the element into services and register it in the global element registry, with immediate: true so already-mounted refs are handled.
Benefits:
- ✅ DOM timing coordination - automatically detects when elements are mounted
- ✅ Immediate execution -
{ immediate: true }catches already-available refs - ✅ Reactive registration - automatically updates when refs change
- ✅ Centralized registry - makes elements available to other components
🎪 8. Global Singleton Coordination (Legacy Bridge)
Strategy: Some singletons (e.g. selectionManager, resumeListController) are attached to window for legacy or cross-cutting access. Call sites validate presence and fail fast with clear errors when a singleton is missing.
Benefits:
- ✅ Cross-cutting coordination - bridges Vue and legacy systems
- ✅ Explicit error messages - clear debugging when singletons are missing
- ✅ Development visibility - easy to inspect global state in dev tools
- ✅ Fallback coordination - provides alternative access path
📊 Strategy Summary by Use Case
| Use Case | Strategy | When to Use | Fail-Fast Level |
|---|---|---|---|
| Critical Vue Services | Immediate Throw + Reactive Tracking | Vue Provide/Inject dependencies | 🚨 IMMEDIATE |
| Async Resources | Promise-based + Idempotent | File loading, network requests | ⏰ COORDINATED |
| DOM Elements | Template Ref Watchers | When elements must exist before use | 👁️ REACTIVE |
| Timing Edge Cases | Retry with Timeout | DOM not ready, race conditions | 🔄 RECOVERY |
| Legacy Integration | Global Singletons + Validation | Cross-system communication | 🎪 BRIDGED |
| Runtime Safety | Safe Access Patterns | Production robustness | 🛡️ GRACEFUL |
🎯 Development vs Production Balance
The application cleverly balances "Fail Fast" development with production robustness:
- Development: Immediate errors with detailed logging expose problems early
- Production: Safe access patterns and retries prevent crashes from timing issues
- Both: Reactive readiness tracking provides real-time visibility into system state
This layered approach ensures that dependency problems are caught early in development while maintaining robustness in production, providing multiple fallback strategies for different types of initialization dependencies.
Application Overview
The flock
Large business cardsare used to describe various jobs, each with its role,
employer, and time period. These cards are larger, slowing moving, and further away from your view. Each business card is surrounded by its flock of
smaller skill cards that hover around them. Business cards include a list of skills learned or used during that job. Click on any skill to see that skill card. Each skill card includes a back arrow that points to each job that used that skill.
Mouse motion over the scene plane causes your point of view to move around, using motion parallax and a fuzzy depth of field to give the flock its sense of depth. Besides focal-point–driven motion parallax, a sense of 3D depth is enhanced by blur and darkness that increase with distance from the view plane.
Moving the mouse vertically also causes your view to slide over the career timeline shown at the scene edge.
Click on a business card or skill card to make it pop into focus at the top of the flock, and to see its details in the details area. Use the color palette selector to enhance the viewing experience.
Each job has a description (from your uploaded resume). Skills, terms, and tools can be marked in the resume details editor. Each skill is displayed as a clickable link that pops up its skill card.
Web links are marked up with parens and are displayed with clickable world wide web icons
.
Image links are marked up with curly braces and are displayed as clickable image icons <img src="static_content/icons/icons8-img-16-white.png'>.
A skill card is created for each square bracketed phrase in the job details. A skill is typically used over many jobs, so each skill card has
one or more return icons
that serve as clickable links back to jobs that used that skill. The number
of return icons indicates the number of jobs and the amount of time used to hone that skill.
Run the resume-flock career resume web app using Cursor with Claude Code agent
Clone this repo to your local development folder
cd <your-local-dev-folder>
git clone git@github.com:sbecker11/resume-flock.git
cd <your-local-dev-folder>/resume-flock
Resume parser: DOCX and PDF resumes are parsed at runtime. The app uses the resume-parser package to convert uploaded resumes into flock data (jobs and skills). To work on or extend the parser locally, clone the resume-parser repo:
git clone git@github.com:sbecker11/resume-parser.git
Cursor IDE with Vite
The app is built with Vite and uses ES modules. Use Cursor (or another editor) with the project's dev script:
- Install dependencies:
npm install - Start the dev server:
npm run dev
This runs the Vite dev server (frontend) and the backend API. Open the URL shown in the terminal (e.g. http://localhost:5174) in your browser. Upload a DOCX or PDF resume to explore your career as business cards and skill cards.
Behold your flock of skill cards and business cards
Naming Conventions
This project follows consistent naming conventions to avoid conflicts and improve code clarity:
File Naming
- Composables (Vue reactive functions):
useXxx.mjs- Example:
useViewport.mjs,useBullsEye.mjs,useFocalPoint.mjs
- Example:
- Modules (regular JavaScript modules):
xxxModule.mjs- Example:
sceneContainerModule.mjs,keyDownModule.mjs,parallaxModule.mjs
- Example:
Variable Naming
- DOM Elements:
xxxElement- Example:
sceneContainerElement,resumeContentDivElement
- Example:
- Imported Modules:
xxx(no suffix)- Example:
sceneContainer,keyDown,parallax
- Example:
- Vue Refs:
xxx(no suffix)- Example:
viewport,bullsEye,focalPoint
- Example:
- Computed Properties:
xxx(no suffix)- Example:
focalPointStyle,sceneContainerStyle
- Example:
Architecture
The application uses a reactive chain: viewport drives bullsEye, which drives aimPoint, which drives focalPoint; Vue composables wire these together.
Benefits:
- Clear distinction between different types of code
- Prevents naming conflicts between DOM elements and imported modules
- Consistent patterns throughout the codebase
- Vue's reactivity system handles all dependency tracking automatically
Module Initialization
Initialization is handled in modules/components/AppContent.vue using Vue's reactive architecture:
Initialization Sequence:
- Core services (keyDown, sceneContainer)
- Data controllers (cardsController, resumeItemsController, resumeListController)
- Assembly (scenePlane, parallax)
- Layout systems (resizeHandle, timeline)
- Template rendering (isLoading = false, nextTick)
- Reactive systems (viewport, bullsEye, aimPoint, focalPoint)
- Final services (sceneViewLabel, autoScroll)
Modern Vue 3 Reactive Architecture
This project uses Vue 3 Composition API patterns to provide clean, maintainable, and performant reactive state management:
1. Composable-Based State Management
The project uses Vue 3 composables (e.g. useBadgeToggle, useResizeHandle) that expose shared reactive refs and methods. State lives in composables so components stay decoupled and get automatic UI updates.
Features:
- Reactive references: Automatic UI updates when state changes
- Global state sharing: Singleton pattern with Vue reactivity
- Loose coupling: Components import only what they need
2. Vue 3 Watcher System
Components use watchers so that when shared state (e.g. badge visibility, selection) changes, side effects run—for example showing or hiding badges for the selected job so UI stays in sync without manual wiring.
Benefits:
- Automatic reactivity: State changes trigger appropriate actions
- Performance optimized: Only runs when watched values change
- Declarative: Clear cause-and-effect relationships
3. Template Refs and DOM Access
Components use template refs (e.g. sceneContainerRef, resizeHandleRef) instead of getElementById. Refs are set after mount, so DOM access and event binding happen in onMounted or in watchers that run when refs become available.
4. Event-Driven Communication
Components communicate via custom events (e.g. resize-handle-changed, job-selected) with detail payloads, and by reading shared reactive state from composables so multiple parts of the app stay in sync without direct references.
5. TypeScript Integration
Components use typed props and emits (e.g. ResizeHandleProps, ResizeHandleEmits) with defineProps and defineEmits so the contract between parent and child is clear and the tooling can catch mismatches.
6. Performance-Optimized Patterns
Resize and other high-frequency updates use debouncing (e.g. 150ms during drag) so the UI does not cascade; when not dragging, events dispatch immediately. Computed properties avoid redundant work by depending only on reactive inputs.
7. Module Organization Strategy
Clean separation: components/ (Vue SFCs with script setup), composables/ (reactive state and logic), types/ (TypeScript definitions), utils/ (pure helpers), core/ (managers and services), scene/ (scene and card logic).
🎯 Key Benefits of Vue 3 Architecture
- Reactive by Default: Automatic UI updates when state changes
- Type Safety: Full TypeScript integration prevents runtime errors
- Performance Optimized: Fine-grained reactivity and debounced events
- Developer Experience: Hot module replacement and reactive debugging
- Maintainable: Clear separation with composables and template refs
- Modern Standards: Following official Vue 3 best practices
This Vue 3 Composition API architecture provides a modern, maintainable, and performant foundation that eliminates the complexity of manual dependency management while providing superior reactivity and type safety.
Hover Flickering Fix for Overlapping Elements
The application contains business card elements (cDivs) that can visually overlap due to their absolute positioning and parallax effects. When multiple cDivs overlap under the mouse cursor, rapid mouseenter/mouseleave events can cause flickering as the elements compete for hover state.
Problem Analysis
- Root Cause: Overlapping absolutely positioned elements receive rapid mouse events
- Z-index Ineffective: Unique z-index values don't prevent event conflicts between overlapping elements
- CSS Transitions Avoided: Project eschews transitions for immediate state changes
- Event Competition: Multiple elements simultaneously trigger hover state changes
Solution Implementation
The fix uses a tracked hover state approach with DOM reordering and event capture:
1. Tracked Hover State
CardsController keeps a single currently hovered element. Incoming mouseenter is ignored if it is for the same element, avoiding redundant updates and flicker.
2. DOM Reordering Strategy
- Hovered Element Positioning: The hovered cDiv is moved in the DOM to position N-1 (just before the selected clone at N) so it receives pointer events on top.
- Original Position Tracking: The original next sibling (or a marker) is stored (e.g. in a data attribute) so the element can be restored on mouseleave.
- Atomic Operations:
insertBefore()is used for a single, predictable DOM update.
3. Event Capture and Propagation Control
mouseenter is attached in the capture phase and propagation is stopped so overlapping elements do not compete; only the intended handler runs for the hovered card.
4. Position Restoration
On mouseleave, the element is reinserted using the stored original-next-sibling reference (or appended if it was the last child), restoring DOM order and avoiding stale stacking.
Key Insights
- DOM Order vs Visual Layer: DOM reordering doesn't affect visual stacking for absolutely positioned elements (z-index controls visual layer)
- Event Conflict Prevention: The fix prevents rapid event switching rather than visual layering conflicts
- Single Hover Constraint: Only one element can be hovered at a time, eliminating competition
- Atomic Operations:
insertBefore()automatically removes and repositions elements without changing total child count
Result
- No More Flickering: Overlapping elements no longer compete for hover state
- Smooth Transitions: Moving between overlapping elements works seamlessly
- Preserved Functionality: All hover behaviors maintained while eliminating flickering
- Paired Selection/Hover: Both cDiv and rDiv elements now display simultaneous selection and hover states through centralized SelectionManager coordination
- Reversible Implementation: Changes can be easily reverted if needed
This solution demonstrates how event management and DOM manipulation can solve visual interaction issues in complex overlapping UI scenarios.
Separation of Concerns: Modules, Components, and Composables
This project implements a clear separation of concerns across different architectural layers to maintain clean, maintainable code:
Modules (*.mjs files)
Purpose: Pure business logic and utilities with no UI dependencies
Characteristics:
- Framework-agnostic: Can be used in any JavaScript environment
- Stateless: Focus on pure functions and data processing
- Reusable: Can be imported into any other module or component
- Testable: Easy to unit test in isolation
Examples: Pure helpers (e.g. mathUtils, colorUtils), global state (stateManager), and other framework-agnostic logic live in .mjs modules.
Responsibilities:
- Data processing and transformation
- Business logic implementation
- Utility functions
- State management
- Event handling
Components (*.vue files)
Purpose: Vue.js UI components that handle presentation and user interaction
Characteristics:
- Framework-specific: Built for Vue.js ecosystem
- Reactive: Use Vue's reactivity system for UI updates
- Template-driven: Combine HTML templates with JavaScript logic
- Lifecycle-aware: Leverage Vue's component lifecycle hooks
Examples: AppContent (main shell), ResumeContainer (resume list and details), and other Vue SFCs use setup(), props, and emits for UI and interaction.
Responsibilities:
- UI rendering and presentation
- User interaction handling
- Component lifecycle management
- Props and event communication
- Template logic
Composables (use*.mjs files)
Purpose: Vue 3 composables that provide reactive state and logic
Characteristics:
- Reactive: Use Vue's
ref(),computed(), andwatch() - Composable: Can be combined and reused across components
- Stateful: Maintain reactive state across component instances
- Framework-specific: Built for Vue 3 Composition API
Examples: useViewport, useColorPalette, and other use*.mjs composables expose refs and methods (e.g. viewportState/updateViewport, currentPalette/applyPalette) for reactive, shareable state.
Responsibilities:
- Reactive state management
- Cross-component logic sharing
- Side effect handling
- Event subscription management
- Computed property calculations
Singleton Pattern Implementation Strategy
The project uses singleton patterns strategically to manage global state and prevent dependency cycles:
When to Use Singletons
✅ Good Candidates:
- Global state managers:
stateManager,selectionManager - Event systems:
eventBus,navigationAPI - Resource managers:
viewport,colorPalette - Configuration systems: App settings and preferences
- Service locators: Central access points for shared services
❌ Avoid Singletons For:
- UI components: Use Vue components instead
- Pure utilities: Use regular modules
- Temporary state: Use composables or component state
- User-specific data: Use props or reactive state
Singleton Implementation Patterns
1. Classic Singleton Pattern
StateManager and similar cores use a static instance check in the constructor so only one instance exists; the module exports that single instance (e.g. stateManager).
2. EventTarget Singleton
SelectionManager extends EventTarget and uses the same constructor guard; one instance is exported so selection and custom events are shared app-wide.
3. Vue Composable Singleton
Composables like useViewport can keep a module-level _instance and return it on subsequent calls so all components share the same reactive state and methods.
Singleton Conversion Process
When converting a class to a singleton pattern:
Step 1: Analysis
Identify a class that is currently instantiated once and exported (e.g. ResumeItemsController with items and isInitialized). That candidate is turned into a singleton.
Step 2: Add Singleton Pattern
In the constructor, add a check for ClassName.instance; if it exists, return it. Otherwise set ClassName.instance = this before any other initialization. Keep a single module-level export.
Step 3: Update Dependencies
Call sites import the singleton from its module and use it directly; no new or factory calls—the singleton is already initialized.
Benefits of This Separation
- Clear Responsibilities: Each layer has a specific purpose
- Testability: Modules can be tested independently
- Reusability: Logic can be shared across different contexts
- Maintainability: Changes are isolated to specific layers
- Performance: Reactive updates only affect necessary components
- Scalability: New features can be added without affecting existing code
Migration Guidelines
When adding new functionality:
- Start with modules for pure business logic
- Use composables for reactive state management
- Create components for UI presentation
- Apply singletons only for truly global state
- Use events for cross-component communication
This layered approach ensures that the application remains maintainable, testable, and scalable as it grows in complexity.
Layout Architecture: Container Width Management
🏗️ Critical Layout Constraint: Available Width Calculation
IMPORTANT: The total available width for scene and resume is window width minus the fixed 20px resize handle. All percentage-based layout (scene/resume split, drag, persistence) must use this available width, not raw window width.
This constraint affects:
- Percentage calculations in all components
- Container sizing and positioning
- Drag operation coordinate transformations
- Layout persistence in app_state.json
📐 Layout Math Verification
All layout calculations must satisfy: scene width + resume width + 20px handle = window width. Example: 1920px window → 1900px available; 60% scene = 1140px, 40% resume = 760px; 1140 + 760 + 20 = 1920.
⚠️ Common Implementation Errors
❌ WRONG: Using window.innerWidth directly for percentage-to-pixel conversion. ✅ CORRECT: Subtract the handle width (20px) first, then apply percentages to that available width.
🔧 Implementation Locations
This constraint is implemented in:
useResizeHandle.mjs- Core percentage calculationsAppContent.vue- Layout percentage displaySceneContainer.vue- Scene width calculationsappStore.mjs- State managementapp_state.json- Persistent storage (see docs/LOCAL-FILES-AND-SECRETS.md for local files and secrets, including.env)
🧮 Percentage Calculation Pattern
Use a constant handle width (20px), compute availableWidth = windowWidth - HANDLE_WIDTH, then derive scene/resume pixel widths from percentages of available width. For drag, convert pixel position back to a 0–100% value using available width and clamp.
ResizeHandle: The Layout Orchestrator
The resizeHandle is a sophisticated 20px-wide interactive control panel that serves as the central layout orchestrator for the entire application. It's positioned between the scene and resume containers, providing both visual separation and interactive layout control.
🎯 Novel Innovation: Multi-Functional Control Hub
The resizeHandle represents a novel approach to layout management that goes beyond traditional resize handles by combining layout control, interaction modes, and visual feedback in a single compact interface. This multi-functional control hub eliminates the need for separate UI controls while providing intuitive access to the application's core interaction modes.
🏗️ Container Hierarchy
The app-container fills the viewport. Inside it: scene-container has dynamic width; resume-container uses flex, with a fixed 20px left strip that holds the resize-handle, and a flex-grow right area for resume content.
🎮 Interactive Features
Layout Control
- Drag to resize: Click and drag to adjust scene/resume container proportions
- Percentage-based: Layout changes are calculated as percentages of total width
- Smooth transitions: Layout changes are animated for visual continuity
- State persistence: Layout preferences are saved and restored
Interaction Modes
- Stepping mode: Toggle between smooth and stepped layout changes
- Step count control: Adjust the number of layout steps (1-10)
- Visual feedback: Real-time preview of layout changes
- Keyboard shortcuts: Arrow keys for precise layout adjustments
Visual Design
- Minimal footprint: Only 20px wide to maximize content space
- Hover effects: Visual feedback on mouse interaction
- Color integration: Adapts to current color palette
- Responsive design: Adapts to different screen sizes
🔧 Technical Implementation
Composable Architecture
useResizeHandle exposes reactive percentage and stepCount (1 = free drag, 2–10 = discrete steps), computed scene/resume percentages, and mouse handlers that start drag and update layout from clientX. Layout is derived from container width and optional stepping.
State Management
updateLayout turns clientX into a percentage of container width. If stepCount > 1, the value is snapped to steps; otherwise it’s free. The result is clamped 0–100, written to the composable state, and persisted (e.g. AppState + saveState).
Event Integration
When layout changes, container widths are set from the current percentages and a layoutChanged (or similar) custom event is dispatched with scene and resume percentages so the rest of the app can react.
🎨 Visual Innovations
Smooth Animations
- CSS transitions: Layout changes are smoothly animated
- Performance optimized: Uses transform and opacity for 60fps animations
- Easing functions: Natural-feeling animation curves
- Reduced motion: Respects user's motion preferences
Responsive Behavior
- Minimum widths: Ensures containers remain usable
- Maximum widths: Prevents containers from becoming too large
- Touch support: Works with touch devices and mobile
- Keyboard accessibility: Full keyboard navigation support
🔄 Integration with Application Architecture
Component Communication
- Event-driven updates: Layout changes trigger events for other components
- Reactive updates: Vue reactivity ensures UI consistency
- State synchronization: Layout state is synchronized across components
- Performance monitoring: Layout changes are optimized for performance
User Experience
- Intuitive interaction: Natural drag-to-resize behavior
- Visual feedback: Clear indication of layout changes
- State persistence: Layout preferences are remembered
- Accessibility: Full keyboard and screen reader support
This resizeHandle represents a significant advancement in layout management, providing a unified interface for both layout control and interaction mode management while maintaining the application's visual coherence and performance.
Resize Handle Performance Optimization: Debounced Event Architecture
🚀 Problem Solved: Cascading Resize Chain
The resize handle drag was causing a cascade: ResizeHandle drag → resize events → ResumeContainer resize → ResumeListScrollContainer → rDiv recalculation → jerky feedback. Firing resize events on every mouse move triggered expensive layout work across the tree.
⚡ Solution: Debounced Event Dispatching
Smart Event Management System
During drag, resize-handle-changed and scene-width-changed are not fired on every move; the latest scene width is stored and a 150ms timer is set. When the timer fires, one event pair is dispatched. If not dragging (or when explicitly requested), events dispatch immediately so step clicks and initial layout stay accurate.
Drag End Optimization
On mouse up, dragging is cleared and any pending debounced events are flushed immediately (clear timeout and dispatch with the last pending width), so the final layout is correct and dragStateManager/updateLayout run with the current percentage.
🎯 Performance Benefits
Before Optimization:
- ❌ Events fired on every mouse move (dozens per second)
- ❌ Cascading layout recalculations during drag
- ❌ Jerky, unresponsive resize handle feedback
- ❌ Performance degradation with complex layouts
After Optimization:
- ✅ Smooth dragging: Events debounced during drag operations
- ✅ Responsive UI: Visual feedback updates without cascade interruptions
- ✅ Final accuracy: Layout updates correctly when drag ends
- ✅ Step operations: Button clicks still work instantly (no debouncing)
- ✅ 150ms debounce: Optimal balance between performance and responsiveness
🏗️ Architecture Highlights
Intelligent Event Classification
- Drag operations: Debounced to prevent cascade
- Step operations: Immediate dispatch (no performance impact)
- Initialization: Immediate dispatch for setup accuracy
State Management
_eventDebounceTimeoutId: Tracks pending debounced events_pendingSceneWidth: Stores the latest width value during debouncingisDragging.value: Controls debouncing behavior
Graceful Cleanup
- Pending events flushed immediately on drag end
- No event loss during transitions
- Maintains final layout accuracy
This debounced event architecture eliminates the cascading resize performance bottleneck while preserving the responsive user experience and accurate final layout positioning.
Resume List Scroll Container: Novel Virtual Scrolling Architecture
This project implements a sophisticated resume list scroll (wrapping) system that provides seamless performance for large datasets while maintaining visual continuity and state preservation. The ResumeListScrollContainer represents a novel approach to virtual scrolling that goes beyond traditional implementations.
🎯 Core Innovation: Hybrid Virtual Scrolling
Unlike traditional virtual scrolling that only renders visible items, this system uses a hybrid approach that combines:
- Virtual positioning: Items are positioned absolutely with calculated heights
- Physical rendering: All items are actually rendered in the DOM
- Intelligent cloning: Selected items get clones for interaction
- State preservation: Scroll position and selection state are maintained
🏗️ Architecture Overview
ResumeListScrollContainer holds all items with computed positions, a visible subset, references to the scrollport and content holder, a cache of item heights, and a clone manager. Items are absolutely positioned; the container manages which are visible and handles clones for the selected item.
📊 Novel Height Calculation System
Dynamic Height Measurement
Heights are measured by temporarily setting height to auto, forcing layout (e.g. reading offsetHeight), then reading scrollHeight; results are cached by item id. Positions are computed by stacking (currentTop += height), then each item gets absolute top and height. A force flag allows invalidating the cache when content changes.
Height Caching Strategy
- Lazy measurement: Heights are calculated only when needed
- Persistent cache: Heights are stored and reused
- Force recalculation: Heights can be recalculated on demand
- Content-aware: Adapts to dynamic content changes
🎭 Intelligent Clone Management
Clone Creation System
Clones are created with cloneNode(true), tagged with classes and data attributes (e.g. resume-list-clone, cloneType, originalIndex). Duplicate IDs are stripped. Palette is applied to the clone when it has a color index; hover/selected classes are cleared so the clone can show selected state independently.
🔄 Advanced Scroll Position Management
Scroll Position Preservation
On scroll, the visible range (scrollTop to scrollTop + clientHeight) is computed and updateVisibleItems runs. Scroll position is preserved by storing a ratio (scrollTop / scrollHeight) and restoring it after content updates so the user’s place is not lost.
Momentum Scrolling
Wheel events update a velocity from deltaY; that velocity is applied to scrollTop and then decayed (e.g. multiplied by 0.95) so scrolling feels natural and continues briefly after the wheel stops.
🎨 Visual Continuity Features
Smooth Transitions
Visible items are determined by comparing item top/height to the visible range. Items entering visibility are shown and faded in (opacity transition); items leaving are faded out and then hidden. visibleItems is updated so the list stays in sync with scroll.
Content-Aware Styling
Each item gets palette application when it has a color index. Selection and hover state are reflected by adding or removing selected/hovered classes so the list matches global selection and hover.
⚡ Performance Optimizations
Efficient Rendering
- Position Caching: Item positions are calculated once and cached
- Selective Updates: Only changed items are updated
- Debounced Resize: Resize events are debounced to prevent excessive calculations
- RequestAnimationFrame: Smooth animations using RAF
Memory Management
On destroy, scroll and wheel listeners are removed, resize and momentum timeouts/animation frames are cleared, content is cleared, and caches (itemHeights, allItems, visibleItems) are reset so the container can be garbage-collected and recreated cleanly.
🎯 Novel Features
1. Content-Aware Height Calculation
- Dynamic measurement: Heights adapt to content changes
- Force recalculation: Heights can be updated on demand
- Cached results: Performance optimization through caching
2. Intelligent Clone System
- Multiple clone types: Different clones for different purposes
- Styling preservation: Clones maintain visual consistency
- State synchronization: Clones stay in sync with originals
3. Advanced Scroll Management
- Position preservation: Scroll position maintained during updates
- Momentum scrolling: Natural-feeling scroll physics
- Smooth transitions: Items fade in/out smoothly
4. State Integration
- Selection preservation: Selected items remain selected during scroll
- Visual state: Hover and selection states are maintained
- Palette integration: Items maintain color theming
🔄 Integration with Application Architecture
Event-Driven Updates
ResumeListController creates the ResumeListScrollContainer with the resume wrapper and content div, passes in the biz resume divs, and registers callbacks: onItemSelect delegates to selectionManager.selectJobNumber; onScroll persists scroll position to AppState and saves state so scroll is restored on reload.
State Synchronization
- Global state: Scroll position and selection state are persisted
- Event system: Changes are communicated via events
- Reactive updates: Vue reactivity ensures UI consistency
🎨 Visual Innovations
Seamless Transitions
- Opacity transitions: Items fade in/out smoothly
- Height animations: Content changes are animated
- Clone positioning: Clones appear/disappear naturally
Performance Indicators
- Scroll performance: 60fps scrolling with large datasets
- Memory efficiency: Minimal memory footprint
- Responsive design: Adapts to different screen sizes
This Infinite Scrolling Container represents a significant advancement in virtual scrolling technology, providing the performance benefits of virtual scrolling while maintaining the visual richness and interaction capabilities of fully rendered content. It's particularly well-suited for applications that require both performance and visual fidelity, such as this resume visualization system.
Development history
version 1.2 March 2025
- High-level features: resume-parser (DOCX/PDF at runtime, optional LLM skill merging), resume-selector, resume-details-editor (Meta / Other / Skills / Jobs tabs), job-details-editor (Jobs tab), job-skills-selector (JobSkillEditor), resume-print (printable HTML). README now introduces these in a “High-level functionality” section.
- 3D Settings: parallax scale (near/far), blur, brightness, saturation at max Z; modal from 3D button (resize handle and next to color palette). Base z-scale (0.9→0) multiplied by user scale.
- Run instructions: Cursor IDE with Vite (
npm run dev); removed VSCode + LiveServer. Resume data from DOCX/PDF parsed at runtime; removed Excel/jobs.xlsx workflow. - README: removed Vue 3 migration narrative, reduced Color Palette and State System sections, removed Future Work and “illustrious career” Excel customization, updated resume-parser description.
version 1.1 July 8, 2024
- Updated installation and customization instructions in README.md
- Deployed latest to github
https://sbecker11.github.io/resume-flock
version 1.0 March 8, 2024
- CURRENT_DATE in job [end] replaced with first day of next month but displayed as 'working'
- Always scroll newly selected bizCardDiv (and optionally its bizCardLineItem) into view in selectTheBizCard
- not started
in highlightTheDivCardBackArrow
unhighlightTheHighlightedDivCardBackArrow
update theHighlightedDivCardBackArrow
find the CardDivLineItemTagSpan of theHighlightedDivCardBackArrow
call highlightTheCardDivLineItemTagSpan
in highlightTheCardDivLineItemTagSpan
unhighlightTheHighlightedCardDivLineItemTagSpan
update theHighlightedCardDivLineItemTagSpan
find the cardDivCardBackArrow of theHighlightedCardDivLineItemTagSpan
call highlightTheCardDivCardBackArrow
version 0.9: January 4, 2024
version 0.8: January 1, 2024
version 0.7: November 18, 2023
- Default timeline year avg of min-max years
- Auto-computing timeline min-max years
- Interpolating CURRENT_DATE in xlsx file
- GoLive link with port 5500 or 5501
- applying parallax on the target for restoreSavedStyle
- replaced addAimationEndListener with endAnimation on a timeout
- BizCards are now animating to the top, but not staying there
- Bizdards return to original position after losing focus
- fixed selectNextBizCard
- added links to three.mjs examples
- added links to Virtual Munsell Color Wheel
- added select all skills button
- added selectNext, selectAll, and clearAll buttons
version 0.6: July 3, 2023
- Upgraded static website to use ES6 modules, thus requiring a local webserver that supports ES6.
- The focal point now eases towards the mouse when it enters the scene-plane area.
- The focal point now eases toward the bullsEye when it leaves the scene-plane areas.
- 8 MB animated gif
version 0.5: June 26, 2023
- A flock of small skill cards and larger business cards float over the scene-plane column (layout can put the scene on the left or right).
- A timeline is displayed at ground level, to visualize the date range of employment for each business card.
- A 3-D parallax effect on cards is controlled by the "focalPoint", which tracks the mouse while over the scene-plane.
- Add line items to the resume column by selecting business cards.
- Select a skill card or resume line item by clicking it, click again to deselect it.
- Selected skill cards and line-items have a red-dashed border.
- Once selected, a skill card or business card is temporarily moved above the flock where it is not subject to motion parallax.
- Click on a skill card to select and scroll its resume line item into view.
- Click on a resume line item to select and scroll its skill card into view.
- The scene-plane viewPort shows "BullsEye" with a plus sign at scene-plane center, where parallax effect is zero.
- FocalPoint defaults back to the viewPort center BullsEye when it leaves the scene-plane.
- The focalPoint starts tracking the mouse as soon as it re-enters the scene-plane area.
- Scene-div auto-scrolling starts when the focalPoint is in top or bottom quarter of the scene-plane.
- Autoscrolling stops when the focalPoint moves to viewPort center and when the mouse leaves the scene-plane.
- Click on a resume line item's top-right delete button to delete it.
- Click on the bottom-right green next button to open and select the resume line item for the next business card.
- Skill cards inherit the color of their parent business card.
- Click the underlined text in a business card's resume line item to select and bring that skill card into view over the flock.
version 0.4: June 18, 2023
- scripted process to convert WordPress media dump xml file into a javascript file of img paths of resized local img files (not included in github) for html inclusion.
- scripted process to convert job data into a javascript file of job objects for html inclusion (resumes now parsed from DOCX/PDF at runtime).
- right side now has fixed header and footer and an auto-scolling content.
- click on any skill card or underlying business card to add a new deletable line item to the right column.
version 0.3: June 7, 2023
- downloads bizCards from local jobs.csv file
- BUT only works when running local instance of http-server from the version3 folder
- click on a red-div to open a new pink line-item in the resume-container
version 0.2: June 6, 2023
- faded timeline on right side
- linear gradiens at top and bottom
- bizCards are purple and far away from viewer
- cards are red and closer to viewer
- cards turn yellow on rollover
- horizontal and vertical mouse motion induce motion parallax
- parallax decreases as distance to viewer increases
- manual vertical scrolling is supported though scrollbar is invisible
- scene-plane scrolls vertically when mouse approaches top and bottom
- right column for diagnostics
version 0.1 - May 23, 2023
- randomized div sizes, locations, and z-index
- z-index affects opacity and brightness
- autogenerated imgs from web
- vertical stack of divs moved to scene-container center on load and resize
- vertical scrollbar
- fat middle line for diagnositcs
- right column for diagnostics
v1.2lkPTc5MGI3NjExd2N3bmVkYmIzajI0cGhydjhxOThzOGs1aWYxcDRvdWxvdHd6ODQzNSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/OzLgdH8NizyWcLH3Ux/giphy.gif)