{"version":3,"sources":["jsdelivr-header.js","/npm/@mywallpaper/addon-sdk@2.17.1/src/manifest.ts","/npm/@mywallpaper/addon-sdk@2.17.1/src/utils.ts","/npm/@mywallpaper/addon-sdk@2.17.1/src/index.ts"],"names":[],"mappings":"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","file":"/npm/@mywallpaper/addon-sdk@2.17.1/dist/index.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.39.0.\n * Original file: /npm/@mywallpaper/addon-sdk@2.17.1/dist/index.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","/**\n * @mywallpaper/addon-sdk - Manifest Schema & Validation\n *\n * Defines the addon manifest format (manifest.json) and provides\n * Zod-based validation for type safety at runtime.\n *\n * @example\n * ```typescript\n * import { validateManifest, type AddonManifest } from '@mywallpaper/addon-sdk/manifest'\n *\n * const manifest: AddonManifest = {\n *   name: 'My Addon',\n *   version: '1.0.0',\n *   sdkVersion: '2.7.0',\n *   description: 'A cool addon',\n *   settings: {\n *     primaryColor: { type: 'color', default: '#ffffff', label: 'Primary Color' }\n *   }\n * }\n *\n * // Validate at runtime\n * const result = validateManifest(manifest)\n * if (!result.success) {\n *   console.error('Invalid manifest:', result.errors)\n * }\n * ```\n */\n\nimport type { SystemEventType, PermissionType, OAuthProvider } from './types'\n\n// =============================================================================\n// SETTING TYPES\n// =============================================================================\n\n/**\n * Available setting types for addon configuration.\n */\nexport type SettingType =\n  | 'string'       // Text input\n  | 'number'       // Numeric input\n  | 'boolean'      // Checkbox/toggle\n  | 'color'        // Color picker\n  | 'select'       // Dropdown selection\n  | 'range'        // Slider\n  | 'image'        // Image upload\n  | 'video'        // Video upload\n  | 'audio'        // Audio upload (mp3, wav, ogg, etc.)\n  | 'file'         // Generic file upload (images + videos + audio)\n  | 'section'      // Visual grouping (no value)\n  | 'textarea'     // Multi-line text\n  | 'radio'        // Radio buttons\n  | 'gradient'     // Gradient picker\n  | 'vector2'      // 2D vector (x, y)\n  | 'button'       // Action button (no value)\n  | 'multi-select' // Multiple selection\n\n/**\n * Definition for a single addon setting.\n *\n * @example\n * ```typescript\n * const colorSetting: SettingDefinition = {\n *   type: 'color',\n *   label: 'Background Color',\n *   description: 'Choose a background color',\n *   default: '#000000'\n * }\n *\n * const speedSetting: SettingDefinition = {\n *   type: 'range',\n *   label: 'Animation Speed',\n *   min: 0.1,\n *   max: 10,\n *   step: 0.1,\n *   default: 1\n * }\n * ```\n */\nexport interface SettingDefinition {\n  /** Setting type determines the UI control */\n  type: SettingType\n\n  /** Human-readable label */\n  label?: string\n\n  /** Help text shown below the control */\n  description?: string\n\n  /** Default value (type depends on setting type) */\n  default?: unknown\n\n  // Select/Radio/Multi-select options\n  /** Options for select, radio, multi-select types */\n  options?: Array<{ value: string; label: string }>\n\n  // Range-specific\n  /** Minimum value for range type */\n  min?: number\n  /** Maximum value for range type */\n  max?: number\n  /** Step increment for range type */\n  step?: number\n\n  // Button-specific\n  /** Label text on button */\n  buttonLabel?: string\n\n  // Vector-specific\n  /** Axis labels for vector types, e.g., ['X', 'Y'] */\n  axisLabels?: [string, string] | [string, string, string]\n\n  // Multi-select specific\n  /** Maximum number of selections */\n  maxItems?: number\n\n  // Conditional visibility\n  /** Only show if another setting has a specific value */\n  showIf?: {\n    setting: string\n    equals: unknown\n  }\n}\n\n// =============================================================================\n// MANIFEST SCHEMA\n// =============================================================================\n\n/**\n * Default layout configuration for addon positioning.\n */\nexport interface AddonDefaultLayout {\n  /** X position as percentage of viewport width (0-100) */\n  xPercent?: number\n  /** Y position as percentage of viewport height (0-100) */\n  yPercent?: number\n  /** Width as percentage of viewport width (0-100) */\n  widthPercent?: number\n  /** Height as percentage of viewport height (0-100) */\n  heightPercent?: number\n  /** Rotation in degrees */\n  rotation?: number\n}\n\n/**\n * The complete addon manifest schema.\n * This defines your addon's identity, settings, and capabilities.\n *\n * @example\n * ```json\n * {\n *   \"name\": \"Weather Widget\",\n *   \"version\": \"1.2.0\",\n *   \"description\": \"Displays current weather conditions\",\n *   \"author\": \"Jane Developer\",\n *   \"categories\": [\"utilities\", \"weather\"],\n *   \"capabilities\": {\n *     \"hotReload\": true,\n *     \"systemEvents\": [\"viewport:resize\", \"theme:change\"]\n *   },\n *   \"permissions\": {\n *     \"storage\": { \"quota\": 512 },\n *     \"network\": { \"domains\": [\"api.openweathermap.org\"] }\n *   },\n *   \"settings\": {\n *     \"apiKey\": {\n *       \"type\": \"string\",\n *       \"label\": \"API Key\",\n *       \"description\": \"Get your key at openweathermap.org\"\n *     },\n *     \"unit\": {\n *       \"type\": \"select\",\n *       \"label\": \"Temperature Unit\",\n *       \"default\": \"celsius\",\n *       \"options\": [\n *         { \"value\": \"celsius\", \"label\": \"Celsius\" },\n *         { \"value\": \"fahrenheit\", \"label\": \"Fahrenheit\" }\n *       ]\n *     }\n *   }\n * }\n * ```\n */\nexport interface AddonManifest {\n  // ---------------------------------------------------------------------------\n  // REQUIRED FIELDS\n  // ---------------------------------------------------------------------------\n\n  /** Addon name (displayed in UI) */\n  name: string\n\n  /** Semantic version (e.g., \"1.0.0\") */\n  version: string\n\n  /**\n   * SDK version this addon was built for.\n   * Must be a valid semver (e.g., \"2.3.0\").\n   * Host uses this to inject the correct client-side SDK.\n   */\n  sdkVersion: string\n\n  // ---------------------------------------------------------------------------\n  // OPTIONAL METADATA\n  // ---------------------------------------------------------------------------\n\n  /** Short description of the addon */\n  description?: string\n\n  /** Author name or organization */\n  author?: string\n\n  /** Addon type/category for filtering (e.g., \"widget\", \"cursor\") */\n  type?: string\n\n  /**\n   * For cursor addons: name of the exported initializer function.\n   * Only used when type is \"cursor\".\n   * The cursor.js script must export a function with this name.\n   * @example \"initializeCursor\"\n   */\n  cursorExport?: string\n\n  /** Categories for discoverability */\n  categories?: string[]\n\n  /** License (e.g., \"MIT\", \"Apache-2.0\") */\n  license?: string\n\n  /** Homepage or documentation URL */\n  homepage?: string\n\n  /** Repository URL */\n  repository?: string\n\n  // ---------------------------------------------------------------------------\n  // SETTINGS\n  // ---------------------------------------------------------------------------\n\n  /**\n   * User-configurable settings.\n   * Key is the setting ID, value is the definition.\n   */\n  settings?: Record<string, SettingDefinition>\n\n  // ---------------------------------------------------------------------------\n  // CAPABILITIES\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Addon capabilities and event subscriptions.\n   */\n  capabilities?: {\n    /** Supports settings changes without full reload */\n    hotReload?: boolean\n    /** System events the addon wants to receive */\n    systemEvents?: SystemEventType[]\n  }\n\n  // ---------------------------------------------------------------------------\n  // PERMISSIONS\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Permissions the addon will request.\n   * Declaring them here improves UX by showing upfront.\n   */\n  permissions?: {\n    /** Storage permission with quota in KB */\n    storage?: { quota: number }\n    /** CPU usage limit */\n    cpu?: { limit: 'low' | 'medium' | 'high' }\n    /** Network access with allowed domains */\n    network?: { domains: string[] }\n    /** Audio playback permission */\n    audio?: boolean\n    /** Notification permission */\n    notifications?: boolean\n    /**\n     * File access permission for uploaded media files.\n     * When enabled, the addon can receive uploaded files (video, audio, images)\n     * via secure blob transfer for playback.\n     *\n     * This is required for addons that play user-uploaded media content.\n     */\n    fileAccess?: {\n      /** User-friendly reason why this permission is needed */\n      reason: string\n    }\n    /**\n     * Same-origin permission for blob URL support.\n     * Required for addons that handle large local files (videos, audio).\n     *\n     * ⚠️ SECURITY WARNING: This allows the addon to access:\n     * - localStorage/sessionStorage of the parent origin\n     * - Cookies of the parent domain\n     * - IndexedDB of the parent origin\n     *\n     * Only grant to trusted addons!\n     */\n    sameOrigin?: {\n      /** User-friendly reason why this permission is needed */\n      reason: string\n    }\n  }\n\n  // ---------------------------------------------------------------------------\n  // LAYOUT\n  // ---------------------------------------------------------------------------\n\n  /**\n   * Default layout for automatic positioning.\n   * Used when addon is first added to a wallpaper.\n   */\n  defaultLayout?: AddonDefaultLayout\n\n  // ---------------------------------------------------------------------------\n  // UI BEHAVIOR\n  // ---------------------------------------------------------------------------\n\n  /**\n   * UI behavior customization for the addon.\n   *\n   * @example\n   * ```json\n   * {\n   *   \"ui\": {\n   *     \"pointerEvents\": \"none\"\n   *   }\n   * }\n   * ```\n   */\n  ui?: {\n    /**\n     * Controls whether the addon captures mouse/touch events.\n     * - \"auto\" (default): Normal behavior, addon is interactive\n     * - \"none\": Clicks pass through to layers below (for overlays like rain, particles)\n     */\n    pointerEvents?: 'auto' | 'none'\n  }\n\n  // ---------------------------------------------------------------------------\n  // FUTURE FIELDS\n  // ---------------------------------------------------------------------------\n\n  /**\n   * OAuth providers and scopes for authenticated addons.\n   *\n   * Declare the OAuth providers your addon needs and the scopes it requires.\n   * Scopes are requested incrementally - users can connect with basic scopes\n   * first, and your addon can request additional scopes when needed.\n   *\n   * @example\n   * ```json\n   * {\n   *   \"permissions\": {\n   *     \"oauth\": {\n   *       \"required\": [\n   *         { \"provider\": \"github\", \"scopes\": [\"read:user\"] }\n   *       ],\n   *       \"optional\": [\n   *         { \"provider\": \"github\", \"scopes\": [\"repo\"] }\n   *       ]\n   *     }\n   *   }\n   * }\n   * ```\n   */\n  oauth?: {\n    /** OAuth providers required for the addon to function */\n    required?: Array<{\n      provider: OAuthProvider\n      scopes: string[]\n      /** Reason shown to user when requesting this permission */\n      reason?: string\n    }>\n    /** OAuth providers that enhance functionality but aren't required */\n    optional?: Array<{\n      provider: OAuthProvider\n      scopes: string[]\n      /** Reason shown to user when requesting this permission */\n      reason?: string\n    }>\n  }\n}\n\n// =============================================================================\n// VALIDATION\n// =============================================================================\n\n/**\n * Validation result type.\n */\nexport interface ValidationResult {\n  success: boolean\n  errors?: string[]\n  manifest?: AddonManifest\n}\n\n/**\n * Validates an addon manifest object.\n *\n * @param input - The manifest object to validate\n * @returns ValidationResult with success status and any errors\n *\n * @example\n * ```typescript\n * const result = validateManifest(myManifest)\n * if (!result.success) {\n *   result.errors?.forEach(err => console.error(err))\n * }\n * ```\n */\nexport function validateManifest(input: unknown): ValidationResult {\n  const errors: string[] = []\n\n  if (!input || typeof input !== 'object') {\n    return { success: false, errors: ['Manifest must be an object'] }\n  }\n\n  const manifest = input as Record<string, unknown>\n\n  // Required fields\n  if (typeof manifest.name !== 'string' || manifest.name.trim() === '') {\n    errors.push('name: Required and must be a non-empty string')\n  }\n\n  if (typeof manifest.version !== 'string' || !/^\\d+\\.\\d+\\.\\d+/.test(manifest.version)) {\n    errors.push('version: Required and must be a valid semver (e.g., \"1.0.0\")')\n  }\n\n  if (typeof manifest.sdkVersion !== 'string' || !/^\\d+\\.\\d+\\.\\d+/.test(manifest.sdkVersion)) {\n    errors.push('sdkVersion: Required and must be a valid semver (e.g., \"2.2.0\")')\n  }\n\n  // Optional string fields\n  const optionalStrings = ['description', 'author', 'type', 'license', 'homepage', 'repository']\n  for (const field of optionalStrings) {\n    if (manifest[field] !== undefined && typeof manifest[field] !== 'string') {\n      errors.push(`${field}: Must be a string`)\n    }\n  }\n\n  // Categories\n  if (manifest.categories !== undefined) {\n    if (!Array.isArray(manifest.categories)) {\n      errors.push('categories: Must be an array of strings')\n    } else if (!manifest.categories.every(c => typeof c === 'string')) {\n      errors.push('categories: All items must be strings')\n    }\n  }\n\n  // Settings validation\n  if (manifest.settings !== undefined) {\n    if (typeof manifest.settings !== 'object' || manifest.settings === null) {\n      errors.push('settings: Must be an object')\n    } else {\n      const settings = manifest.settings as Record<string, unknown>\n      for (const [key, value] of Object.entries(settings)) {\n        if (typeof value !== 'object' || value === null) {\n          errors.push(`settings.${key}: Must be an object`)\n          continue\n        }\n\n        const setting = value as Record<string, unknown>\n        const validTypes: SettingType[] = [\n          'string', 'number', 'boolean', 'color', 'select', 'range',\n          'image', 'video', 'audio', 'file', 'section', 'textarea', 'radio', 'gradient', 'vector2',\n          'button', 'multi-select'\n        ]\n\n        if (!validTypes.includes(setting.type as SettingType)) {\n          errors.push(`settings.${key}.type: Must be one of: ${validTypes.join(', ')}`)\n        }\n\n        // Type-specific validation\n        if (['select', 'radio', 'multi-select'].includes(setting.type as string)) {\n          if (!Array.isArray(setting.options) || setting.options.length === 0) {\n            errors.push(`settings.${key}.options: Required for ${setting.type} type`)\n          }\n        }\n\n        if (setting.type === 'range') {\n          if (typeof setting.min !== 'number' || typeof setting.max !== 'number') {\n            errors.push(`settings.${key}: range type requires min and max`)\n          }\n        }\n      }\n    }\n  }\n\n  // Capabilities validation\n  if (manifest.capabilities !== undefined) {\n    if (typeof manifest.capabilities !== 'object' || manifest.capabilities === null) {\n      errors.push('capabilities: Must be an object')\n    } else {\n      const caps = manifest.capabilities as Record<string, unknown>\n\n      if (caps.hotReload !== undefined && typeof caps.hotReload !== 'boolean') {\n        errors.push('capabilities.hotReload: Must be a boolean')\n      }\n\n      if (caps.systemEvents !== undefined) {\n        const validEvents: SystemEventType[] = [\n          'viewport:resize', 'theme:change', 'visibility:change',\n          'layer:focus', 'layer:blur', 'app:idle', 'app:active'\n        ]\n\n        if (!Array.isArray(caps.systemEvents)) {\n          errors.push('capabilities.systemEvents: Must be an array')\n        } else {\n          for (const event of caps.systemEvents) {\n            if (!validEvents.includes(event)) {\n              errors.push(`capabilities.systemEvents: Invalid event \"${event}\"`)\n            }\n          }\n        }\n      }\n    }\n  }\n\n  // Permissions validation\n  if (manifest.permissions !== undefined) {\n    if (typeof manifest.permissions !== 'object' || manifest.permissions === null) {\n      errors.push('permissions: Must be an object')\n    } else {\n      const perms = manifest.permissions as Record<string, unknown>\n\n      if (perms.storage !== undefined) {\n        const storage = perms.storage as Record<string, unknown>\n        if (typeof storage?.quota !== 'number' || storage.quota <= 0) {\n          errors.push('permissions.storage.quota: Must be a positive number')\n        }\n      }\n\n      if (perms.cpu !== undefined) {\n        const cpu = perms.cpu as Record<string, unknown>\n        if (!['low', 'medium', 'high'].includes(cpu?.limit as string)) {\n          errors.push('permissions.cpu.limit: Must be low, medium, or high')\n        }\n      }\n\n      if (perms.network !== undefined) {\n        const network = perms.network as Record<string, unknown>\n        if (!Array.isArray(network?.domains)) {\n          errors.push('permissions.network.domains: Must be an array of domain strings')\n        }\n      }\n    }\n  }\n\n  // Default layout validation\n  if (manifest.defaultLayout !== undefined) {\n    if (typeof manifest.defaultLayout !== 'object' || manifest.defaultLayout === null) {\n      errors.push('defaultLayout: Must be an object')\n    } else {\n      const layout = manifest.defaultLayout as Record<string, unknown>\n      const numFields = ['xPercent', 'yPercent', 'widthPercent', 'heightPercent', 'rotation']\n      for (const field of numFields) {\n        if (layout[field] !== undefined && typeof layout[field] !== 'number') {\n          errors.push(`defaultLayout.${field}: Must be a number`)\n        }\n      }\n    }\n  }\n\n  if (errors.length > 0) {\n    return { success: false, errors }\n  }\n\n  return { success: true, manifest: manifest as unknown as AddonManifest }\n}\n\n/**\n * Type guard to check if an object is a valid AddonManifest.\n *\n * @param input - Object to check\n * @returns True if input is a valid AddonManifest\n */\nexport function isValidManifest(input: unknown): input is AddonManifest {\n  return validateManifest(input).success\n}\n\n// =============================================================================\n// UTILITY FUNCTIONS\n// =============================================================================\n\n/**\n * Check if manifest declares hot-reload capability.\n */\nexport function supportsHotReload(manifest: AddonManifest): boolean {\n  return manifest.capabilities?.hotReload === true\n}\n\n/**\n * Get system events the addon subscribes to.\n */\nexport function getSubscribedEvents(manifest: AddonManifest): SystemEventType[] {\n  return manifest.capabilities?.systemEvents ?? []\n}\n\n/**\n * Get required permissions from manifest.\n */\nexport function getRequiredPermissions(manifest: AddonManifest): PermissionType[] {\n  const permissions: PermissionType[] = []\n\n  if (manifest.permissions?.storage) permissions.push('storage')\n  if (manifest.permissions?.cpu) permissions.push('cpu-high')\n  if (manifest.permissions?.network) permissions.push('network')\n  if (manifest.permissions?.audio) permissions.push('audio')\n  if (manifest.permissions?.notifications) permissions.push('notifications')\n\n  return permissions\n}\n","/**\n * @mywallpaper/addon-sdk - Utility Functions\n *\n * Helper utilities for addon development.\n */\n\nimport type { AddonManifest, SettingDefinition } from './manifest'\n\n// =============================================================================\n// CONFIG UTILITIES\n// =============================================================================\n\n/**\n * Creates a default configuration object from manifest settings.\n *\n * @param manifest - Addon manifest with settings definitions\n * @returns Object with all setting keys and their default values\n *\n * @example\n * ```typescript\n * const manifest = {\n *   name: 'My Addon',\n *   version: '1.0.0',\n *   settings: {\n *     color: { type: 'color', default: '#ff0000' },\n *     speed: { type: 'range', default: 1, min: 0, max: 10 }\n *   }\n * }\n *\n * const defaults = createDefaultConfig(manifest)\n * // { color: '#ff0000', speed: 1 }\n * ```\n */\nexport function createDefaultConfig(manifest: AddonManifest): Record<string, unknown> {\n  const config: Record<string, unknown> = {}\n\n  if (!manifest.settings) return config\n\n  for (const [key, setting] of Object.entries(manifest.settings)) {\n    if (setting.type === 'section' || setting.type === 'button') {\n      // Skip non-value settings\n      continue\n    }\n    config[key] = setting.default ?? getTypeDefault(setting)\n  }\n\n  return config\n}\n\n/**\n * Get type-appropriate default value when none specified.\n */\nfunction getTypeDefault(setting: SettingDefinition): unknown {\n  switch (setting.type) {\n    case 'string':\n    case 'textarea':\n      return ''\n    case 'number':\n      return 0\n    case 'boolean':\n      return false\n    case 'color':\n      return '#000000'\n    case 'select':\n    case 'radio':\n      return setting.options?.[0]?.value ?? ''\n    case 'range':\n      return setting.min ?? 0\n    case 'image':\n      return null\n    case 'gradient':\n      return { stops: [{ color: '#000000', position: 0 }, { color: '#ffffff', position: 100 }] }\n    case 'vector2':\n      return { x: 0, y: 0 }\n    case 'multi-select':\n      return []\n    default:\n      return null\n  }\n}\n\n/**\n * Merges user config with defaults, ensuring all keys exist.\n *\n * @param defaults - Default configuration\n * @param userConfig - User-provided configuration (may be partial)\n * @returns Complete configuration with user values taking precedence\n *\n * @example\n * ```typescript\n * const defaults = { color: '#ff0000', speed: 1 }\n * const user = { color: '#00ff00' }\n *\n * const config = mergeConfigs(defaults, user)\n * // { color: '#00ff00', speed: 1 }\n * ```\n */\nexport function mergeConfigs(\n  defaults: Record<string, unknown>,\n  userConfig: Record<string, unknown>\n): Record<string, unknown> {\n  return { ...defaults, ...userConfig }\n}\n\n// =============================================================================\n// VERSION UTILITIES\n// =============================================================================\n\n/**\n * Semantic version comparison result.\n */\nexport type VersionComparison = -1 | 0 | 1\n\n/**\n * Compare two semantic versions.\n *\n * @param a - First version string\n * @param b - Second version string\n * @returns -1 if a < b, 0 if a === b, 1 if a > b\n *\n * @example\n * ```typescript\n * compareVersions('1.0.0', '1.0.1')  // -1\n * compareVersions('2.0.0', '1.9.9')  // 1\n * compareVersions('1.0.0', '1.0.0')  // 0\n * ```\n */\nexport function compareVersions(a: string, b: string): VersionComparison {\n  const parseVersion = (v: string): number[] => {\n    return v.split('.').map(n => parseInt(n, 10) || 0)\n  }\n\n  const partsA = parseVersion(a)\n  const partsB = parseVersion(b)\n\n  for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {\n    const numA = partsA[i] ?? 0\n    const numB = partsB[i] ?? 0\n\n    if (numA < numB) return -1\n    if (numA > numB) return 1\n  }\n\n  return 0\n}\n\n/**\n * Check if version satisfies a minimum requirement.\n *\n * @param version - Version to check\n * @param minimum - Minimum required version\n * @returns True if version >= minimum\n */\nexport function satisfiesMinVersion(version: string, minimum: string): boolean {\n  return compareVersions(version, minimum) >= 0\n}\n\n// =============================================================================\n// STORAGE UTILITIES\n// =============================================================================\n\n/**\n * Calculate the byte size of a value when JSON serialized.\n * Useful for quota tracking.\n *\n * @param value - Value to measure\n * @returns Size in bytes\n */\nexport function getStorageSize(value: unknown): number {\n  try {\n    return new Blob([JSON.stringify(value)]).size\n  } catch {\n    return 0\n  }\n}\n\n/**\n * Format bytes as human-readable string.\n *\n * @param bytes - Number of bytes\n * @returns Formatted string (e.g., \"1.5 KB\", \"2.3 MB\")\n */\nexport function formatBytes(bytes: number): string {\n  if (bytes === 0) return '0 B'\n\n  const units = ['B', 'KB', 'MB', 'GB']\n  const k = 1024\n  const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k))\n\n  return `${(bytes / Math.pow(k, i)).toFixed(i > 0 ? 1 : 0)} ${units[i]}`\n}\n\n// =============================================================================\n// TYPE GUARDS\n// =============================================================================\n\n/**\n * Check if a value is a plain object.\n */\nexport function isPlainObject(value: unknown): value is Record<string, unknown> {\n  return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/**\n * Check if a value is a valid color hex string.\n */\nexport function isValidColor(value: unknown): value is string {\n  if (typeof value !== 'string') return false\n  return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(value)\n}\n\n// =============================================================================\n// DEBOUNCE/THROTTLE\n// =============================================================================\n\n/**\n * Debounce a function - only call after delay with no new calls.\n *\n * @param fn - Function to debounce\n * @param delayMs - Delay in milliseconds\n * @returns Debounced function\n *\n * @example\n * ```typescript\n * const save = debounce((data) => {\n *   localStorage.setItem('data', JSON.stringify(data))\n * }, 1000)\n *\n * // Rapid calls only trigger one save after 1s\n * save({ a: 1 })\n * save({ a: 2 })\n * save({ a: 3 }) // Only this one runs (after 1s)\n * ```\n */\nexport function debounce<T extends (...args: unknown[]) => void>(\n  fn: T,\n  delayMs: number\n): (...args: Parameters<T>) => void {\n  let timeoutId: ReturnType<typeof setTimeout> | null = null\n\n  return (...args: Parameters<T>) => {\n    if (timeoutId) clearTimeout(timeoutId)\n    timeoutId = setTimeout(() => fn(...args), delayMs)\n  }\n}\n\n/**\n * Throttle a function - call at most once per interval.\n *\n * @param fn - Function to throttle\n * @param intervalMs - Minimum interval between calls\n * @returns Throttled function\n */\nexport function throttle<T extends (...args: unknown[]) => void>(\n  fn: T,\n  intervalMs: number\n): (...args: Parameters<T>) => void {\n  let lastCall = 0\n\n  return (...args: Parameters<T>) => {\n    const now = Date.now()\n    if (now - lastCall >= intervalMs) {\n      lastCall = now\n      fn(...args)\n    }\n  }\n}\n\n// =============================================================================\n// EVENT HELPERS\n// =============================================================================\n\n/**\n * Create a one-time event listener that auto-removes after first call.\n *\n * @param element - DOM element or window\n * @param event - Event name\n * @param handler - Event handler\n */\nexport function once(\n  element: EventTarget,\n  event: string,\n  handler: EventListener\n): void {\n  const wrappedHandler: EventListener = (e) => {\n    element.removeEventListener(event, wrappedHandler)\n    handler(e)\n  }\n  element.addEventListener(event, wrappedHandler)\n}\n","/**\n * @mywallpaper/addon-sdk\n *\n * Official SDK for building MyWallpaper addons.\n *\n * @packageDocumentation\n *\n * ## Installation\n *\n * ```bash\n * npm install @mywallpaper/addon-sdk\n * ```\n *\n * ## Quick Start\n *\n * ```typescript\n * // Your addon's main file\n * import type { MyWallpaperAPI } from '@mywallpaper/addon-sdk'\n *\n * // The API is automatically available on window.MyWallpaper\n * const api = window.MyWallpaper\n *\n * // Access configuration\n * const { backgroundColor, animationSpeed } = api.config\n *\n * // React to setting changes (hot-reload)\n * api.onSettingsChange((settings, changedKeys) => {\n *   if (changedKeys.includes('backgroundColor')) {\n *     document.body.style.background = settings.backgroundColor as string\n *   }\n * })\n *\n * // Subscribe to system events\n * api.onEvent('viewport:resize', ({ width, height }) => {\n *   canvas.width = width\n *   canvas.height = height\n * })\n *\n * // Signal addon is ready\n * api.ready({\n *   capabilities: ['hot-reload'],\n *   subscribedEvents: ['viewport:resize']\n * })\n *\n * // After visual rendering is complete\n * await loadImages()\n * render()\n * api.renderComplete()\n * ```\n *\n * ## Manifest Validation\n *\n * ```typescript\n * import { validateManifest, type AddonManifest } from '@mywallpaper/addon-sdk/manifest'\n *\n * const manifest: AddonManifest = {\n *   name: 'My Addon',\n *   version: '1.0.0',\n *   settings: {\n *     color: { type: 'color', default: '#ffffff' }\n *   }\n * }\n *\n * const result = validateManifest(manifest)\n * if (!result.success) {\n *   console.error(result.errors)\n * }\n * ```\n *\n * @module\n */\n\n// =============================================================================\n// TYPE EXPORTS\n// =============================================================================\n\nexport type {\n  // Main API\n  MyWallpaperAPI,\n  StorageAPI,\n  NetworkAPI,\n  NetworkResponse,\n\n  // OAuth API\n  OAuthAPI,\n  OAuthResponse,\n  OAuthScopesError,\n  OAuthProvider,\n  OAuthPermissionType,\n\n  // File Access API\n  FileAccessAPI,\n  FileReference,\n  FileAccessResult,\n\n  // Events\n  SystemEventType,\n  SystemEventData,\n\n  // Capabilities & Permissions\n  AddonCapability,\n  PermissionType,\n  PermissionConstraints,\n\n  // Callbacks\n  SettingsCallback,\n  EventCallback,\n  ReadyOptions,\n\n  // Lifecycle\n  LifecycleEvent,\n\n  // Utility types\n  AddonValues,\n} from './types'\n\n// =============================================================================\n// MANIFEST EXPORTS\n// =============================================================================\n\nexport type {\n  // Manifest types\n  AddonManifest,\n  SettingDefinition,\n  SettingType,\n  AddonDefaultLayout,\n  ValidationResult,\n} from './manifest'\n\nexport {\n  // Validation\n  validateManifest,\n  isValidManifest,\n\n  // Manifest utilities\n  supportsHotReload,\n  getSubscribedEvents,\n  getRequiredPermissions,\n} from './manifest'\n\n// =============================================================================\n// UTILITY EXPORTS\n// =============================================================================\n\nexport {\n  // Config utilities\n  createDefaultConfig,\n  mergeConfigs,\n\n  // Version utilities\n  compareVersions,\n  satisfiesMinVersion,\n\n  // Storage utilities\n  getStorageSize,\n  formatBytes,\n\n  // Type guards\n  isPlainObject,\n  isValidColor,\n\n  // Function utilities\n  debounce,\n  throttle,\n  once,\n} from './utils'\n\nexport type { VersionComparison } from './utils'\n\n// =============================================================================\n// SDK VERSION\n// =============================================================================\n\n/**\n * Current SDK version.\n */\nexport const SDK_VERSION = '2.3.0'\n\n/**\n * Minimum compatible host version.\n */\nexport const MIN_HOST_VERSION = '2.0.0'\n"]}