Source

utils/themeSystem.js

/**
 * @fileoverview Theme and color system for animated backgrounds
 * @module ThemeSystem
 */

/**
 * @typedef {Object} ColorScheme
 * @property {string} name - Theme name
 * @property {Array<string>} colors - Array of hex colors
 * @property {Object} gradients - Gradient definitions
 * @property {Object} effects - Special effect colors
 */

/**
 * @typedef {Object} ThemeConfig
 * @property {string} name - Theme name
 * @property {ColorScheme} colorScheme - Color scheme for the theme
 * @property {Object} animationSettings - Theme-specific animation settings
 * @property {Array<string>} recommendedAnimations - Animations that work well with this theme
 */

/**
 * Predefined color schemes
 */
export const COLOR_SCHEMES = {
  cyberpunk: {
    name: 'Cyberpunk',
    colors: ['#0D1B2A', '#1B263B', '#415A77', '#778DA9', '#E0E1DD'],
    gradients: {
      primary: ['#0D1B2A', '#415A77'],
      secondary: ['#1B263B', '#778DA9'],
      accent: ['#415A77', '#E0E1DD']
    },
    effects: {
      glow: '#778DA9',
      highlight: '#E0E1DD',
      shadow: '#0D1B2A'
    }
  },

  nature: {
    name: 'Nature',
    colors: ['#2D5016', '#4F772D', '#90A955', '#C8D5B9', '#F8FFF4'],
    gradients: {
      primary: ['#2D5016', '#90A955'],
      secondary: ['#4F772D', '#C8D5B9'],
      accent: ['#90A955', '#F8FFF4']
    },
    effects: {
      glow: '#90A955',
      highlight: '#F8FFF4',
      shadow: '#2D5016'
    }
  },

  ocean: {
    name: 'Ocean',
    colors: ['#03045E', '#0077B6', '#00B4D8', '#90E0EF', '#CAF0F8'],
    gradients: {
      primary: ['#03045E', '#00B4D8'],
      secondary: ['#0077B6', '#90E0EF'],
      accent: ['#00B4D8', '#CAF0F8']
    },
    effects: {
      glow: '#00B4D8',
      highlight: '#CAF0F8',
      shadow: '#03045E'
    }
  },

  sunset: {
    name: 'Sunset',
    colors: ['#FF6B35', '#F7931E', '#FFD23F', '#06FFA5', '#4ECDC4'],
    gradients: {
      primary: ['#FF6B35', '#FFD23F'],
      secondary: ['#F7931E', '#06FFA5'],
      accent: ['#06FFA5', '#4ECDC4']
    },
    effects: {
      glow: '#FFD23F',
      highlight: '#06FFA5',
      shadow: '#FF6B35'
    }
  },

  monochrome: {
    name: 'Monochrome',
    colors: ['#000000', '#333333', '#666666', '#999999', '#ffffff'],
    gradients: {
      primary: ['#000000', '#666666'],
      secondary: ['#333333', '#999999'],
      accent: ['#666666', '#ffffff']
    },
    effects: {
      glow: '#ffffff',
      highlight: '#cccccc',
      shadow: '#000000'
    }
  },

  neon: {
    name: 'Neon',
    colors: ['#FF0080', '#00FF80', '#8000FF', '#FF8000', '#00FFFF'],
    gradients: {
      primary: ['#FF0080', '#00FF80'],
      secondary: ['#8000FF', '#FF8000'],
      accent: ['#FF8000', '#00FFFF']
    },
    effects: {
      glow: '#00FF80',
      highlight: '#FF0080',
      shadow: '#000011'
    }
  },

  space: {
    name: 'Space',
    colors: ['#0C0C20', '#1A1A3E', '#2E2E5C', '#6B46C1', '#A855F7'],
    gradients: {
      primary: ['#0C0C20', '#2E2E5C'],
      secondary: ['#1A1A3E', '#6B46C1'],
      accent: ['#6B46C1', '#A855F7']
    },
    effects: {
      glow: '#A855F7',
      highlight: '#6B46C1',
      shadow: '#0C0C20'
    }
  },

  retro: {
    name: 'Retro',
    colors: ['#FF006E', '#8338EC', '#3A86FF', '#06FFA5', '#FFBE0B'],
    gradients: {
      primary: ['#FF006E', '#8338EC'],
      secondary: ['#3A86FF', '#06FFA5'],
      accent: ['#06FFA5', '#FFBE0B']
    },
    effects: {
      glow: '#06FFA5',
      highlight: '#FFBE0B',
      shadow: '#FF006E'
    }
  },

  cosmic: {
    name: 'Cosmic',
    colors: ['#1A0B3D', '#6B2C91', '#B83DBA', '#EF4CE0', '#FFB3FD'],
    gradients: {
      primary: ['#1A0B3D', '#B83DBA'],
      secondary: ['#6B2C91', '#EF4CE0'],
      accent: ['#EF4CE0', '#FFB3FD']
    },
    effects: {
      glow: '#EF4CE0',
      highlight: '#FFB3FD',
      shadow: '#1A0B3D'
    }
  },

  aurora: {
    name: 'Aurora',
    colors: ['#003366', '#0066CC', '#00CCFF', '#66FFCC', '#CCFF99'],
    gradients: {
      primary: ['#003366', '#00CCFF'],
      secondary: ['#0066CC', '#66FFCC'],
      accent: ['#66FFCC', '#CCFF99']
    },
    effects: {
      glow: '#66FFCC',
      highlight: '#CCFF99',
      shadow: '#003366'
    }
  },

  volcano: {
    name: 'Volcano',
    colors: ['#1A0000', '#8B0000', '#FF4500', '#FF8C00', '#FFD700'],
    gradients: {
      primary: ['#1A0000', '#FF4500'],
      secondary: ['#8B0000', '#FF8C00'],
      accent: ['#FF8C00', '#FFD700']
    },
    effects: {
      glow: '#FF8C00',
      highlight: '#FFD700',
      shadow: '#1A0000'
    }
  },

  forest: {
    name: 'Forest',
    colors: ['#0D2818', '#1A4C32', '#2D7049', '#4A9960', '#66C2A5'],
    gradients: {
      primary: ['#0D2818', '#2D7049'],
      secondary: ['#1A4C32', '#4A9960'],
      accent: ['#4A9960', '#66C2A5']
    },
    effects: {
      glow: '#4A9960',
      highlight: '#66C2A5',
      shadow: '#0D2818'
    }
  },

  ice: {
    name: 'Ice',
    colors: ['#E8F4FD', '#A8DADC', '#457B9D', '#1D3557', '#F1FAEE'],
    gradients: {
      primary: ['#E8F4FD', '#457B9D'],
      secondary: ['#A8DADC', '#1D3557'],
      accent: ['#1D3557', '#F1FAEE']
    },
    effects: {
      glow: '#A8DADC',
      highlight: '#F1FAEE',
      shadow: '#1D3557'
    }
  },

  galaxy: {
    name: 'Galaxy',
    colors: ['#0F0F23', '#2D1B69', '#6A4C93', '#C490E4', '#FFEEDD'],
    gradients: {
      primary: ['#0F0F23', '#6A4C93'],
      secondary: ['#2D1B69', '#C490E4'],
      accent: ['#C490E4', '#FFEEDD']
    },
    effects: {
      glow: '#C490E4',
      highlight: '#FFEEDD',
      shadow: '#0F0F23'
    }
  },

  desert: {
    name: 'Desert',
    colors: ['#8B4513', '#CD853F', '#DEB887', '#FFE4B5', '#FFEFD5'],
    gradients: {
      primary: ['#8B4513', '#DEB887'],
      secondary: ['#CD853F', '#FFE4B5'],
      accent: ['#FFE4B5', '#FFEFD5']
    },
    effects: {
      glow: '#FFE4B5',
      highlight: '#FFEFD5',
      shadow: '#8B4513'
    }
  }
};

