import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js'; export class OnboardingView extends LitElement { static styles = css` * { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; cursor: default; user-select: none; margin: 0; padding: 0; box-sizing: border-box; } :host { display: block; height: 100%; width: 100%; position: fixed; top: 0; left: 0; overflow: hidden; } .onboarding-container { position: relative; width: 100%; height: 100%; background: #0a0a0a; overflow: hidden; } .close-button { position: absolute; top: 12px; right: 12px; z-index: 10; background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 6px; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease; color: rgba(255, 255, 255, 0.6); } .close-button:hover { background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 255, 255, 0.2); color: rgba(255, 255, 255, 0.9); } .close-button svg { width: 16px; height: 16px; } .gradient-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 0; } .content-wrapper { position: absolute; top: 0; left: 0; right: 0; bottom: 60px; z-index: 1; display: flex; flex-direction: column; justify-content: center; padding: 32px 48px; max-width: 500px; color: #e5e5e5; overflow: hidden; } .slide-icon { width: 48px; height: 48px; margin-bottom: 16px; opacity: 0.9; display: block; } .slide-title { font-size: 28px; font-weight: 600; margin-bottom: 12px; color: #ffffff; line-height: 1.3; } .slide-content { font-size: 16px; line-height: 1.5; margin-bottom: 24px; color: #b8b8b8; font-weight: 400; } .context-textarea { width: 100%; height: 100px; padding: 16px; border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 8px; background: rgba(255, 255, 255, 0.05); color: #e5e5e5; font-size: 14px; font-family: inherit; resize: vertical; transition: all 0.2s ease; margin-bottom: 24px; } .context-textarea::placeholder { color: rgba(255, 255, 255, 0.4); font-size: 14px; } .context-textarea:focus { outline: none; border-color: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.08); } .feature-list { max-width: 100%; } .feature-item { display: flex; align-items: center; margin-bottom: 12px; font-size: 15px; color: #b8b8b8; } .feature-icon { font-size: 16px; margin-right: 12px; opacity: 0.8; } .migration-buttons { display: flex; gap: 12px; margin-top: 24px; } .migration-button { flex: 1; padding: 12px 24px; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; border: 1px solid rgba(255, 255, 255, 0.1); } .migration-button.primary { background: rgba(59, 130, 246, 0.8); color: #ffffff; border-color: rgba(59, 130, 246, 0.9); } .migration-button.primary:hover { background: rgba(59, 130, 246, 0.9); border-color: rgba(59, 130, 246, 1); } .migration-button.secondary { background: rgba(255, 255, 255, 0.08); color: #e5e5e5; border-color: rgba(255, 255, 255, 0.1); } .migration-button.secondary:hover { background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 255, 255, 0.2); } .migration-button:active { transform: scale(0.98); } .navigation { position: absolute; bottom: 0; left: 0; right: 0; z-index: 2; display: flex; align-items: center; justify-content: space-between; padding: 16px 24px; background: rgba(0, 0, 0, 0.3); backdrop-filter: blur(10px); border-top: 1px solid rgba(255, 255, 255, 0.05); height: 60px; box-sizing: border-box; } .nav-button { background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.1); color: #e5e5e5; padding: 8px 16px; border-radius: 6px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; display: flex; align-items: center; justify-content: center; min-width: 36px; min-height: 36px; } .nav-button:hover { background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 255, 255, 0.2); } .nav-button:active { transform: scale(0.98); } .nav-button:disabled { opacity: 0.4; cursor: not-allowed; } .nav-button:disabled:hover { background: rgba(255, 255, 255, 0.08); border-color: rgba(255, 255, 255, 0.1); transform: none; } .progress-dots { display: flex; gap: 12px; align-items: center; } .dot { width: 8px; height: 8px; border-radius: 50%; background: rgba(255, 255, 255, 0.2); transition: all 0.2s ease; cursor: pointer; } .dot:hover { background: rgba(255, 255, 255, 0.4); } .dot.active { background: rgba(255, 255, 255, 0.8); transform: scale(1.2); } `; static properties = { currentSlide: { type: Number }, contextText: { type: String }, hasOldConfig: { type: Boolean }, onComplete: { type: Function }, onClose: { type: Function }, }; constructor() { super(); this.currentSlide = 0; this.contextText = ''; this.hasOldConfig = false; this.onComplete = () => {}; this.onClose = () => {}; this.canvas = null; this.ctx = null; this.animationId = null; // Transition properties this.isTransitioning = false; this.transitionStartTime = 0; this.transitionDuration = 800; // 800ms fade duration this.previousColorScheme = null; // Subtle dark color schemes for each slide this.colorSchemes = [ // Slide 1 - Welcome (Very dark purple/gray) [ [25, 25, 35], // Dark gray-purple [20, 20, 30], // Darker gray [30, 25, 40], // Slightly purple [15, 15, 25], // Very dark [35, 30, 45], // Muted purple [10, 10, 20], // Almost black ], // Slide 2 - Privacy (Dark blue-gray) [ [20, 25, 35], // Dark blue-gray [15, 20, 30], // Darker blue-gray [25, 30, 40], // Slightly blue [10, 15, 25], // Very dark blue [30, 35, 45], // Muted blue [5, 10, 20], // Almost black ], // Slide 3 - Context (Dark neutral) [ [25, 25, 25], // Neutral dark [20, 20, 20], // Darker neutral [30, 30, 30], // Light dark [15, 15, 15], // Very dark [35, 35, 35], // Lighter dark [10, 10, 10], // Almost black ], // Slide 4 - Features (Dark green-gray) [ [20, 30, 25], // Dark green-gray [15, 25, 20], // Darker green-gray [25, 35, 30], // Slightly green [10, 20, 15], // Very dark green [30, 40, 35], // Muted green [5, 15, 10], // Almost black ], // Slide 5 - Migration (Dark teal-gray) [ [20, 30, 30], // Dark teal-gray [15, 25, 25], // Darker teal-gray [25, 35, 35], // Slightly teal [10, 20, 20], // Very dark teal [30, 40, 40], // Muted teal [5, 15, 15], // Almost black ], // Slide 6 - Complete (Dark warm gray) [ [30, 25, 20], // Dark warm gray [25, 20, 15], // Darker warm [35, 30, 25], // Slightly warm [20, 15, 10], // Very dark warm [40, 35, 30], // Muted warm [15, 10, 5], // Almost black ], ]; } async firstUpdated() { this.canvas = this.shadowRoot.querySelector('.gradient-canvas'); this.ctx = this.canvas.getContext('2d'); this.resizeCanvas(); this.startGradientAnimation(); window.addEventListener('resize', () => this.resizeCanvas()); // Check if old config exists if (window.mastermind && window.mastermind.storage) { try { this.hasOldConfig = await window.mastermind.storage.hasOldConfig(); console.log('Has old config:', this.hasOldConfig); this.requestUpdate(); // Force re-render with new hasOldConfig value } catch (error) { console.error('Error checking old config:', error); this.hasOldConfig = false; } } } disconnectedCallback() { super.disconnectedCallback(); if (this.animationId) { cancelAnimationFrame(this.animationId); } window.removeEventListener('resize', () => this.resizeCanvas()); } resizeCanvas() { if (!this.canvas) return; const rect = this.getBoundingClientRect(); this.canvas.width = rect.width; this.canvas.height = rect.height; } startGradientAnimation() { if (!this.ctx) return; const animate = timestamp => { this.drawGradient(timestamp); this.animationId = requestAnimationFrame(animate); }; animate(0); } drawGradient(timestamp) { if (!this.ctx || !this.canvas) return; const { width, height } = this.canvas; let colors = this.colorSchemes[this.currentSlide]; // Handle color scheme transitions if (this.isTransitioning && this.previousColorScheme) { const elapsed = timestamp - this.transitionStartTime; const progress = Math.min(elapsed / this.transitionDuration, 1); // Use easing function for smoother transition const easedProgress = this.easeInOutCubic(progress); colors = this.interpolateColorSchemes(this.previousColorScheme, this.colorSchemes[this.currentSlide], easedProgress); // End transition when complete if (progress >= 1) { this.isTransitioning = false; this.previousColorScheme = null; } } const time = timestamp * 0.0005; // Much slower animation // Create moving gradient with subtle flow const flowX = Math.sin(time * 0.7) * width * 0.3; const flowY = Math.cos(time * 0.5) * height * 0.2; const gradient = this.ctx.createLinearGradient(flowX, flowY, width + flowX * 0.5, height + flowY * 0.5); // Very subtle color variations with movement colors.forEach((color, index) => { const offset = index / (colors.length - 1); const wave = Math.sin(time + index * 0.3) * 0.05; // Very subtle wave const r = Math.max(0, Math.min(255, color[0] + wave * 5)); const g = Math.max(0, Math.min(255, color[1] + wave * 5)); const b = Math.max(0, Math.min(255, color[2] + wave * 5)); gradient.addColorStop(offset, `rgb(${r}, ${g}, ${b})`); }); // Fill with moving gradient this.ctx.fillStyle = gradient; this.ctx.fillRect(0, 0, width, height); // Add a second layer with radial gradient for more depth const centerX = width * 0.5 + Math.sin(time * 0.3) * width * 0.15; const centerY = height * 0.5 + Math.cos(time * 0.4) * height * 0.1; const radius = Math.max(width, height) * 0.8; const radialGradient = this.ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, radius); // Very subtle radial overlay radialGradient.addColorStop(0, `rgba(${colors[0][0] + 10}, ${colors[0][1] + 10}, ${colors[0][2] + 10}, 0.1)`); radialGradient.addColorStop(0.5, `rgba(${colors[2][0]}, ${colors[2][1]}, ${colors[2][2]}, 0.05)`); radialGradient.addColorStop( 1, `rgba(${colors[colors.length - 1][0]}, ${colors[colors.length - 1][1]}, ${colors[colors.length - 1][2]}, 0.03)` ); this.ctx.globalCompositeOperation = 'overlay'; this.ctx.fillStyle = radialGradient; this.ctx.fillRect(0, 0, width, height); this.ctx.globalCompositeOperation = 'source-over'; } nextSlide() { if (this.currentSlide < 5) { this.startColorTransition(this.currentSlide + 1); } else { this.completeOnboarding(); } } prevSlide() { if (this.currentSlide > 0) { this.startColorTransition(this.currentSlide - 1); } } startColorTransition(newSlide) { this.previousColorScheme = [...this.colorSchemes[this.currentSlide]]; this.currentSlide = newSlide; this.isTransitioning = true; this.transitionStartTime = performance.now(); } // Interpolate between two color schemes interpolateColorSchemes(scheme1, scheme2, progress) { return scheme1.map((color1, index) => { const color2 = scheme2[index]; return [ color1[0] + (color2[0] - color1[0]) * progress, color1[1] + (color2[1] - color1[1]) * progress, color1[2] + (color2[2] - color1[2]) * progress, ]; }); } // Easing function for smooth transitions easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; } handleContextInput(e) { this.contextText = e.target.value; } async handleClose() { if (window.require) { const { ipcRenderer } = window.require('electron'); await ipcRenderer.invoke('quit-application'); } } async handleMigrate() { const success = await window.mastermind.storage.migrateFromOldConfig(); if (success) { console.log('Migration completed successfully'); } this.nextSlide(); } async handleSkipMigration() { this.nextSlide(); } async completeOnboarding() { if (this.contextText.trim()) { await mastermind.storage.updatePreference('customPrompt', this.contextText.trim()); } await mastermind.storage.updateConfig('onboarded', true); this.onComplete(); } getSlideContent() { const slides = [ { icon: 'assets/onboarding/welcome.svg', title: 'Welcome to Mastermind', content: 'Your AI assistant that listens and watches, then provides intelligent suggestions automatically during interviews, meetings, and presentations.', }, { icon: 'assets/onboarding/security.svg', title: 'Completely Private', content: 'Invisible to screen sharing apps and recording software. Your secret advantage stays completely hidden from others.', }, { icon: 'assets/onboarding/context.svg', title: 'Add Your Context', content: 'Share relevant information to help the AI provide better, more personalized assistance.', showTextarea: true, }, { icon: 'assets/onboarding/customize.svg', title: 'Additional Features', content: '', showFeatures: true, }, { icon: 'assets/onboarding/context.svg', title: 'Migrate Settings?', content: this.hasOldConfig ? 'Mastermind is a fork of Cheating Daddy. We detected existing Cheating Daddy settings on your system. Would you like to automatically migrate your settings, API keys, and history?' : 'Mastermind is a fork of Cheating Daddy. No previous settings were detected.', showMigration: this.hasOldConfig, }, { icon: 'assets/onboarding/ready.svg', title: 'Ready to Go', content: 'Choose your AI Provider and start getting AI-powered assistance in real-time.', }, ]; return slides[this.currentSlide]; } render() { const slide = this.getSlideContent(); return html`
${slide.title} icon
${slide.title}
${slide.content}
${slide.showTextarea ? html` ` : ''} ${slide.showFeatures ? html`
- Customize AI behavior and responses
- Review conversation history
- Adjust capture settings and intervals
` : ''} ${slide.showMigration ? html`
` : ''}
`; } } customElements.define('onboarding-view', OnboardingView);