/**
 * Predefined themes with complete configurations
 */
export const THEMES = {
  gaming: {
    name: 'Gaming',
    colorScheme: COLOR_SCHEMES.cyberpunk,
    animationSettings: {
      intensity: 'high',
      speed: 1.2,
      particleCount: 150,
      glowEffect: true
    },
    recommendedAnimations: ['matrixRain', 'electricStorm', 'neonPulse', 'particleNetwork']
  },

  portfolio: {
    name: 'Portfolio',
    colorScheme: COLOR_SCHEMES.monochrome,
    animationSettings: {
      intensity: 'medium',
      speed: 0.8,
      particleCount: 80,
      glowEffect: false
    },
    recommendedAnimations: ['geometricShapes', 'floatingBubbles', 'gradientWave']
  },

  landing: {
    name: 'Landing Page',
    colorScheme: COLOR_SCHEMES.sunset,
    animationSettings: {
      intensity: 'medium',
      speed: 1.0,
      particleCount: 100,
      glowEffect: true
    },
    recommendedAnimations: ['galaxySpiral', 'rainbowWaves', 'auroraBorealis']
  },

  presentation: {
    name: 'Presentation',
    colorScheme: COLOR_SCHEMES.space,
    animationSettings: {
      intensity: 'low',
      speed: 0.6,
      particleCount: 50,
      glowEffect: false
    },
    recommendedAnimations: ['starryNight', 'cosmicDust', 'quantumField']
  },

  wellness: {
    name: 'Wellness',
    colorScheme: COLOR_SCHEMES.nature,
    animationSettings: {
      intensity: 'low',
      speed: 0.7,
      particleCount: 60,
      glowEffect: false
    },
    recommendedAnimations: ['oceanWaves', 'autumnLeaves', 'fireflyForest']
  },

  party: {
    name: 'Party',
    colorScheme: COLOR_SCHEMES.neon,
    animationSettings: {
      intensity: 'high',
      speed: 1.5,
      particleCount: 200,
      glowEffect: true
    },
    recommendedAnimations: ['rainbowWaves', 'neonPulse', 'electricStorm']
  },

  cyberpunk: {
    name: 'Cyberpunk',
    colorScheme: COLOR_SCHEMES.cyberpunk,
    animationSettings: {
      intensity: 'high',
      speed: 1.3,
      particleCount: 180,
      glowEffect: true
    },
    recommendedAnimations: ['matrixRain', 'electricStorm', 'neonPulse', 'quantumField']
  },

  retro: {
    name: 'Retro',
    colorScheme: COLOR_SCHEMES.retro,
    animationSettings: {
      intensity: 'medium',
      speed: 1.0,
      particleCount: 120,
      glowEffect: true
    },
    recommendedAnimations: ['rainbowWaves', 'geometricShapes', 'neonPulse', 'galaxySpiral']
  }
};

/**
 * Theme utility functions
 */
export class ThemeManager {
  constructor() {
    this.currentTheme = null;
    this.customThemes = new Map();
    this.currentColors = [];
    this.isTransitioning = false;
    this.themeCycleInterval = null;
  }

  /**
   * Apply a theme to animation settings
   * @param {string} themeName - Name of the theme to apply
   * @returns {Object} Theme configuration
   */
  applyTheme(themeName) {
    const theme = THEMES[themeName] || this.customThemes.get(themeName);
    if (!theme) {
      console.warn(`Theme "${themeName}" not found. Using default.`);
      return THEMES.portfolio;
    }
    
    this.currentTheme = theme;
    this.currentColors = theme.colorScheme.colors;
    return theme;
  }

  /**
   * Get random color from current theme
   * @param {string} [category='colors'] - Color category ('colors', 'primary', 'secondary', 'accent')
   * @returns {string} Hex color string
   */
  getRandomColor(category = 'colors') {
    if (!this.currentTheme) return '#ffffff';
    
    const colorScheme = this.currentTheme.colorScheme;
    let colorArray;
    
    switch (category) {
      case 'primary':
        colorArray = colorScheme.gradients.primary;
        break;
      case 'secondary':
        colorArray = colorScheme.gradients.secondary;
        break;
      case 'accent':
        colorArray = colorScheme.gradients.accent;
        break;
      default:
        colorArray = colorScheme.colors;
    }
    
    return colorArray[Math.floor(Math.random() * colorArray.length)];
  }

  /**
   * Get gradient colors for canvas
   * @param {CanvasRenderingContext2D} ctx - Canvas context
   * @param {string} gradientType - 'primary', 'secondary', or 'accent'
   * @param {Object} coordinates - {x1, y1, x2, y2} for linear gradient
   * @returns {CanvasGradient} Canvas gradient object
   */
  createGradient(ctx, gradientType = 'primary', coordinates = {x1: 0, y1: 0, x2: 0, y2: 100}) {
    if (!this.currentTheme) return null;
    
    const colors = this.currentTheme.colorScheme.gradients[gradientType];
    const gradient = ctx.createLinearGradient(coordinates.x1, coordinates.y1, coordinates.x2, coordinates.y2);
    
    colors.forEach((color, index) => {
      gradient.addColorStop(index / (colors.length - 1), color);
    });
    
    return gradient;
  }

  /**
   * Create a custom theme
   * @param {string} name - Theme name
   * @param {Object} config - Theme configuration
   */
  createCustomTheme(name, config) {
    this.customThemes.set(name, config);
  }

  /**
   * Get effect color from current theme
   * @param {string} effectType - 'glow', 'highlight', or 'shadow'
   * @returns {string} Hex color string
   */
  getEffectColor(effectType) {
    if (!this.currentTheme) return '#ffffff';
    return this.currentTheme.colorScheme.effects[effectType] || '#ffffff';
  }

  /**
   * Generate theme-appropriate settings for animations
   * @param {string} animationType - Type of animation
   * @returns {Object} Animation-specific settings
   */
  getAnimationSettings(animationType) {
    if (!this.currentTheme) return {};
    
    const baseSettings = this.currentTheme.animationSettings;
    const settings = { ...baseSettings };
    
    // Adjust settings based on animation type
    switch (animationType) {
      case 'particleNetwork':
      case 'matrixRain':
        settings.particleCount = Math.floor(baseSettings.particleCount * 1.2);
        break;
      case 'starryNight':
      case 'cosmicDust':
        settings.particleCount = Math.floor(baseSettings.particleCount * 0.8);
        break;
      case 'electricStorm':
      case 'neonPulse':
        settings.glowEffect = true;
        settings.intensity = 'high';
        break;
    }
    
    return settings;
  }

  /**
   * Get all available themes
   * @returns {Array<string>} Array of theme names
   */
  getAvailableThemes() {
    return [...Object.keys(THEMES), ...Array.from(this.customThemes.keys())];
  }

  /**
   * Get recommended animations for current theme
   * @returns {Array<string>} Array of animation names
   */
  getRecommendedAnimations() {
    if (!this.currentTheme) return [];
    return this.currentTheme.recommendedAnimations || [];
  }

  /**
   * Get current theme colors
   * @returns {Array<string>} Array of current colors
   */
  getCurrentColors() {
    if (!this.currentTheme) return ['#ffffff', '#cccccc', '#999999'];
    return this.currentColors || this.currentTheme.colorScheme.colors;
  }

  /**
   * Get background color for current theme
   * @param {number} [opacity=0.1] - Background opacity
   * @returns {string} Background color with opacity
   */
  getBackgroundColor(opacity = 0.1) {
    if (!this.currentTheme) return `rgba(15, 23, 42, ${opacity})`;
    
    // Use the darkest color from the theme as background
    const colors = this.getCurrentColors();
    const darkestColor = colors[colors.length - 1] || colors[0];
    
    // Convert hex to rgba with opacity
    const rgb = this.parseColor(darkestColor);
    return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`;
  }

  /**
   * Get current theme name
   * @returns {string} Current theme name or 'default'
   */
  getCurrentThemeName() {
    return this.currentTheme ? this.currentTheme.name : 'default';
  }

  /**
   * Check if a theme is currently applied
   * @returns {boolean} True if a theme is applied
   */
  hasTheme() {
    return this.currentTheme !== null;
  }

  /**
   * Convert any color to rgba format
   * @param {string} color - Input color (hex, rgb, rgba)
   * @param {number} [opacity=1] - Opacity (0-1)
   * @returns {string} RGBA color string
   */
  toRgba(color, opacity = 1) {
    const rgb = this.parseColor(color);
    return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${opacity})`;
  }

  /**
   * Smooth transition between themes
   * @param {string} newTheme - Target theme name
   * @param {number} [duration=2000] - Transition duration in milliseconds
   * @param {Function} [easingFunction] - Custom easing function
   */
  transitionToTheme(newTheme, duration = 2000, easingFunction = this.easeInOutCubic) {
    if (!THEMES[newTheme] || this.isTransitioning) return;
    
    const startTheme = this.currentTheme;
    const startTime = Date.now();
    this.isTransitioning = true;
    
    const startColors = [...this.currentColors];
    const targetColors = THEMES[newTheme].colorScheme.colors;
    
    const transitionStep = () => {
      const elapsed = Date.now() - startTime;
      const progress = Math.min(elapsed / duration, 1);
      const easedProgress = easingFunction(progress);
      
      // Interpolate colors
      this.currentColors = startColors.map((startColor, index) => {
        const targetColor = targetColors[index] || targetColors[targetColors.length - 1];
        return this.interpolateColor(startColor, targetColor, easedProgress);
      });
      
      if (progress < 1) {
        requestAnimationFrame(transitionStep);
      } else {
        this.currentTheme = newTheme;
        this.isTransitioning = false;
      }
    };
    
    transitionStep();
  }

  /**
   * Interpolate between two colors
   * @param {string} color1 - Start color (hex or rgb)
   * @param {string} color2 - End color (hex or rgb)
   * @param {number} factor - Interpolation factor (0-1)
   * @returns {string} Interpolated color
   */
  interpolateColor(color1, color2, factor) {
    const rgb1 = this.parseColor(color1);
    const rgb2 = this.parseColor(color2);
    
    const r = Math.round(rgb1.r + (rgb2.r - rgb1.r) * factor);
    const g = Math.round(rgb1.g + (rgb2.g - rgb1.g) * factor);
    const b = Math.round(rgb1.b + (rgb2.b - rgb1.b) * factor);
    
    return `rgb(${r}, ${g}, ${b})`;
  }

  /**
   * Parse color string to RGB object
   * @param {string} color - Color string
   * @returns {Object} RGB object
   */
  parseColor(color) {
    if (color.startsWith('#')) {
      const hex = color.slice(1);
      return {
        r: parseInt(hex.slice(0, 2), 16),
        g: parseInt(hex.slice(2, 4), 16),
        b: parseInt(hex.slice(4, 6), 16)
      };
    } else if (color.startsWith('rgb')) {
      const matches = color.match(/\d+/g);
      return {
        r: parseInt(matches[0]),
        g: parseInt(matches[1]),
        b: parseInt(matches[2])
      };
    }
    return { r: 255, g: 255, b: 255 };
  }

  /**
   * Cubic easing function
   * @param {number} t - Time parameter (0-1)
   * @returns {number} Eased value
   */
  easeInOutCubic(t) {
    return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
  }

  /**
   * Enable automatic theme cycling
   * @param {Array} themes - Array of theme names to cycle through
   * @param {number} [interval=10000] - Time between changes in milliseconds
   */
  enableThemeCycling(themes, interval = 10000) {
    this.stopThemeCycling();
    
    let currentIndex = 0;
    this.themeCycleInterval = setInterval(() => {
      currentIndex = (currentIndex + 1) % themes.length;
      this.transitionToTheme(themes[currentIndex]);
    }, interval);
  }

  /**
   * Stop automatic theme cycling
   */
  stopThemeCycling() {
    if (this.themeCycleInterval) {
      clearInterval(this.themeCycleInterval);
      this.themeCycleInterval = null;
    }
  }
}

// Export a singleton instance
export const themeManager = new ThemeManager();