@skhema/web-component 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"index.es.js","sources":["../src/utils/analytics.ts","../src/utils/hash.ts","../src/utils/sanitization.ts","../src/utils/validation.ts","../src/utils/seo.ts","../src/components/SkhemaElement.ts","../src/index.ts"],"sourcesContent":["import type { ContentData, EmbedAnalytics } from '../components/types.js'\n\n/**\n * Encode string to URL-safe base64\n */\nfunction toUrlSafeBase64(str: string): string {\n // Convert string to base64\n const base64 = btoa(\n encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, p1) =>\n String.fromCharCode(parseInt(p1, 16))\n )\n )\n // Make it URL-safe by replacing + with -, / with _, and removing trailing =\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n// Cookie-based tracking management\ninterface TrackedEmbed {\n contentHash: string\n timestamp: number\n}\n\nconst TRACKING_COOKIE_NAME = '_sk'\nconst TRACKING_EXPIRY_HOURS = 24\nconst MAX_TRACKED_ITEMS = 50 // Prevent cookie from growing too large\n\nfunction getTrackedEmbeds(): TrackedEmbed[] {\n try {\n const cookie = document.cookie\n .split('; ')\n .find((row) => row.startsWith(`${TRACKING_COOKIE_NAME}=`))\n\n if (!cookie) return []\n\n const data = JSON.parse(decodeURIComponent(cookie.split('=')[1]))\n const now = Date.now()\n const cutoff = now - TRACKING_EXPIRY_HOURS * 60 * 60 * 1000\n\n // Filter out expired entries\n return data.filter((item: TrackedEmbed) => item.timestamp > cutoff)\n } catch {\n return []\n }\n}\n\nfunction setTrackedEmbeds(tracked: TrackedEmbed[]): void {\n try {\n // Keep only the most recent entries to prevent cookie bloat\n const limited = tracked.slice(-MAX_TRACKED_ITEMS)\n const expires = new Date(\n Date.now() + TRACKING_EXPIRY_HOURS * 60 * 60 * 1000\n )\n\n document.cookie = `${TRACKING_COOKIE_NAME}=${encodeURIComponent(\n JSON.stringify(limited)\n )}; expires=${expires.toUTCString()}; path=/; SameSite=Lax`\n } catch {\n // Fail silently if cookie storage fails\n }\n}\n\nfunction hasBeenTracked(contentHash: string): boolean {\n const tracked = getTrackedEmbeds()\n return tracked.some((item) => item.contentHash === contentHash)\n}\n\nfunction markAsTracked(contentHash: string): void {\n const tracked = getTrackedEmbeds()\n tracked.push({\n contentHash,\n timestamp: Date.now(),\n })\n setTrackedEmbeds(tracked)\n}\n\n// Batching system for analytics\ninterface BatchedAnalytics {\n embeds: EmbedAnalytics[]\n clicks: ContentData[]\n}\n\nclass AnalyticsBatcher {\n private batch: BatchedAnalytics = { embeds: [], clicks: [] }\n private batchTimeout: number | null = null\n private readonly BATCH_DELAY = 2000 // 2 seconds\n private readonly MAX_BATCH_SIZE = 10\n\n addEmbedLoad(analytics: EmbedAnalytics): void {\n this.batch.embeds.push(analytics)\n this.scheduleBatchSend()\n }\n\n addClick(contentData: ContentData): void {\n this.batch.clicks.push(contentData)\n this.scheduleBatchSend()\n }\n\n private scheduleBatchSend(): void {\n // Clear existing timeout\n if (this.batchTimeout) {\n clearTimeout(this.batchTimeout)\n }\n\n // Send immediately if batch is full\n if (\n this.batch.embeds.length >= this.MAX_BATCH_SIZE ||\n this.batch.clicks.length >= this.MAX_BATCH_SIZE\n ) {\n this.sendBatch()\n return\n }\n\n // Otherwise, wait for more events or timeout\n this.batchTimeout = window.setTimeout(() => {\n this.sendBatch()\n }, this.BATCH_DELAY)\n }\n\n private async sendBatch(): Promise<void> {\n if (this.batchTimeout) {\n clearTimeout(this.batchTimeout)\n this.batchTimeout = null\n }\n\n const currentBatch = { ...this.batch }\n this.batch = { embeds: [], clicks: [] }\n\n if (currentBatch.embeds.length === 0 && currentBatch.clicks.length === 0) {\n return\n }\n\n // Send embeds if any\n if (currentBatch.embeds.length > 0) {\n await this.sendEmbeds(currentBatch.embeds)\n }\n\n // Send clicks if any\n if (currentBatch.clicks.length > 0) {\n await this.sendClicks(currentBatch.clicks)\n }\n }\n\n private async sendEmbeds(embeds: EmbedAnalytics[]): Promise<void> {\n // For now, send individually to maintain compatibility with existing endpoint\n // In future, create a batch endpoint on the server\n for (const embed of embeds) {\n try {\n const data = new URLSearchParams({\n contributor_id: embed.contributorId,\n element_type: embed.elementType,\n content_hash: embed.contentHash,\n content: toUrlSafeBase64(embed.content),\n page_url: embed.pageUrl,\n page_title: embed.pageTitle || '',\n timestamp: embed.timestamp.toString(),\n user_agent: embed.userAgent || '',\n })\n\n // Use beacon for reliability\n if (navigator.sendBeacon) {\n navigator.sendBeacon(\n 'https://api.skhema.com/api:XGdoUqHx/component/embed',\n data\n )\n } else {\n // Fallback to fetch with retry\n await sendWithRetry(\n 'https://api.skhema.com/api:XGdoUqHx/component/embed',\n data,\n 'urlencoded'\n )\n }\n } catch (error) {\n console.debug('Embed tracking failed:', error)\n }\n }\n }\n\n private async sendClicks(clicks: ContentData[]): Promise<void> {\n // For now, send individually to maintain compatibility with existing endpoint\n for (const click of clicks) {\n try {\n const data = {\n contributor_id: click.contributor_id,\n element_type: click.element_type,\n content_hash: click.content_hash,\n source_url: click.source_url,\n timestamp: click.timestamp,\n }\n\n await sendWithRetry(\n 'https://api.skhema.com/api:XGdoUqHx/component/click',\n data,\n 'json'\n )\n } catch (error) {\n console.debug('Click tracking failed:', error)\n }\n }\n }\n\n // Ensure batch is sent when page unloads\n flush(): void {\n if (this.batch.embeds.length > 0 || this.batch.clicks.length > 0) {\n this.sendBatch()\n }\n }\n}\n\n// Global batcher instance\nconst analyticsBatcher = new AnalyticsBatcher()\n\n// Flush on page unload\nif (typeof window !== 'undefined') {\n window.addEventListener('beforeunload', () => {\n analyticsBatcher.flush()\n })\n\n // Also flush on visibility change (mobile browsers)\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n analyticsBatcher.flush()\n }\n })\n}\n\n// Retry logic with exponential backoff\nasync function sendWithRetry(\n url: string,\n data: URLSearchParams | Record<string, unknown>,\n contentType: 'json' | 'urlencoded' = 'json',\n maxRetries = 3\n): Promise<void> {\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n const options: RequestInit = {\n method: 'POST',\n credentials: 'omit',\n keepalive: true,\n }\n\n if (contentType === 'json') {\n options.headers = { 'Content-Type': 'application/json' }\n options.body = JSON.stringify(data)\n } else {\n options.body = data as URLSearchParams\n }\n\n const response = await fetch(url, options)\n\n if (response.ok) return\n\n if (response.status >= 400 && response.status < 500) {\n // Client error, don't retry\n break\n }\n } catch (error) {\n if (attempt === maxRetries - 1) {\n console.debug('Analytics failed after retries:', error)\n return\n }\n }\n\n // Exponential backoff: 1s, 2s, 4s\n await new Promise((resolve) =>\n setTimeout(resolve, Math.pow(2, attempt) * 1000)\n )\n }\n}\n\n// Main tracking functions\nexport async function trackEmbedLoad(analytics: EmbedAnalytics): Promise<void> {\n try {\n // Check if this embed has already been tracked\n if (hasBeenTracked(analytics.contentHash)) {\n console.debug('Embed already tracked, skipping:', analytics.contentHash)\n return\n }\n\n // Mark as tracked before sending to prevent race conditions\n markAsTracked(analytics.contentHash)\n\n // Add to batch instead of sending immediately\n analyticsBatcher.addEmbedLoad(analytics)\n } catch (error) {\n console.debug('Analytics tracking failed:', error)\n }\n}\n\nexport async function trackClick(contentData: ContentData): Promise<void> {\n try {\n // Add to batch instead of sending immediately\n analyticsBatcher.addClick(contentData)\n } catch (error) {\n console.debug('Click tracking failed:', error)\n }\n}\n\nexport function shouldTrackAnalytics(element: HTMLElement): boolean {\n const trackAnalytics = element.getAttribute('track-analytics')\n return trackAnalytics !== 'false'\n}\n","export function generateContentHash(content: string): string {\n // Simple hash function for content identification\n let hash = 0\n const cleanContent = content.trim().substring(0, 200)\n\n for (let i = 0; i < cleanContent.length; i++) {\n const char = cleanContent.charCodeAt(i)\n hash = (hash << 5) - hash + char\n hash = hash & hash // Convert to 32bit integer\n }\n\n return Math.abs(hash).toString(36).substring(0, 12)\n}\n","/**\n * Content sanitization utilities for Skhema web component\n */\n\n/**\n * Sanitizes content to prevent XSS attacks and removes URLs\n * @param content The raw content to sanitize\n * @returns Sanitized HTML-safe content with URLs removed\n */\nexport function sanitizeContent(content: string): string {\n // HTML entity encoding for basic XSS protection\n const htmlEncode = (str: string): string => {\n const div = document.createElement('div')\n div.textContent = str\n return div.innerHTML\n }\n\n // Strip URLs first\n let sanitized = stripUrls(content)\n\n // Encode all HTML entities to prevent script injection\n sanitized = htmlEncode(sanitized)\n\n // Preserve line breaks for readability\n sanitized = sanitized.replace(/\\n/g, '<br>')\n\n // Apply text wrapping rules for long text\n sanitized = applyTextWrapping(sanitized)\n\n return sanitized\n}\n\n/**\n * Strips all URLs from the content\n * @param text The text containing potential URLs\n * @returns Text with all URLs removed\n */\nfunction stripUrls(text: string): string {\n // Comprehensive URL patterns to remove\n const patterns = [\n // Standard URLs with protocols\n /https?:\\/\\/[^\\s<>\"{}|\\\\^`[\\]]+/gi,\n // FTP URLs\n /ftp:\\/\\/[^\\s<>\"{}|\\\\^`[\\]]+/gi,\n // URLs without protocol but with www\n /www\\.[^\\s<>\"{}|\\\\^`[\\]]+/gi,\n // Email-like patterns\n /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/gi,\n // Common domain patterns (anything.com, anything.org, etc.)\n /(?:^|\\s)([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}(?:\\/[^\\s]*)?/gi,\n ]\n\n let stripped = text\n patterns.forEach((pattern) => {\n stripped = stripped.replace(pattern, '')\n })\n\n // Clean up any multiple spaces left after URL removal\n stripped = stripped.replace(/\\s+/g, ' ').trim()\n\n return stripped\n}\n\n/**\n * Applies intelligent text wrapping to prevent layout breaking\n * @param text The text to apply wrapping rules to\n * @returns Text with appropriate wrapping hints\n */\nfunction applyTextWrapping(text: string): string {\n // Split text into words\n const words = text.split(/(\\s+)/)\n\n return words\n .map((word) => {\n // Skip if it's whitespace or already contains HTML\n if (/^\\s+$/.test(word) || word.includes('<')) {\n return word\n }\n\n // For very long words (>30 chars), add word-break opportunities\n if (word.length > 30) {\n // Insert zero-width spaces every 10 characters for breaking\n return word.replace(/(.{10})/g, '$1\\u200B')\n }\n\n return word\n })\n .join('')\n}\n\n/**\n * Validates if content contains potentially malicious patterns or URLs\n * @param content The content to validate\n * @returns Object with validation status and detected issues\n */\nexport function validateContentSecurity(content: string): {\n isSecure: boolean\n issues: string[]\n} {\n const issues: string[] = []\n\n // Check for script tags\n if (/<script[\\s>]/i.test(content)) {\n issues.push('Script tags detected')\n }\n\n // Check for event handlers\n if (/on\\w+\\s*=/i.test(content)) {\n issues.push('Event handlers detected')\n }\n\n // Check for javascript: protocol\n if (/javascript:/i.test(content)) {\n issues.push('JavaScript protocol detected')\n }\n\n // Check for data: URLs that could contain scripts\n if (/data:[^,]*script/i.test(content)) {\n issues.push('Data URL with script detected')\n }\n\n // Check for iframe tags\n if (/<iframe[\\s>]/i.test(content)) {\n issues.push('Iframe tags detected')\n }\n\n // Check for URLs (since we want to disallow them)\n if (/https?:\\/\\//i.test(content) || /www\\./i.test(content)) {\n issues.push('URLs detected in content')\n }\n\n return {\n isSecure: issues.length === 0,\n issues,\n }\n}\n\n/**\n * Strips all HTML tags from content\n * @param content The content to strip\n * @returns Plain text content\n */\nexport function stripHtml(content: string): string {\n const div = document.createElement('div')\n div.innerHTML = content\n return div.textContent || div.innerText || ''\n}\n\n/**\n * Checks if content contains any URLs\n * @param content The content to check\n * @returns True if URLs are found\n */\nexport function containsUrls(content: string): boolean {\n const urlPatterns = [\n /https?:\\/\\//i,\n /ftp:\\/\\//i,\n /www\\./i,\n /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/i,\n /(?:^|\\s)([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}(?:\\/[^\\s]*)?/i,\n ]\n\n return urlPatterns.some((pattern) => pattern.test(content))\n}\n","import type { ElementValue } from '@skhema/types'\nimport { ELEMENT_TYPES } from '@skhema/types'\n\nexport function isValidElementType(\n elementType: string\n): elementType is ElementValue {\n const validTypes = Object.values(ELEMENT_TYPES).map((type) => type.value)\n return validTypes.includes(elementType as ElementValue)\n}\n\nexport function validateAttributes(element: HTMLElement): {\n isValid: boolean\n errors: string[]\n elementType?: ElementValue\n contributorId?: string\n} {\n const errors: string[] = []\n\n const elementType = element.getAttribute('element-type')\n const contributorId = element.getAttribute('contributor-id')\n\n if (!elementType) {\n errors.push('Missing required attribute: element-type')\n } else if (!isValidElementType(elementType)) {\n const validTypes = Object.values(ELEMENT_TYPES)\n .map((t) => t.value)\n .join(', ')\n errors.push(\n `Invalid element-type \"${elementType}\". Valid types: ${validTypes}`\n )\n }\n\n if (!contributorId) {\n errors.push('Missing required attribute: contributor-id')\n } else if (contributorId.trim().length === 0) {\n errors.push('contributor-id cannot be empty')\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n elementType: isValidElementType(elementType || '')\n ? (elementType as ElementValue)\n : undefined,\n contributorId: contributorId || undefined,\n }\n}\n\nexport function getElementTypeLabel(elementType: ElementValue): string {\n const type = Object.values(ELEMENT_TYPES).find((t) => t.value === elementType)\n return type?.label || elementType\n}\n\nexport function getElementTypeAcronym(elementType: ElementValue): string {\n const type = Object.values(ELEMENT_TYPES).find((t) => t.value === elementType)\n return type?.acronym || elementType.substring(0, 2).toUpperCase()\n}\n","import type { ElementValue } from '@skhema/types'\nimport { generateContentHash } from './hash.js'\nimport { getElementTypeLabel } from './validation.js'\n\nexport function generateStructuredData(\n content: string,\n elementType: ElementValue,\n contributorId: string,\n sourceUrl: string\n): object {\n return {\n '@context': 'https://schema.org',\n '@type': 'AnalysisContent',\n text: content,\n analysisType: elementType,\n category: getElementTypeLabel(elementType),\n contributor: contributorId,\n url: generateRedirectUrl(content, elementType, contributorId),\n provider: {\n '@type': 'Organization',\n name: 'Skhema',\n url: 'https://skhema.com',\n },\n isPartOf: {\n '@type': 'WebPage',\n url: sourceUrl,\n },\n dateCreated: new Date().toISOString(),\n platform: 'Skhema',\n }\n}\n\nexport function generateRedirectUrl(\n content: string,\n elementType: ElementValue,\n contributorId: string,\n options: {\n baseUrl?: string\n utmSource?: string\n utmMedium?: string\n utmCampaign?: string\n } = {}\n): string {\n const baseUrl = options.baseUrl || 'https://app.skhema.com/save' // This page will handle the authentication and content saving\n const contentHash = generateContentHash(content)\n const sourceUrl = encodeURIComponent(window.location.href)\n const timestamp = Date.now()\n\n const params = new URLSearchParams({\n source: sourceUrl,\n t: timestamp.toString(),\n utm_source: options.utmSource || 'web_component',\n utm_medium: options.utmMedium || 'embedded',\n utm_campaign: options.utmCampaign || elementType,\n utm_content: contributorId,\n })\n\n return `${baseUrl}?contributor_id=${contributorId}&element_type=${elementType}&content_hash=${contentHash}&${params.toString()}`\n // return `${baseUrl}/contributor_id=${contributorId}&element_type=${elementType}&content_hash=${contentHash}?${params.toString()}`;\n}\n\nexport function createMetaTags(\n content: string,\n elementType: ElementValue,\n contributorId: string\n): string {\n const label = getElementTypeLabel(elementType)\n\n return `\n <div itemscope itemtype=\"https://schema.org/AnalysisContent\" style=\"display:none;\">\n <meta itemprop=\"analysisType\" content=\"${elementType}\">\n <meta itemprop=\"text\" content=\"${content}\">\n <meta itemprop=\"contributor\" content=\"${contributorId}\">\n <meta itemprop=\"category\" content=\"${label}\">\n <meta itemprop=\"platform\" content=\"Skhema\">\n </div>\n `\n}\n\nexport function createAriaAttributes(\n elementType: ElementValue\n): Record<string, string> {\n const label = getElementTypeLabel(elementType)\n\n return {\n role: 'article',\n 'aria-label': `${label} - Strategic insight`,\n 'aria-describedby': 'skhema-description',\n }\n}\n","import {\n shouldTrackAnalytics,\n trackClick,\n trackEmbedLoad,\n} from '../utils/analytics.js'\nimport { generateContentHash } from '../utils/hash.js'\nimport {\n sanitizeContent,\n validateContentSecurity,\n} from '../utils/sanitization.js'\nimport {\n createAriaAttributes,\n createMetaTags,\n generateRedirectUrl,\n generateStructuredData,\n} from '../utils/seo.js'\nimport { getElementTypeLabel, validateAttributes } from '../utils/validation.js'\nimport type {\n ContentData,\n EmbedAnalytics,\n SkhemaElementAttributes,\n SkhemaElementEventMap,\n} from './types.js'\n\n// Inline styles matching Skhema UI library design system\nconst styles = `\n:host {\n /* Skhema Brand Colors - matching UI library */\n --skhema-primary: hsl(344 57% 54%); /* #cd476a */\n --skhema-primary-hover: hsl(344 50% 47%); /* #b53d5e */\n --skhema-primary-pressed: hsl(343 50% 41%); /* #9d3552 */\n --skhema-secondary: hsl(345 100% 75%); /* #ff82a2 */\n --skhema-gradient: linear-gradient(135deg, hsl(344 57% 54%) 0%, hsl(345 100% 75%) 100%);\n \n /* Light mode colors */\n --skhema-bg: hsl(0 0% 100%);\n --skhema-card: hsl(0 0% 100%);\n --skhema-border: hsl(214.3 31.8% 91.4%);\n --skhema-text: hsl(222.2 84% 4.9%);\n --skhema-text-muted: hsl(215.4 16.3% 46.9%);\n --skhema-accent: hsl(210 40% 96%);\n \n /* Shadows matching UI library */\n --skhema-shadow: 0 1px 3px 0 hsl(0 0 0 / 0.1), 0 1px 2px -1px hsl(0 0 0 / 0.1);\n --skhema-shadow-md: 0 4px 6px -1px hsl(0 0 0 / 0.1), 0 2px 4px -2px hsl(0 0 0 / 0.1);\n --skhema-shadow-lg: 0 10px 15px -3px hsl(0 0 0 / 0.1), 0 4px 6px -4px hsl(0 0 0 / 0.1);\n --skhema-radius: 0.375rem;\n \n display: block;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Inter', sans-serif;\n line-height: 1.5;\n color: var(--skhema-text);\n}\n\n:host([theme=\"dark\"]) {\n /* Dark mode colors */\n --skhema-bg: hsl(222.2 84% 4.9%);\n --skhema-card: hsl(222.2 84% 4.9%);\n --skhema-border: hsl(217.2 32.6% 17.5%);\n --skhema-text: hsl(210 40% 98%);\n --skhema-text-muted: hsl(215 20.2% 65.1%);\n --skhema-accent: hsl(217.2 32.6% 17.5%);\n}\n\n/* Main component card - inspired by your design */\n.skhema-insight-card {\n position: relative;\n background: var(--skhema-card);\n border: 1px solid var(--skhema-border);\n border-radius: calc(var(--skhema-radius) * 2);\n padding: 16px;\n box-shadow: var(--skhema-shadow);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n max-width: 600px;\n margin: 8px 0;\n}\n\n.skhema-insight-card:hover {\n box-shadow: var(--skhema-shadow-lg);\n border-color: var(--skhema-primary);\n transform: translateY(-1px);\n}\n\n/* Header section with contributor info */\n.skhema-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 12px;\n gap: 12px;\n}\n\n.skhema-contributor {\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n}\n\n.skhema-avatar {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: var(--skhema-gradient);\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n font-size: 14px;\n color: white;\n flex-shrink: 0;\n}\n\n.skhema-contributor-info {\n min-width: 0;\n flex: 1;\n}\n\n.skhema-contributor-name {\n font-weight: 500;\n font-size: 14px;\n color: var(--skhema-text);\n margin: 0;\n line-height: 1.2;\n}\n\n.skhema-contributor-role {\n font-size: 12px;\n color: var(--skhema-text-muted);\n margin: 0;\n line-height: 1.2;\n}\n\n/* Element type badge */\n.skhema-element-badge {\n display: inline-flex;\n align-items: center;\n padding: 4px 8px;\n background: var(--skhema-accent);\n border: 1px solid var(--skhema-border);\n border-radius: var(--skhema-radius);\n font-size: 11px;\n font-weight: 500;\n color: var(--skhema-text-muted);\n text-transform: capitalize;\n white-space: nowrap;\n flex-shrink: 0;\n}\n\n/* Content section */\n.skhema-content {\n margin: 12px 0 16px 0;\n padding: 0;\n}\n\n.skhema-content-text {\n font-size: 15px;\n line-height: 1.6;\n color: var(--skhema-text);\n margin: 0;\n font-style: italic;\n position: relative;\n word-wrap: break-word;\n overflow-wrap: break-word;\n hyphens: auto;\n max-width: 100%;\n}\n\n.skhema-content-text::before {\n content: '\"';\n color: var(--skhema-primary);\n font-size: 20px;\n font-weight: 600;\n margin-right: 4px;\n}\n\n.skhema-content-text::after {\n content: '\"';\n color: var(--skhema-primary);\n font-size: 20px;\n font-weight: 600;\n margin-left: 4px;\n}\n\n/* Footer section */\n.skhema-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--skhema-border);\n}\n\n.skhema-attribution {\n font-size: 11px;\n color: var(--skhema-text-muted);\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.skhema-attribution a {\n color: var(--skhema-primary);\n text-decoration: none;\n font-weight: 500;\n}\n\n.skhema-attribution a:hover {\n text-decoration: underline;\n}\n\n/* Save button with gradient and arrow */\n.skhema-save-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n background: var(--skhema-gradient);\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: var(--skhema-radius);\n font-size: 13px;\n font-weight: 500;\n text-decoration: none;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: var(--skhema-shadow);\n white-space: nowrap;\n}\n\n.skhema-save-btn:hover {\n transform: translateY(-1px);\n box-shadow: var(--skhema-shadow-md);\n}\n\n.skhema-save-btn:active {\n transform: translateY(0);\n}\n\n.skhema-save-btn:focus {\n outline: 2px solid var(--skhema-primary);\n outline-offset: 2px;\n}\n\n.skhema-save-btn::after {\n content: '→';\n transition: transform 0.2s ease;\n}\n\n.skhema-save-btn:hover::after {\n transform: translateX(2px);\n}\n\n/* Error state */\n.skhema-error {\n background: hsl(0 93% 94%);\n border: 1px solid hsl(0 84% 60%);\n color: hsl(0 74% 42%);\n padding: 12px;\n border-radius: var(--skhema-radius);\n font-size: 13px;\n}\n\n.skhema-error-title {\n font-weight: 600;\n margin-bottom: 8px;\n}\n\n.skhema-error-list {\n margin: 0;\n padding-left: 16px;\n}\n\n/* Loading state */\n.skhema-loading {\n background: var(--skhema-accent);\n border: 1px solid var(--skhema-border);\n padding: 12px;\n border-radius: var(--skhema-radius);\n color: var(--skhema-text-muted);\n font-size: 13px;\n text-align: center;\n}\n\n.skhema-loading::after {\n content: '...';\n animation: loading 1.5s infinite;\n}\n\n@keyframes loading {\n 0%, 33% { content: '...'; }\n 66% { content: '..'; }\n 100% { content: '.'; }\n}\n\n/* Responsive design */\n@media (max-width: 640px) {\n .skhema-insight-card {\n margin: 4px 0;\n padding: 12px;\n }\n \n .skhema-header {\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n }\n \n .skhema-footer {\n flex-direction: column;\n align-items: stretch;\n gap: 8px;\n }\n \n .skhema-save-btn {\n justify-content: center;\n }\n}\n\n/* Accessibility */\n@media (prefers-reduced-motion: reduce) {\n .skhema-insight-card,\n .skhema-save-btn {\n transition: none;\n }\n \n .skhema-save-btn::after {\n transition: none;\n }\n \n .skhema-save-btn:hover::after {\n transform: none;\n }\n}\n\n.skhema-structured-data {\n display: none !important;\n}\n`\n\nexport class SkhemaElement extends HTMLElement {\n private shadow: ShadowRoot\n private contentData: ContentData | null = null\n private componentConnected = false\n private hasTrackedLoad = false\n\n constructor() {\n super()\n this.shadow = this.attachShadow({ mode: 'closed' })\n }\n\n static get observedAttributes(): (keyof SkhemaElementAttributes)[] {\n return [\n 'element-type',\n 'contributor-id',\n 'content',\n 'source-url',\n 'theme',\n 'track-analytics',\n ]\n }\n\n connectedCallback() {\n if (this.componentConnected) return\n this.componentConnected = true\n\n try {\n this.render()\n this.trackLoad()\n } catch (error) {\n this.renderError('Failed to initialize component', error)\n }\n }\n\n attributeChangedCallback(\n _name: keyof SkhemaElementAttributes,\n oldValue: string | null,\n newValue: string | null\n ) {\n if (oldValue !== newValue && this.componentConnected) {\n this.render()\n }\n }\n\n private render() {\n const validation = validateAttributes(this as HTMLElement)\n\n if (!validation.isValid) {\n this.renderError('Invalid component attributes', validation.errors)\n return\n }\n\n const content = this.getContent()\n if (!content.trim()) {\n this.renderError('Component requires content', [\n 'Add content between the opening and closing tags, or use the content attribute',\n ])\n return\n }\n\n // Validate content security\n const securityValidation = validateContentSecurity(content)\n if (!securityValidation.isSecure) {\n this.renderError(\n 'Content security validation failed',\n securityValidation.issues\n )\n return\n }\n\n this.contentData = {\n contributor_id: validation.contributorId!,\n element_type: validation.elementType!,\n content: content,\n content_hash: generateContentHash(content),\n source_url: this.getAttribute('source-url') || window.location.href,\n timestamp: new Date().toISOString(),\n page_title: document.title,\n }\n\n this.renderContent()\n this.addStructuredData()\n }\n\n private getContent(): string {\n return this.getAttribute('content') || this.textContent || ''\n }\n\n private renderContent() {\n if (!this.contentData) return\n\n const { element_type, contributor_id, content } = this.contentData\n const label = getElementTypeLabel(element_type)\n const redirectUrl = generateRedirectUrl(\n content,\n element_type,\n contributor_id\n )\n const theme = this.getAttribute('theme') || 'auto'\n\n // Generate contributor display name and initials\n const displayName = this.formatContributorName(contributor_id)\n const initials = this.getInitials(displayName)\n\n // Set ARIA attributes on host element\n const ariaAttrs = createAriaAttributes(element_type)\n Object.entries(ariaAttrs).forEach(([key, value]) => {\n this.setAttribute(key, value)\n })\n\n this.shadow.innerHTML = `\n <style>${styles}</style>\n \n <div class=\"skhema-insight-card\" data-theme=\"${theme}\">\n <div class=\"skhema-header\">\n <div class=\"skhema-contributor\">\n <div class=\"skhema-avatar\" title=\"${displayName}\">\n ${initials}\n </div>\n <div class=\"skhema-contributor-info\">\n <div class=\"skhema-contributor-name\">${displayName}</div>\n <div class=\"skhema-contributor-role\">Strategy Insight</div>\n </div>\n </div>\n <div class=\"skhema-element-badge\" title=\"${label}\">\n ${label}\n </div>\n </div>\n \n <div class=\"skhema-content\">\n <div class=\"skhema-content-text\">${sanitizeContent(content)}</div>\n </div>\n \n <div class=\"skhema-footer\">\n <div class=\"skhema-attribution\">\n Powered by <a href=\"https://skhema.com\" target=\"_blank\" rel=\"noopener noreferrer\">Skhema</a>\n </div>\n <a href=\"${redirectUrl}\" \n class=\"skhema-save-btn\" \n target=\"_blank\"\n rel=\"noopener noreferrer\"\n title=\"Save this insight to Skhema\">\n Save to Skhema\n </a>\n </div>\n </div>\n `\n\n // Add click event listener\n const saveBtn = this.shadow.querySelector(\n '.skhema-save-btn'\n ) as HTMLAnchorElement\n if (saveBtn) {\n saveBtn.addEventListener('click', (event) => {\n this.handleSaveClick(event)\n })\n }\n }\n\n private formatContributorName(contributorId: string): string {\n // Convert contributor_id to display name\n return contributorId\n .split(/[_-]/)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(' ')\n }\n\n private getInitials(name: string): string {\n return name\n .split(' ')\n .map((word) => word.charAt(0))\n .join('')\n .toUpperCase()\n .substring(0, 2)\n }\n\n private renderError(title: string, errors: string | string[] | unknown) {\n const errorList = Array.isArray(errors) ? errors : [String(errors)]\n\n this.shadow.innerHTML = `\n <style>${styles}</style>\n \n <div class=\"skhema-insight-card\">\n <div class=\"skhema-error\">\n <div class=\"skhema-error-title\">Skhema Component Error: ${title}</div>\n <ul class=\"skhema-error-list\">\n ${errorList.map((error) => `<li>${error}</li>`).join('')}\n </ul>\n </div>\n </div>\n `\n\n // Dispatch error event\n this.dispatchEvent(\n new CustomEvent('skhema:error', {\n detail: { error: title, details: errors },\n bubbles: true,\n })\n )\n }\n\n private addStructuredData() {\n if (!this.contentData) return\n\n const { content, element_type, contributor_id, source_url } =\n this.contentData\n\n // Add structured data to the document head\n const structuredData = generateStructuredData(\n content,\n element_type,\n contributor_id,\n source_url\n )\n const script = document.createElement('script')\n script.type = 'application/ld+json'\n script.textContent = JSON.stringify(structuredData)\n script.className = 'skhema-structured-data'\n document.head.appendChild(script)\n\n // Add meta tags for SEO\n const metaDiv = document.createElement('div')\n metaDiv.innerHTML = createMetaTags(content, element_type, contributor_id)\n metaDiv.className = 'skhema-structured-data'\n document.body.appendChild(metaDiv)\n }\n\n private async trackLoad() {\n if (\n !shouldTrackAnalytics(this as HTMLElement) ||\n !this.contentData ||\n this.hasTrackedLoad\n ) {\n return\n }\n\n this.hasTrackedLoad = true\n\n const analytics: EmbedAnalytics = {\n contributorId: this.contentData.contributor_id,\n elementType: this.contentData.element_type,\n contentHash: this.contentData.content_hash,\n content: this.contentData.content,\n pageUrl: window.location.href,\n pageTitle: document.title,\n timestamp: Date.now(),\n userAgent: navigator.userAgent,\n }\n\n await trackEmbedLoad(analytics)\n\n // Dispatch load event\n this.dispatchEvent(\n new CustomEvent('skhema:load', {\n detail: analytics,\n bubbles: true,\n })\n )\n }\n\n private async handleSaveClick(_event: Event) {\n if (!this.contentData) return\n\n // Track click analytics\n if (shouldTrackAnalytics(this as HTMLElement)) {\n await trackClick(this.contentData)\n }\n\n // Dispatch click event\n this.dispatchEvent(\n new CustomEvent('skhema:click', {\n detail: this.contentData,\n bubbles: true,\n })\n )\n }\n\n // Public API methods\n public getContentData(): ContentData | null {\n return this.contentData\n }\n\n public refresh(): void {\n this.render()\n }\n}\n\n// Type augmentation for custom events and JSX elements\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n interface HTMLElementEventMap extends SkhemaElementEventMap {}\n\n interface SkhemaElementJSX extends Partial<SkhemaElementAttributes> {\n [key: string]: unknown\n }\n\n // Module augmentation for JSX without using namespace\n interface JSXIntrinsicElements {\n 'skhema-element': SkhemaElementJSX\n }\n}\n","import { SkhemaElement } from './components/SkhemaElement.js'\n\n// Export the component class\nexport { SkhemaElement }\n\n// Export types for TypeScript users\nexport type {\n ContentData,\n EmbedAnalytics,\n SkhemaElementAttributes,\n SkhemaElementEventMap,\n} from './components/types.js'\n\n// Export utilities\nexport {\n getElementTypeAcronym,\n getElementTypeLabel,\n isValidElementType,\n validateAttributes,\n} from './utils/validation.js'\n\nexport {\n // Removed generateContentHash\n shouldTrackAnalytics,\n} from './utils/analytics.js'\n\nexport { generateRedirectUrl, generateStructuredData } from './utils/seo.js'\n\n// Manual registration function for consuming applications\nexport function registerSkhemaElement() {\n if (typeof window !== 'undefined' && !customElements.get('skhema-element')) {\n customElements.define(\n 'skhema-element',\n SkhemaElement as CustomElementConstructor\n )\n }\n}\n\n// Auto-register in browser environments (can be tree-shaken if not needed)\nif (typeof window !== 'undefined' && !customElements.get('skhema-element')) {\n customElements.define(\n 'skhema-element',\n SkhemaElement as CustomElementConstructor\n )\n}\n\n// Default export for convenience\nexport default SkhemaElement\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.es.js","sources":["../src/utils/analytics.ts","../src/utils/hash.ts","../src/utils/sanitization.ts","../src/utils/validation.ts","../src/utils/seo.ts","../src/components/SkhemaElement.ts","../src/index.ts"],"sourcesContent":["import type { ContentData, EmbedAnalytics } from '../components/types.js'\n\n/**\n * Encode string to URL-safe base64\n */\nfunction toUrlSafeBase64(str: string): string {\n // Convert string to base64\n const base64 = btoa(\n encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, p1) =>\n String.fromCharCode(parseInt(p1, 16))\n )\n )\n // Make it URL-safe by replacing + with -, / with _, and removing trailing =\n return base64.replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '')\n}\n\n// Cookie-based tracking management\ninterface TrackedEmbed {\n contentHash: string\n timestamp: number\n}\n\nconst TRACKING_COOKIE_NAME = '_sk'\nconst TRACKING_EXPIRY_HOURS = 24\nconst MAX_TRACKED_ITEMS = 50 // Prevent cookie from growing too large\n\nfunction getTrackedEmbeds(): TrackedEmbed[] {\n try {\n const cookie = document.cookie\n .split('; ')\n .find((row) => row.startsWith(`${TRACKING_COOKIE_NAME}=`))\n\n if (!cookie) return []\n\n const data = JSON.parse(decodeURIComponent(cookie.split('=')[1]))\n const now = Date.now()\n const cutoff = now - TRACKING_EXPIRY_HOURS * 60 * 60 * 1000\n\n // Filter out expired entries\n return data.filter((item: TrackedEmbed) => item.timestamp > cutoff)\n } catch {\n return []\n }\n}\n\nfunction setTrackedEmbeds(tracked: TrackedEmbed[]): void {\n try {\n // Keep only the most recent entries to prevent cookie bloat\n const limited = tracked.slice(-MAX_TRACKED_ITEMS)\n const expires = new Date(\n Date.now() + TRACKING_EXPIRY_HOURS * 60 * 60 * 1000\n )\n\n document.cookie = `${TRACKING_COOKIE_NAME}=${encodeURIComponent(\n JSON.stringify(limited)\n )}; expires=${expires.toUTCString()}; path=/; SameSite=Lax`\n } catch {\n // Fail silently if cookie storage fails\n }\n}\n\nfunction hasBeenTracked(contentHash: string): boolean {\n const tracked = getTrackedEmbeds()\n return tracked.some((item) => item.contentHash === contentHash)\n}\n\nfunction markAsTracked(contentHash: string): void {\n const tracked = getTrackedEmbeds()\n tracked.push({\n contentHash,\n timestamp: Date.now(),\n })\n setTrackedEmbeds(tracked)\n}\n\n// Batching system for analytics\ninterface BatchedAnalytics {\n embeds: EmbedAnalytics[]\n clicks: ContentData[]\n}\n\nclass AnalyticsBatcher {\n private batch: BatchedAnalytics = { embeds: [], clicks: [] }\n private batchTimeout: number | null = null\n private readonly BATCH_DELAY = 2000 // 2 seconds\n private readonly MAX_BATCH_SIZE = 10\n\n addEmbedLoad(analytics: EmbedAnalytics): void {\n this.batch.embeds.push(analytics)\n this.scheduleBatchSend()\n }\n\n addClick(contentData: ContentData): void {\n this.batch.clicks.push(contentData)\n this.scheduleBatchSend()\n }\n\n private scheduleBatchSend(): void {\n // Clear existing timeout\n if (this.batchTimeout) {\n clearTimeout(this.batchTimeout)\n }\n\n // Send immediately if batch is full\n if (\n this.batch.embeds.length >= this.MAX_BATCH_SIZE ||\n this.batch.clicks.length >= this.MAX_BATCH_SIZE\n ) {\n this.sendBatch()\n return\n }\n\n // Otherwise, wait for more events or timeout\n this.batchTimeout = window.setTimeout(() => {\n this.sendBatch()\n }, this.BATCH_DELAY)\n }\n\n private async sendBatch(): Promise<void> {\n if (this.batchTimeout) {\n clearTimeout(this.batchTimeout)\n this.batchTimeout = null\n }\n\n const currentBatch = { ...this.batch }\n this.batch = { embeds: [], clicks: [] }\n\n if (currentBatch.embeds.length === 0 && currentBatch.clicks.length === 0) {\n return\n }\n\n // Send embeds if any\n if (currentBatch.embeds.length > 0) {\n await this.sendEmbeds(currentBatch.embeds)\n }\n\n // Send clicks if any\n if (currentBatch.clicks.length > 0) {\n await this.sendClicks(currentBatch.clicks)\n }\n }\n\n private async sendEmbeds(embeds: EmbedAnalytics[]): Promise<void> {\n if (embeds.length === 0) return\n\n try {\n // Always send as array to /embed endpoint (handles 1 or many)\n const payload = {\n embeds: embeds.map((embed) => ({\n contributor_id: embed.contributorId,\n element_type: embed.elementType,\n content_hash: embed.contentHash,\n content: toUrlSafeBase64(embed.content),\n page_url: embed.pageUrl,\n page_title: embed.pageTitle || '',\n timestamp: embed.timestamp.toString(),\n user_agent: embed.userAgent || '',\n })),\n batch_size: embeds.length,\n batch_timestamp: Date.now(),\n }\n\n // Use beacon for reliability if available\n if (navigator.sendBeacon) {\n navigator.sendBeacon(\n 'https://api.skhema.com/api:XGdoUqHx/component/embed',\n new Blob([JSON.stringify(payload)], { type: 'application/json' })\n )\n } else {\n // Fallback to fetch\n await sendWithRetry(\n 'https://api.skhema.com/api:XGdoUqHx/component/embed',\n payload,\n 'json'\n )\n }\n } catch (error) {\n console.debug('Embed tracking failed:', error)\n }\n }\n\n private async sendClicks(clicks: ContentData[]): Promise<void> {\n // Send clicks individually since they typically navigate away from page\n for (const click of clicks) {\n try {\n const data = {\n contributor_id: click.contributor_id,\n element_type: click.element_type,\n content_hash: click.content_hash,\n source_url: click.source_url,\n timestamp: click.timestamp,\n }\n\n // Use beacon for reliability since user is likely navigating away\n if (navigator.sendBeacon) {\n navigator.sendBeacon(\n 'https://api.skhema.com/api:XGdoUqHx/component/click',\n new Blob([JSON.stringify(data)], { type: 'application/json' })\n )\n } else {\n await sendWithRetry(\n 'https://api.skhema.com/api:XGdoUqHx/component/click',\n data,\n 'json'\n )\n }\n } catch (error) {\n console.debug('Click tracking failed:', error)\n }\n }\n }\n\n // Ensure batch is sent when page unloads\n flush(): void {\n if (this.batch.embeds.length > 0 || this.batch.clicks.length > 0) {\n this.sendBatch()\n }\n }\n}\n\n// Global batcher instance\nconst analyticsBatcher = new AnalyticsBatcher()\n\n// Flush on page unload\nif (typeof window !== 'undefined') {\n window.addEventListener('beforeunload', () => {\n analyticsBatcher.flush()\n })\n\n // Also flush on visibility change (mobile browsers)\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n analyticsBatcher.flush()\n }\n })\n}\n\n// Retry logic with exponential backoff\nasync function sendWithRetry(\n url: string,\n data: URLSearchParams | Record<string, unknown>,\n contentType: 'json' | 'urlencoded' = 'json',\n maxRetries = 3\n): Promise<void> {\n for (let attempt = 0; attempt < maxRetries; attempt++) {\n try {\n const options: RequestInit = {\n method: 'POST',\n credentials: 'omit',\n keepalive: true,\n }\n\n if (contentType === 'json') {\n options.headers = { 'Content-Type': 'application/json' }\n options.body = JSON.stringify(data)\n } else {\n options.body = data as URLSearchParams\n }\n\n const response = await fetch(url, options)\n\n if (response.ok) return\n\n if (response.status >= 400 && response.status < 500) {\n // Client error, don't retry\n break\n }\n } catch (error) {\n if (attempt === maxRetries - 1) {\n console.debug('Analytics failed after retries:', error)\n return\n }\n }\n\n // Exponential backoff: 1s, 2s, 4s\n await new Promise((resolve) =>\n setTimeout(resolve, Math.pow(2, attempt) * 1000)\n )\n }\n}\n\n// Main tracking functions\nexport async function trackEmbedLoad(analytics: EmbedAnalytics): Promise<void> {\n try {\n // Check if this embed has already been tracked\n if (hasBeenTracked(analytics.contentHash)) {\n console.debug('Embed already tracked, skipping:', analytics.contentHash)\n return\n }\n\n // Mark as tracked before sending to prevent race conditions\n markAsTracked(analytics.contentHash)\n\n // Add to batch instead of sending immediately\n analyticsBatcher.addEmbedLoad(analytics)\n } catch (error) {\n console.debug('Analytics tracking failed:', error)\n }\n}\n\nexport async function trackClick(contentData: ContentData): Promise<void> {\n try {\n // Add to batch instead of sending immediately\n analyticsBatcher.addClick(contentData)\n } catch (error) {\n console.debug('Click tracking failed:', error)\n }\n}\n\nexport function shouldTrackAnalytics(element: HTMLElement): boolean {\n const trackAnalytics = element.getAttribute('track-analytics')\n return trackAnalytics !== 'false'\n}\n","export function generateContentHash(content: string): string {\n // Simple hash function for content identification\n let hash = 0\n const cleanContent = content.trim().substring(0, 200)\n\n for (let i = 0; i < cleanContent.length; i++) {\n const char = cleanContent.charCodeAt(i)\n hash = (hash << 5) - hash + char\n hash = hash & hash // Convert to 32bit integer\n }\n\n return Math.abs(hash).toString(36).substring(0, 12)\n}\n","/**\n * Content sanitization utilities for Skhema web component\n */\n\n/**\n * Sanitizes content to prevent XSS attacks and removes URLs\n * @param content The raw content to sanitize\n * @returns Sanitized HTML-safe content with URLs removed\n */\nexport function sanitizeContent(content: string): string {\n // HTML entity encoding for basic XSS protection\n const htmlEncode = (str: string): string => {\n const div = document.createElement('div')\n div.textContent = str\n return div.innerHTML\n }\n\n // Strip URLs first\n let sanitized = stripUrls(content)\n\n // Encode all HTML entities to prevent script injection\n sanitized = htmlEncode(sanitized)\n\n // Preserve line breaks for readability\n sanitized = sanitized.replace(/\\n/g, '<br>')\n\n // Apply text wrapping rules for long text\n sanitized = applyTextWrapping(sanitized)\n\n return sanitized\n}\n\n/**\n * Strips all URLs from the content\n * @param text The text containing potential URLs\n * @returns Text with all URLs removed\n */\nfunction stripUrls(text: string): string {\n // Comprehensive URL patterns to remove\n const patterns = [\n // Standard URLs with protocols\n /https?:\\/\\/[^\\s<>\"{}|\\\\^`[\\]]+/gi,\n // FTP URLs\n /ftp:\\/\\/[^\\s<>\"{}|\\\\^`[\\]]+/gi,\n // URLs without protocol but with www\n /www\\.[^\\s<>\"{}|\\\\^`[\\]]+/gi,\n // Email-like patterns\n /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/gi,\n // Common domain patterns (anything.com, anything.org, etc.)\n /(?:^|\\s)([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}(?:\\/[^\\s]*)?/gi,\n ]\n\n let stripped = text\n patterns.forEach((pattern) => {\n stripped = stripped.replace(pattern, '')\n })\n\n // Clean up any multiple spaces left after URL removal\n stripped = stripped.replace(/\\s+/g, ' ').trim()\n\n return stripped\n}\n\n/**\n * Applies intelligent text wrapping to prevent layout breaking\n * @param text The text to apply wrapping rules to\n * @returns Text with appropriate wrapping hints\n */\nfunction applyTextWrapping(text: string): string {\n // Split text into words\n const words = text.split(/(\\s+)/)\n\n return words\n .map((word) => {\n // Skip if it's whitespace or already contains HTML\n if (/^\\s+$/.test(word) || word.includes('<')) {\n return word\n }\n\n // For very long words (>30 chars), add word-break opportunities\n if (word.length > 30) {\n // Insert zero-width spaces every 10 characters for breaking\n return word.replace(/(.{10})/g, '$1\\u200B')\n }\n\n return word\n })\n .join('')\n}\n\n/**\n * Validates if content contains potentially malicious patterns or URLs\n * @param content The content to validate\n * @returns Object with validation status and detected issues\n */\nexport function validateContentSecurity(content: string): {\n isSecure: boolean\n issues: string[]\n} {\n const issues: string[] = []\n\n // Check for script tags\n if (/<script[\\s>]/i.test(content)) {\n issues.push('Script tags detected')\n }\n\n // Check for event handlers\n if (/on\\w+\\s*=/i.test(content)) {\n issues.push('Event handlers detected')\n }\n\n // Check for javascript: protocol\n if (/javascript:/i.test(content)) {\n issues.push('JavaScript protocol detected')\n }\n\n // Check for data: URLs that could contain scripts\n if (/data:[^,]*script/i.test(content)) {\n issues.push('Data URL with script detected')\n }\n\n // Check for iframe tags\n if (/<iframe[\\s>]/i.test(content)) {\n issues.push('Iframe tags detected')\n }\n\n // Check for URLs (since we want to disallow them)\n if (/https?:\\/\\//i.test(content) || /www\\./i.test(content)) {\n issues.push('URLs detected in content')\n }\n\n return {\n isSecure: issues.length === 0,\n issues,\n }\n}\n\n/**\n * Strips all HTML tags from content\n * @param content The content to strip\n * @returns Plain text content\n */\nexport function stripHtml(content: string): string {\n const div = document.createElement('div')\n div.innerHTML = content\n return div.textContent || div.innerText || ''\n}\n\n/**\n * Checks if content contains any URLs\n * @param content The content to check\n * @returns True if URLs are found\n */\nexport function containsUrls(content: string): boolean {\n const urlPatterns = [\n /https?:\\/\\//i,\n /ftp:\\/\\//i,\n /www\\./i,\n /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/i,\n /(?:^|\\s)([a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}(?:\\/[^\\s]*)?/i,\n ]\n\n return urlPatterns.some((pattern) => pattern.test(content))\n}\n","import type { ElementValue } from '@skhema/types'\nimport { ELEMENT_TYPES } from '@skhema/types'\n\nexport function isValidElementType(\n elementType: string\n): elementType is ElementValue {\n const validTypes = Object.values(ELEMENT_TYPES).map((type) => type.value)\n return validTypes.includes(elementType as ElementValue)\n}\n\nexport function validateAttributes(element: HTMLElement): {\n isValid: boolean\n errors: string[]\n elementType?: ElementValue\n contributorId?: string\n} {\n const errors: string[] = []\n\n const elementType = element.getAttribute('element-type')\n const contributorId = element.getAttribute('contributor-id')\n\n if (!elementType) {\n errors.push('Missing required attribute: element-type')\n } else if (!isValidElementType(elementType)) {\n const validTypes = Object.values(ELEMENT_TYPES)\n .map((t) => t.value)\n .join(', ')\n errors.push(\n `Invalid element-type \"${elementType}\". Valid types: ${validTypes}`\n )\n }\n\n if (!contributorId) {\n errors.push('Missing required attribute: contributor-id')\n } else if (contributorId.trim().length === 0) {\n errors.push('contributor-id cannot be empty')\n }\n\n return {\n isValid: errors.length === 0,\n errors,\n elementType: isValidElementType(elementType || '')\n ? (elementType as ElementValue)\n : undefined,\n contributorId: contributorId || undefined,\n }\n}\n\nexport function getElementTypeLabel(elementType: ElementValue): string {\n const type = Object.values(ELEMENT_TYPES).find((t) => t.value === elementType)\n return type?.label || elementType\n}\n\nexport function getElementTypeAcronym(elementType: ElementValue): string {\n const type = Object.values(ELEMENT_TYPES).find((t) => t.value === elementType)\n return type?.acronym || elementType.substring(0, 2).toUpperCase()\n}\n","import type { ElementValue } from '@skhema/types'\nimport { generateContentHash } from './hash.js'\nimport { getElementTypeLabel } from './validation.js'\n\nexport function generateStructuredData(\n content: string,\n elementType: ElementValue,\n contributorId: string,\n sourceUrl: string\n): object {\n return {\n '@context': 'https://schema.org',\n '@type': 'AnalysisContent',\n text: content,\n analysisType: elementType,\n category: getElementTypeLabel(elementType),\n contributor: contributorId,\n url: generateRedirectUrl(content, elementType, contributorId),\n provider: {\n '@type': 'Organization',\n name: 'Skhema',\n url: 'https://skhema.com',\n },\n isPartOf: {\n '@type': 'WebPage',\n url: sourceUrl,\n },\n dateCreated: new Date().toISOString(),\n platform: 'Skhema',\n }\n}\n\nexport function generateRedirectUrl(\n content: string,\n elementType: ElementValue,\n contributorId: string,\n options: {\n baseUrl?: string\n utmSource?: string\n utmMedium?: string\n utmCampaign?: string\n } = {}\n): string {\n const baseUrl = options.baseUrl || 'https://app.skhema.com/save' // This page will handle the authentication and content saving\n const contentHash = generateContentHash(content)\n const sourceUrl = encodeURIComponent(window.location.href)\n const timestamp = Date.now()\n\n const params = new URLSearchParams({\n source: sourceUrl,\n t: timestamp.toString(),\n utm_source: options.utmSource || 'web_component',\n utm_medium: options.utmMedium || 'embedded',\n utm_campaign: options.utmCampaign || elementType,\n utm_content: contributorId,\n })\n\n return `${baseUrl}?contributor_id=${contributorId}&element_type=${elementType}&content_hash=${contentHash}&${params.toString()}`\n // return `${baseUrl}/contributor_id=${contributorId}&element_type=${elementType}&content_hash=${contentHash}?${params.toString()}`;\n}\n\nexport function createMetaTags(\n content: string,\n elementType: ElementValue,\n contributorId: string\n): string {\n const label = getElementTypeLabel(elementType)\n\n return `\n <div itemscope itemtype=\"https://schema.org/AnalysisContent\" style=\"display:none;\">\n <meta itemprop=\"analysisType\" content=\"${elementType}\">\n <meta itemprop=\"text\" content=\"${content}\">\n <meta itemprop=\"contributor\" content=\"${contributorId}\">\n <meta itemprop=\"category\" content=\"${label}\">\n <meta itemprop=\"platform\" content=\"Skhema\">\n </div>\n `\n}\n\nexport function createAriaAttributes(\n elementType: ElementValue\n): Record<string, string> {\n const label = getElementTypeLabel(elementType)\n\n return {\n role: 'article',\n 'aria-label': `${label} - Strategic insight`,\n 'aria-describedby': 'skhema-description',\n }\n}\n","import {\n shouldTrackAnalytics,\n trackClick,\n trackEmbedLoad,\n} from '../utils/analytics.js'\nimport { generateContentHash } from '../utils/hash.js'\nimport {\n sanitizeContent,\n validateContentSecurity,\n} from '../utils/sanitization.js'\nimport {\n createAriaAttributes,\n createMetaTags,\n generateRedirectUrl,\n generateStructuredData,\n} from '../utils/seo.js'\nimport { getElementTypeLabel, validateAttributes } from '../utils/validation.js'\nimport type {\n ContentData,\n EmbedAnalytics,\n SkhemaElementAttributes,\n SkhemaElementEventMap,\n} from './types.js'\n\n// Inline styles matching Skhema UI library design system\nconst styles = `\n:host {\n /* Skhema Brand Colors - matching UI library */\n --skhema-primary: hsl(344 57% 54%); /* #cd476a */\n --skhema-primary-hover: hsl(344 50% 47%); /* #b53d5e */\n --skhema-primary-pressed: hsl(343 50% 41%); /* #9d3552 */\n --skhema-secondary: hsl(345 100% 75%); /* #ff82a2 */\n --skhema-gradient: linear-gradient(135deg, hsl(344 57% 54%) 0%, hsl(345 100% 75%) 100%);\n \n /* Light mode colors */\n --skhema-bg: hsl(0 0% 100%);\n --skhema-card: hsl(0 0% 100%);\n --skhema-border: hsl(214.3 31.8% 91.4%);\n --skhema-text: hsl(222.2 84% 4.9%);\n --skhema-text-muted: hsl(215.4 16.3% 46.9%);\n --skhema-accent: hsl(210 40% 96%);\n \n /* Shadows matching UI library */\n --skhema-shadow: 0 1px 3px 0 hsl(0 0 0 / 0.1), 0 1px 2px -1px hsl(0 0 0 / 0.1);\n --skhema-shadow-md: 0 4px 6px -1px hsl(0 0 0 / 0.1), 0 2px 4px -2px hsl(0 0 0 / 0.1);\n --skhema-shadow-lg: 0 10px 15px -3px hsl(0 0 0 / 0.1), 0 4px 6px -4px hsl(0 0 0 / 0.1);\n --skhema-radius: 0.375rem;\n \n display: block;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Inter', sans-serif;\n line-height: 1.5;\n color: var(--skhema-text);\n}\n\n/* Dark mode styles - applied via data-theme attribute */\n.skhema-insight-card[data-theme=\"dark\"],\n.skhema-skeleton[data-theme=\"dark\"] {\n --skhema-bg: hsl(222.2 84% 4.9%);\n --skhema-card: hsl(222.2 84% 4.9%);\n --skhema-border: hsl(217.2 32.6% 17.5%);\n --skhema-text: hsl(210 40% 98%);\n --skhema-text-muted: hsl(215 20.2% 65.1%);\n --skhema-accent: hsl(217.2 32.6% 17.5%);\n}\n\n/* Main component card - inspired by your design */\n.skhema-insight-card {\n position: relative;\n background: var(--skhema-card);\n border: 1px solid var(--skhema-border);\n border-radius: calc(var(--skhema-radius) * 2);\n padding: 16px;\n box-shadow: var(--skhema-shadow);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n max-width: 600px;\n margin: 8px 0;\n}\n\n.skhema-insight-card:hover {\n box-shadow: var(--skhema-shadow-lg);\n border-color: var(--skhema-primary);\n transform: translateY(-1px);\n}\n\n/* Header section with contributor info */\n.skhema-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 12px;\n gap: 12px;\n}\n\n.skhema-contributor {\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n}\n\n.skhema-avatar {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: var(--skhema-gradient);\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n font-size: 14px;\n color: white;\n flex-shrink: 0;\n}\n\n.skhema-contributor-info {\n min-width: 0;\n flex: 1;\n}\n\n.skhema-contributor-name {\n font-weight: 500;\n font-size: 14px;\n color: var(--skhema-text);\n margin: 0;\n line-height: 1.2;\n}\n\n.skhema-contributor-role {\n font-size: 12px;\n color: var(--skhema-text-muted);\n margin: 0;\n line-height: 1.2;\n}\n\n/* Element type badge */\n.skhema-element-badge {\n display: inline-flex;\n align-items: center;\n padding: 4px 8px;\n background: var(--skhema-accent);\n border: 1px solid var(--skhema-border);\n border-radius: var(--skhema-radius);\n font-size: 11px;\n font-weight: 500;\n color: var(--skhema-text-muted);\n text-transform: capitalize;\n white-space: nowrap;\n flex-shrink: 0;\n}\n\n/* Content section */\n.skhema-content {\n margin: 12px 0 16px 0;\n padding: 0;\n}\n\n.skhema-content-text {\n font-size: 15px;\n line-height: 1.6;\n color: var(--skhema-text);\n margin: 0;\n font-style: italic;\n position: relative;\n word-wrap: break-word;\n overflow-wrap: break-word;\n hyphens: auto;\n max-width: 100%;\n}\n\n.skhema-content-text::before {\n content: '\"';\n color: var(--skhema-primary);\n font-size: 20px;\n font-weight: 600;\n margin-right: 4px;\n}\n\n.skhema-content-text::after {\n content: '\"';\n color: var(--skhema-primary);\n font-size: 20px;\n font-weight: 600;\n margin-left: 4px;\n}\n\n/* Footer section */\n.skhema-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--skhema-border);\n}\n\n.skhema-attribution {\n font-size: 11px;\n color: var(--skhema-text-muted);\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.skhema-attribution a {\n color: var(--skhema-primary);\n text-decoration: none;\n font-weight: 500;\n}\n\n.skhema-attribution a:hover {\n text-decoration: underline;\n}\n\n/* Save button with gradient and arrow */\n.skhema-save-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n background: var(--skhema-gradient);\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: var(--skhema-radius);\n font-size: 13px;\n font-weight: 500;\n text-decoration: none;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: var(--skhema-shadow);\n white-space: nowrap;\n}\n\n.skhema-save-btn:hover {\n transform: translateY(-1px);\n box-shadow: var(--skhema-shadow-md);\n}\n\n.skhema-save-btn:active {\n transform: translateY(0);\n}\n\n.skhema-save-btn:focus {\n outline: 2px solid var(--skhema-primary);\n outline-offset: 2px;\n}\n\n.skhema-save-btn::after {\n content: '→';\n transition: transform 0.2s ease;\n}\n\n.skhema-save-btn:hover::after {\n transform: translateX(2px);\n}\n\n/* Error state */\n.skhema-error {\n background: hsl(0 93% 94%);\n border: 1px solid hsl(0 84% 60%);\n color: hsl(0 74% 42%);\n padding: 12px;\n border-radius: var(--skhema-radius);\n font-size: 13px;\n}\n\n.skhema-error-title {\n font-weight: 600;\n margin-bottom: 8px;\n}\n\n.skhema-error-list {\n margin: 0;\n padding-left: 16px;\n}\n\n/* Skeleton loading state */\n.skhema-skeleton {\n background: var(--skhema-card);\n border: 1px solid var(--skhema-border);\n border-radius: calc(var(--skhema-radius) * 2);\n padding: 16px;\n box-shadow: var(--skhema-shadow);\n max-width: 600px;\n margin: 8px 0;\n animation: skeletonPulse 1.5s ease-in-out infinite;\n}\n\n.skhema-skeleton-header {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 12px;\n}\n\n.skhema-skeleton-avatar {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: linear-gradient(90deg,\n var(--skhema-border) 25%,\n var(--skhema-accent) 50%,\n var(--skhema-border) 75%);\n background-size: 200% 100%;\n animation: shimmer 1.5s infinite;\n}\n\n.skhema-skeleton-text {\n flex: 1;\n}\n\n.skhema-skeleton-line {\n height: 12px;\n background: linear-gradient(90deg,\n var(--skhema-border) 25%,\n var(--skhema-accent) 50%,\n var(--skhema-border) 75%);\n background-size: 200% 100%;\n animation: shimmer 1.5s infinite;\n border-radius: var(--skhema-radius);\n margin: 6px 0;\n}\n\n.skhema-skeleton-line.short {\n width: 40%;\n}\n\n.skhema-skeleton-line.medium {\n width: 70%;\n}\n\n.skhema-skeleton-content {\n margin: 16px 0;\n}\n\n@keyframes skeletonPulse {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.8;\n }\n}\n\n@keyframes shimmer {\n 0% {\n background-position: -200% 0;\n }\n 100% {\n background-position: 200% 0;\n }\n}\n\n/* Responsive design */\n@media (max-width: 640px) {\n .skhema-insight-card {\n margin: 4px 0;\n padding: 12px;\n }\n \n .skhema-header {\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n }\n \n .skhema-footer {\n flex-direction: column;\n align-items: stretch;\n gap: 8px;\n }\n \n .skhema-save-btn {\n justify-content: center;\n }\n}\n\n/* Accessibility */\n@media (prefers-reduced-motion: reduce) {\n .skhema-insight-card,\n .skhema-save-btn {\n transition: none;\n }\n \n .skhema-save-btn::after {\n transition: none;\n }\n \n .skhema-save-btn:hover::after {\n transform: none;\n }\n}\n\n.skhema-structured-data {\n display: none !important;\n}\n`\n\nexport class SkhemaElement extends HTMLElement {\n private shadow: ShadowRoot\n private contentData: ContentData | null = null\n private componentConnected = false\n private hasTrackedLoad = false\n private themeObserver: MutationObserver | null = null\n private mediaQueryListener: MediaQueryList | null = null\n\n constructor() {\n super()\n this.shadow = this.attachShadow({ mode: 'closed' })\n // Show skeleton immediately while component initializes\n this.renderSkeleton()\n }\n\n static get observedAttributes(): (keyof SkhemaElementAttributes)[] {\n return [\n 'element-type',\n 'contributor-id',\n 'content',\n 'source-url',\n 'theme',\n 'track-analytics',\n ]\n }\n\n connectedCallback() {\n if (this.componentConnected) return\n this.componentConnected = true\n\n try {\n // Add preconnect hints for faster analytics loading\n this.addPreconnectHints()\n\n // Use requestAnimationFrame for smoother render\n requestAnimationFrame(() => {\n this.render()\n this.trackLoad()\n this.setupThemeListeners()\n })\n } catch (error) {\n this.renderError('Failed to initialize component', error)\n }\n }\n\n disconnectedCallback() {\n this.cleanupThemeListeners()\n }\n\n attributeChangedCallback(\n _name: keyof SkhemaElementAttributes,\n oldValue: string | null,\n newValue: string | null\n ) {\n if (oldValue !== newValue && this.componentConnected) {\n this.render()\n }\n }\n\n private render() {\n const validation = validateAttributes(this as HTMLElement)\n\n if (!validation.isValid) {\n this.renderError('Invalid component attributes', validation.errors)\n return\n }\n\n const content = this.getContent()\n if (!content.trim()) {\n this.renderError('Component requires content', [\n 'Add content between the opening and closing tags, or use the content attribute',\n ])\n return\n }\n\n // Validate content security\n const securityValidation = validateContentSecurity(content)\n if (!securityValidation.isSecure) {\n this.renderError(\n 'Content security validation failed',\n securityValidation.issues\n )\n return\n }\n\n this.contentData = {\n contributor_id: validation.contributorId!,\n element_type: validation.elementType!,\n content: content,\n content_hash: generateContentHash(content),\n source_url: this.getAttribute('source-url') || window.location.href,\n timestamp: new Date().toISOString(),\n page_title: document.title,\n }\n\n this.renderContent()\n this.addStructuredData()\n }\n\n private getContent(): string {\n return this.getAttribute('content') || this.textContent || ''\n }\n\n private renderContent() {\n if (!this.contentData) return\n\n const { element_type, contributor_id, content } = this.contentData\n const label = getElementTypeLabel(element_type)\n const redirectUrl = generateRedirectUrl(\n content,\n element_type,\n contributor_id\n )\n\n // Determine the actual theme to use\n const themeAttribute = this.getAttribute('theme') || 'auto'\n const actualTheme = this.getActualTheme(themeAttribute)\n\n // Generate contributor display name and initials\n const displayName = this.formatContributorName(contributor_id)\n const initials = this.getInitials(displayName)\n\n // Set ARIA attributes on host element\n const ariaAttrs = createAriaAttributes(element_type)\n Object.entries(ariaAttrs).forEach(([key, value]) => {\n this.setAttribute(key, value)\n })\n\n this.shadow.innerHTML = `\n <style>${styles}</style>\n\n <div class=\"skhema-insight-card\" data-theme=\"${actualTheme}\">\n <div class=\"skhema-header\">\n <div class=\"skhema-contributor\">\n <div class=\"skhema-avatar\" title=\"${displayName}\">\n ${initials}\n </div>\n <div class=\"skhema-contributor-info\">\n <div class=\"skhema-contributor-name\">${displayName}</div>\n <div class=\"skhema-contributor-role\">Strategy Insight</div>\n </div>\n </div>\n <div class=\"skhema-element-badge\" title=\"${label}\">\n ${label}\n </div>\n </div>\n \n <div class=\"skhema-content\">\n <div class=\"skhema-content-text\">${sanitizeContent(content)}</div>\n </div>\n \n <div class=\"skhema-footer\">\n <div class=\"skhema-attribution\">\n Powered by <a href=\"https://skhema.com\" target=\"_blank\" rel=\"noopener noreferrer\">Skhema</a>\n </div>\n <a href=\"${redirectUrl}\" \n class=\"skhema-save-btn\" \n target=\"_blank\"\n rel=\"noopener noreferrer\"\n title=\"Save this insight to Skhema\">\n Save to Skhema\n </a>\n </div>\n </div>\n `\n\n // Add click event listener\n const saveBtn = this.shadow.querySelector(\n '.skhema-save-btn'\n ) as HTMLAnchorElement\n if (saveBtn) {\n saveBtn.addEventListener('click', (event) => {\n this.handleSaveClick(event)\n })\n }\n }\n\n private getActualTheme(themeAttribute: string): 'light' | 'dark' {\n if (themeAttribute === 'light' || themeAttribute === 'dark') {\n return themeAttribute\n }\n\n // Auto mode - detect from system/page\n // First check if the page has set a data-theme or theme attribute\n const htmlElement = document.documentElement\n const bodyElement = document.body\n\n // Check common theme attributes on html or body\n const htmlTheme =\n htmlElement.getAttribute('data-theme') ||\n htmlElement.getAttribute('theme') ||\n htmlElement.className.match(/theme-(\\w+)/)?.[1]\n\n const bodyTheme =\n bodyElement.getAttribute('data-theme') ||\n bodyElement.getAttribute('theme') ||\n bodyElement.className.match(/theme-(\\w+)/)?.[1]\n\n // Check for dark mode classes\n const hasDarkClass =\n htmlElement.classList.contains('dark') ||\n bodyElement.classList.contains('dark') ||\n htmlElement.classList.contains('dark-mode') ||\n bodyElement.classList.contains('dark-mode')\n\n if (hasDarkClass || htmlTheme === 'dark' || bodyTheme === 'dark') {\n return 'dark'\n }\n\n // Check CSS custom properties that might indicate theme\n const computedStyles = window.getComputedStyle(htmlElement)\n const colorScheme = computedStyles.getPropertyValue('color-scheme')\n if (colorScheme && colorScheme.includes('dark')) {\n return 'dark'\n }\n\n // Check system preference\n if (\n window.matchMedia &&\n window.matchMedia('(prefers-color-scheme: dark)').matches\n ) {\n return 'dark'\n }\n\n // Default to light\n return 'light'\n }\n\n private formatContributorName(contributorId: string): string {\n // Convert contributor_id to display name\n return contributorId\n .split(/[_-]/)\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(' ')\n }\n\n private getInitials(name: string): string {\n return name\n .split(' ')\n .map((word) => word.charAt(0))\n .join('')\n .toUpperCase()\n .substring(0, 2)\n }\n\n private addPreconnectHints() {\n // Only add preconnect hints once per page\n if (\n document.querySelector('link[rel=\"preconnect\"][href*=\"api.skhema.com\"]')\n ) {\n return\n }\n\n try {\n // Preconnect to analytics API\n const preconnectApi = document.createElement('link')\n preconnectApi.rel = 'preconnect'\n preconnectApi.href = 'https://api.skhema.com'\n document.head.appendChild(preconnectApi)\n\n // DNS prefetch for main domain\n const dnsPrefetch = document.createElement('link')\n dnsPrefetch.rel = 'dns-prefetch'\n dnsPrefetch.href = 'https://skhema.com'\n document.head.appendChild(dnsPrefetch)\n } catch (error) {\n console.debug('Failed to add preconnect hints:', error)\n }\n }\n\n private renderSkeleton() {\n // Determine theme for skeleton\n const themeAttribute = this.getAttribute('theme') || 'auto'\n const actualTheme = this.getActualTheme(themeAttribute)\n\n this.shadow.innerHTML = `\n <style>${styles}</style>\n\n <div class=\"skhema-skeleton\" data-theme=\"${actualTheme}\">\n <div class=\"skhema-skeleton-header\">\n <div class=\"skhema-skeleton-avatar\"></div>\n <div class=\"skhema-skeleton-text\">\n <div class=\"skhema-skeleton-line medium\"></div>\n <div class=\"skhema-skeleton-line short\"></div>\n </div>\n </div>\n <div class=\"skhema-skeleton-content\">\n <div class=\"skhema-skeleton-line\"></div>\n <div class=\"skhema-skeleton-line\"></div>\n <div class=\"skhema-skeleton-line medium\"></div>\n </div>\n </div>\n `\n }\n\n private renderError(title: string, errors: string | string[] | unknown) {\n const errorList = Array.isArray(errors) ? errors : [String(errors)]\n\n this.shadow.innerHTML = `\n <style>${styles}</style>\n\n <div class=\"skhema-insight-card\">\n <div class=\"skhema-error\">\n <div class=\"skhema-error-title\">Skhema Component Error: ${title}</div>\n <ul class=\"skhema-error-list\">\n ${errorList.map((error) => `<li>${error}</li>`).join('')}\n </ul>\n </div>\n </div>\n `\n\n // Dispatch error event\n this.dispatchEvent(\n new CustomEvent('skhema:error', {\n detail: { error: title, details: errors },\n bubbles: true,\n })\n )\n }\n\n private addStructuredData() {\n if (!this.contentData) return\n\n const { content, element_type, contributor_id, source_url } =\n this.contentData\n\n // Add structured data to the document head\n const structuredData = generateStructuredData(\n content,\n element_type,\n contributor_id,\n source_url\n )\n const script = document.createElement('script')\n script.type = 'application/ld+json'\n script.textContent = JSON.stringify(structuredData)\n script.className = 'skhema-structured-data'\n document.head.appendChild(script)\n\n // Add meta tags for SEO\n const metaDiv = document.createElement('div')\n metaDiv.innerHTML = createMetaTags(content, element_type, contributor_id)\n metaDiv.className = 'skhema-structured-data'\n document.body.appendChild(metaDiv)\n }\n\n private async trackLoad() {\n if (\n !shouldTrackAnalytics(this as HTMLElement) ||\n !this.contentData ||\n this.hasTrackedLoad\n ) {\n return\n }\n\n this.hasTrackedLoad = true\n\n const analytics: EmbedAnalytics = {\n contributorId: this.contentData.contributor_id,\n elementType: this.contentData.element_type,\n contentHash: this.contentData.content_hash,\n content: this.contentData.content,\n pageUrl: window.location.href,\n pageTitle: document.title,\n timestamp: Date.now(),\n userAgent: navigator.userAgent,\n }\n\n await trackEmbedLoad(analytics)\n\n // Dispatch load event\n this.dispatchEvent(\n new CustomEvent('skhema:load', {\n detail: analytics,\n bubbles: true,\n })\n )\n }\n\n private async handleSaveClick(_event: Event) {\n if (!this.contentData) return\n\n // Track click analytics\n if (shouldTrackAnalytics(this as HTMLElement)) {\n await trackClick(this.contentData)\n }\n\n // Dispatch click event\n this.dispatchEvent(\n new CustomEvent('skhema:click', {\n detail: this.contentData,\n bubbles: true,\n })\n )\n }\n\n // Public API methods\n public getContentData(): ContentData | null {\n return this.contentData\n }\n\n public refresh(): void {\n this.render()\n }\n\n private setupThemeListeners(): void {\n // Only set up listeners if theme is 'auto' or not specified\n const themeAttribute = this.getAttribute('theme')\n if (themeAttribute === 'auto' || !themeAttribute) {\n // Listen for system theme changes\n if (window.matchMedia) {\n this.mediaQueryListener = window.matchMedia(\n '(prefers-color-scheme: dark)'\n )\n const handleThemeChange = () => this.updateTheme()\n this.mediaQueryListener.addEventListener('change', handleThemeChange)\n }\n\n // Listen for theme changes on html/body elements\n this.themeObserver = new MutationObserver(() => this.updateTheme())\n\n // Observe both html and body for attribute changes\n this.themeObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['class', 'data-theme', 'theme'],\n })\n\n this.themeObserver.observe(document.body, {\n attributes: true,\n attributeFilter: ['class', 'data-theme', 'theme'],\n })\n }\n }\n\n private cleanupThemeListeners(): void {\n if (this.themeObserver) {\n this.themeObserver.disconnect()\n this.themeObserver = null\n }\n\n if (this.mediaQueryListener) {\n // Note: We can't remove the specific listener without storing the reference\n // but setting to null will allow garbage collection\n this.mediaQueryListener = null\n }\n }\n\n private updateTheme(): void {\n // Only update if component is using auto theme\n const themeAttribute = this.getAttribute('theme') || 'auto'\n if (themeAttribute === 'auto') {\n // Re-render with new detected theme\n const card = this.shadow.querySelector('.skhema-insight-card')\n if (card) {\n const newTheme = this.getActualTheme('auto')\n card.setAttribute('data-theme', newTheme)\n }\n }\n }\n}\n\n// Type augmentation for custom events and JSX elements\ndeclare global {\n // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n interface HTMLElementEventMap extends SkhemaElementEventMap {}\n\n interface SkhemaElementJSX extends Partial<SkhemaElementAttributes> {\n [key: string]: unknown\n }\n\n // Module augmentation for JSX without using namespace\n interface JSXIntrinsicElements {\n 'skhema-element': SkhemaElementJSX\n }\n}\n","import { SkhemaElement } from './components/SkhemaElement.js'\n\n// Export the component class\nexport { SkhemaElement }\n\n// Export types for TypeScript users\nexport type {\n ContentData,\n EmbedAnalytics,\n SkhemaElementAttributes,\n SkhemaElementEventMap,\n} from './components/types.js'\n\n// Export utilities\nexport {\n getElementTypeAcronym,\n getElementTypeLabel,\n isValidElementType,\n validateAttributes,\n} from './utils/validation.js'\n\nexport {\n // Removed generateContentHash\n shouldTrackAnalytics,\n} from './utils/analytics.js'\n\nexport { generateRedirectUrl, generateStructuredData } from './utils/seo.js'\n\n// Manual registration function for consuming applications\nexport function registerSkhemaElement() {\n if (typeof window !== 'undefined' && !customElements.get('skhema-element')) {\n customElements.define(\n 'skhema-element',\n SkhemaElement as CustomElementConstructor\n )\n }\n}\n\n// Auto-register in browser environments (can be tree-shaken if not needed)\nif (typeof window !== 'undefined' && !customElements.get('skhema-element')) {\n customElements.define(\n 'skhema-element',\n SkhemaElement as CustomElementConstructor\n )\n}\n\n// Default export for convenience\nexport default SkhemaElement\n"],"names":[],"mappings":"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A;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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"}
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/utils/analytics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AA+QzE,wBAAsB,cAAc,CAAC,SAAS,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB7E;AAED,wBAAsB,UAAU,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAOxE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAGlE"}
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/utils/analytics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AA0RzE,wBAAsB,cAAc,CAAC,SAAS,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB7E;AAED,wBAAsB,UAAU,CAAC,WAAW,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAOxE;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAGlE"}
@@ -1,2 +1,2 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).SkhemaWebComponent={})}(this,function(e){"use strict";function t(e){return btoa(encodeURIComponent(e).replace(/%([0-9A-F]{2})/g,(e,t)=>String.fromCharCode(parseInt(t,16)))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function n(){try{const e=document.cookie.split("; ").find(e=>e.startsWith("_sk="));if(!e)return[];const t=JSON.parse(decodeURIComponent(e.split("=")[1])),n=Date.now()-864e5;return t.filter(e=>e.timestamp>n)}catch{return[]}}function a(e){const t=n();t.push({contentHash:e,timestamp:Date.now()}),function(e){try{const t=e.slice(-50),n=new Date(Date.now()+864e5);document.cookie=`_sk=${encodeURIComponent(JSON.stringify(t))}; expires=${n.toUTCString()}; path=/; SameSite=Lax`}catch{}}(t)}const s=new class{constructor(){this.batch={embeds:[],clicks:[]},this.batchTimeout=null,this.BATCH_DELAY=2e3,this.MAX_BATCH_SIZE=10}addEmbedLoad(e){this.batch.embeds.push(e),this.scheduleBatchSend()}addClick(e){this.batch.clicks.push(e),this.scheduleBatchSend()}scheduleBatchSend(){this.batchTimeout&&clearTimeout(this.batchTimeout),this.batch.embeds.length>=this.MAX_BATCH_SIZE||this.batch.clicks.length>=this.MAX_BATCH_SIZE?this.sendBatch():this.batchTimeout=window.setTimeout(()=>{this.sendBatch()},this.BATCH_DELAY)}async sendBatch(){this.batchTimeout&&(clearTimeout(this.batchTimeout),this.batchTimeout=null);const e={...this.batch};this.batch={embeds:[],clicks:[]},0===e.embeds.length&&0===e.clicks.length||(e.embeds.length>0&&await this.sendEmbeds(e.embeds),e.clicks.length>0&&await this.sendClicks(e.clicks))}async sendEmbeds(e){for(const a of e)try{const e=new URLSearchParams({contributor_id:a.contributorId,element_type:a.elementType,content_hash:a.contentHash,content:t(a.content),page_url:a.pageUrl,page_title:a.pageTitle||"",timestamp:a.timestamp.toString(),user_agent:a.userAgent||""});navigator.sendBeacon?navigator.sendBeacon("https://api.skhema.com/api:XGdoUqHx/component/embed",e):await i("https://api.skhema.com/api:XGdoUqHx/component/embed",e,"urlencoded")}catch(n){console.debug("Embed tracking failed:",n)}}async sendClicks(e){for(const n of e)try{const e={contributor_id:n.contributor_id,element_type:n.element_type,content_hash:n.content_hash,source_url:n.source_url,timestamp:n.timestamp};await i("https://api.skhema.com/api:XGdoUqHx/component/click",e,"json")}catch(t){console.debug("Click tracking failed:",t)}}flush(){(this.batch.embeds.length>0||this.batch.clicks.length>0)&&this.sendBatch()}};async function i(e,t,n="json",a=3){for(let i=0;i<a;i++){try{const a={method:"POST",credentials:"omit",keepalive:!0};"json"===n?(a.headers={"Content-Type":"application/json"},a.body=JSON.stringify(t)):a.body=t;const s=await fetch(e,a);if(s.ok)return;if(s.status>=400&&s.status<500)break}catch(s){if(i===a-1)return void console.debug("Analytics failed after retries:",s)}await new Promise(e=>setTimeout(e,1e3*Math.pow(2,i)))}}async function o(e){try{if(t=e.contentHash,n().some(e=>e.contentHash===t))return void console.debug("Embed already tracked, skipping:",e.contentHash);a(e.contentHash),s.addEmbedLoad(e)}catch(i){console.debug("Analytics tracking failed:",i)}var t}function r(e){return"false"!==e.getAttribute("track-analytics")}function c(e){let t=0;const n=e.trim().substring(0,200);for(let a=0;a<n.length;a++){t=(t<<5)-t+n.charCodeAt(a),t&=t}return Math.abs(t).toString(36).substring(0,12)}"undefined"!=typeof window&&(window.addEventListener("beforeunload",()=>{s.flush()}),document.addEventListener("visibilitychange",()=>{"hidden"===document.visibilityState&&s.flush()}));const h={KEY_CHALLENGE:{value:"key_challenge",label:"Key Challenge",acronym:"KC"},SUPPORTING_FACT:{value:"supporting_fact",label:"Supporting Fact",acronym:"SF"},ASSOCIATED_IMPACT:{value:"associated_impact",label:"Associated Impact",acronym:"AI"},GUIDING_POLICY:{value:"guiding_policy",label:"Guiding Policy",acronym:"GP"},COMPETITOR_MOVE:{value:"competitor_move",label:"Competitor Move",acronym:"CM"},SCOPE:{value:"scope",label:"Scope",acronym:"SC"},CONSTRAINT:{value:"constraint",label:"Constraint",acronym:"CN"},SOLUTION_ALTERNATIVE:{value:"solution_alternative",label:"Solution Alternative",acronym:"SA"},ASSUMPTION_HYPOTHESIS:{value:"assumption_hypothesis",label:"Assumption Hypothesis",acronym:"AH"},EXPERIMENT:{value:"experiment",label:"Experiment",acronym:"EX"},ACTION:{value:"action",label:"Action",acronym:"AC"},INVESTMENT:{value:"investment",label:"Investment",acronym:"IN"},ESTIMATE:{value:"estimate",label:"Estimate",acronym:"ES"},BASELINE:{value:"baseline",label:"Baseline",acronym:"BL"},OUTCOME:{value:"outcome",label:"Outcome",acronym:"OC"},PERFORMANCE_VARIABLE:{value:"performance_variable",label:"Performance Variable",acronym:"PV"},CAPABILITY:{value:"capability",label:"Capability",acronym:"CP"},SYSTEM:{value:"system",label:"System",acronym:"SY"},PRINCIPLE:{value:"principle",label:"Principle",acronym:"PR"}};function l(e){return Object.values(h).map(e=>e.value).includes(e)}function d(e){const t=Object.values(h).find(t=>t.value===e);return t?.label||e}function m(e,t,n,a={}){const s=a.baseUrl||"https://app.skhema.com/save",i=c(e),o=encodeURIComponent(window.location.href),r=Date.now();return`${s}?contributor_id=${n}&element_type=${t}&content_hash=${i}&${new URLSearchParams({source:o,t:r.toString(),utm_source:a.utmSource||"web_component",utm_medium:a.utmMedium||"embedded",utm_campaign:a.utmCampaign||t,utm_content:n}).toString()}`}const p="\n:host {\n /* Skhema Brand Colors - matching UI library */\n --skhema-primary: hsl(344 57% 54%); /* #cd476a */\n --skhema-primary-hover: hsl(344 50% 47%); /* #b53d5e */\n --skhema-primary-pressed: hsl(343 50% 41%); /* #9d3552 */\n --skhema-secondary: hsl(345 100% 75%); /* #ff82a2 */\n --skhema-gradient: linear-gradient(135deg, hsl(344 57% 54%) 0%, hsl(345 100% 75%) 100%);\n \n /* Light mode colors */\n --skhema-bg: hsl(0 0% 100%);\n --skhema-card: hsl(0 0% 100%);\n --skhema-border: hsl(214.3 31.8% 91.4%);\n --skhema-text: hsl(222.2 84% 4.9%);\n --skhema-text-muted: hsl(215.4 16.3% 46.9%);\n --skhema-accent: hsl(210 40% 96%);\n \n /* Shadows matching UI library */\n --skhema-shadow: 0 1px 3px 0 hsl(0 0 0 / 0.1), 0 1px 2px -1px hsl(0 0 0 / 0.1);\n --skhema-shadow-md: 0 4px 6px -1px hsl(0 0 0 / 0.1), 0 2px 4px -2px hsl(0 0 0 / 0.1);\n --skhema-shadow-lg: 0 10px 15px -3px hsl(0 0 0 / 0.1), 0 4px 6px -4px hsl(0 0 0 / 0.1);\n --skhema-radius: 0.375rem;\n \n display: block;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Inter', sans-serif;\n line-height: 1.5;\n color: var(--skhema-text);\n}\n\n:host([theme=\"dark\"]) {\n /* Dark mode colors */\n --skhema-bg: hsl(222.2 84% 4.9%);\n --skhema-card: hsl(222.2 84% 4.9%);\n --skhema-border: hsl(217.2 32.6% 17.5%);\n --skhema-text: hsl(210 40% 98%);\n --skhema-text-muted: hsl(215 20.2% 65.1%);\n --skhema-accent: hsl(217.2 32.6% 17.5%);\n}\n\n/* Main component card - inspired by your design */\n.skhema-insight-card {\n position: relative;\n background: var(--skhema-card);\n border: 1px solid var(--skhema-border);\n border-radius: calc(var(--skhema-radius) * 2);\n padding: 16px;\n box-shadow: var(--skhema-shadow);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n max-width: 600px;\n margin: 8px 0;\n}\n\n.skhema-insight-card:hover {\n box-shadow: var(--skhema-shadow-lg);\n border-color: var(--skhema-primary);\n transform: translateY(-1px);\n}\n\n/* Header section with contributor info */\n.skhema-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 12px;\n gap: 12px;\n}\n\n.skhema-contributor {\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n}\n\n.skhema-avatar {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: var(--skhema-gradient);\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n font-size: 14px;\n color: white;\n flex-shrink: 0;\n}\n\n.skhema-contributor-info {\n min-width: 0;\n flex: 1;\n}\n\n.skhema-contributor-name {\n font-weight: 500;\n font-size: 14px;\n color: var(--skhema-text);\n margin: 0;\n line-height: 1.2;\n}\n\n.skhema-contributor-role {\n font-size: 12px;\n color: var(--skhema-text-muted);\n margin: 0;\n line-height: 1.2;\n}\n\n/* Element type badge */\n.skhema-element-badge {\n display: inline-flex;\n align-items: center;\n padding: 4px 8px;\n background: var(--skhema-accent);\n border: 1px solid var(--skhema-border);\n border-radius: var(--skhema-radius);\n font-size: 11px;\n font-weight: 500;\n color: var(--skhema-text-muted);\n text-transform: capitalize;\n white-space: nowrap;\n flex-shrink: 0;\n}\n\n/* Content section */\n.skhema-content {\n margin: 12px 0 16px 0;\n padding: 0;\n}\n\n.skhema-content-text {\n font-size: 15px;\n line-height: 1.6;\n color: var(--skhema-text);\n margin: 0;\n font-style: italic;\n position: relative;\n word-wrap: break-word;\n overflow-wrap: break-word;\n hyphens: auto;\n max-width: 100%;\n}\n\n.skhema-content-text::before {\n content: '\"';\n color: var(--skhema-primary);\n font-size: 20px;\n font-weight: 600;\n margin-right: 4px;\n}\n\n.skhema-content-text::after {\n content: '\"';\n color: var(--skhema-primary);\n font-size: 20px;\n font-weight: 600;\n margin-left: 4px;\n}\n\n/* Footer section */\n.skhema-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--skhema-border);\n}\n\n.skhema-attribution {\n font-size: 11px;\n color: var(--skhema-text-muted);\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.skhema-attribution a {\n color: var(--skhema-primary);\n text-decoration: none;\n font-weight: 500;\n}\n\n.skhema-attribution a:hover {\n text-decoration: underline;\n}\n\n/* Save button with gradient and arrow */\n.skhema-save-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n background: var(--skhema-gradient);\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: var(--skhema-radius);\n font-size: 13px;\n font-weight: 500;\n text-decoration: none;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: var(--skhema-shadow);\n white-space: nowrap;\n}\n\n.skhema-save-btn:hover {\n transform: translateY(-1px);\n box-shadow: var(--skhema-shadow-md);\n}\n\n.skhema-save-btn:active {\n transform: translateY(0);\n}\n\n.skhema-save-btn:focus {\n outline: 2px solid var(--skhema-primary);\n outline-offset: 2px;\n}\n\n.skhema-save-btn::after {\n content: '→';\n transition: transform 0.2s ease;\n}\n\n.skhema-save-btn:hover::after {\n transform: translateX(2px);\n}\n\n/* Error state */\n.skhema-error {\n background: hsl(0 93% 94%);\n border: 1px solid hsl(0 84% 60%);\n color: hsl(0 74% 42%);\n padding: 12px;\n border-radius: var(--skhema-radius);\n font-size: 13px;\n}\n\n.skhema-error-title {\n font-weight: 600;\n margin-bottom: 8px;\n}\n\n.skhema-error-list {\n margin: 0;\n padding-left: 16px;\n}\n\n/* Loading state */\n.skhema-loading {\n background: var(--skhema-accent);\n border: 1px solid var(--skhema-border);\n padding: 12px;\n border-radius: var(--skhema-radius);\n color: var(--skhema-text-muted);\n font-size: 13px;\n text-align: center;\n}\n\n.skhema-loading::after {\n content: '...';\n animation: loading 1.5s infinite;\n}\n\n@keyframes loading {\n 0%, 33% { content: '...'; }\n 66% { content: '..'; }\n 100% { content: '.'; }\n}\n\n/* Responsive design */\n@media (max-width: 640px) {\n .skhema-insight-card {\n margin: 4px 0;\n padding: 12px;\n }\n \n .skhema-header {\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n }\n \n .skhema-footer {\n flex-direction: column;\n align-items: stretch;\n gap: 8px;\n }\n \n .skhema-save-btn {\n justify-content: center;\n }\n}\n\n/* Accessibility */\n@media (prefers-reduced-motion: reduce) {\n .skhema-insight-card,\n .skhema-save-btn {\n transition: none;\n }\n \n .skhema-save-btn::after {\n transition: none;\n }\n \n .skhema-save-btn:hover::after {\n transform: none;\n }\n}\n\n.skhema-structured-data {\n display: none !important;\n}\n";class u extends HTMLElement{constructor(){super(),this.contentData=null,this.componentConnected=!1,this.hasTrackedLoad=!1,this.shadow=this.attachShadow({mode:"closed"})}static get observedAttributes(){return["element-type","contributor-id","content","source-url","theme","track-analytics"]}connectedCallback(){if(!this.componentConnected){this.componentConnected=!0;try{this.render(),this.trackLoad()}catch(e){this.renderError("Failed to initialize component",e)}}}attributeChangedCallback(e,t,n){t!==n&&this.componentConnected&&this.render()}render(){const e=function(e){const t=[],n=e.getAttribute("element-type"),a=e.getAttribute("contributor-id");if(n){if(!l(n)){const e=Object.values(h).map(e=>e.value).join(", ");t.push(`Invalid element-type "${n}". Valid types: ${e}`)}}else t.push("Missing required attribute: element-type");return a?0===a.trim().length&&t.push("contributor-id cannot be empty"):t.push("Missing required attribute: contributor-id"),{isValid:0===t.length,errors:t,elementType:l(n||"")?n:void 0,contributorId:a||void 0}}(this);if(!e.isValid)return void this.renderError("Invalid component attributes",e.errors);const t=this.getContent();if(!t.trim())return void this.renderError("Component requires content",["Add content between the opening and closing tags, or use the content attribute"]);const n=function(e){const t=[];return/<script[\s>]/i.test(e)&&t.push("Script tags detected"),/on\w+\s*=/i.test(e)&&t.push("Event handlers detected"),/javascript:/i.test(e)&&t.push("JavaScript protocol detected"),/data:[^,]*script/i.test(e)&&t.push("Data URL with script detected"),/<iframe[\s>]/i.test(e)&&t.push("Iframe tags detected"),(/https?:\/\//i.test(e)||/www\./i.test(e))&&t.push("URLs detected in content"),{isSecure:0===t.length,issues:t}}(t);n.isSecure?(this.contentData={contributor_id:e.contributorId,element_type:e.elementType,content:t,content_hash:c(t),source_url:this.getAttribute("source-url")||window.location.href,timestamp:(new Date).toISOString(),page_title:document.title},this.renderContent(),this.addStructuredData()):this.renderError("Content security validation failed",n.issues)}getContent(){return this.getAttribute("content")||this.textContent||""}renderContent(){if(!this.contentData)return;const{element_type:e,contributor_id:t,content:n}=this.contentData,a=d(e),s=m(n,e,t),i=this.getAttribute("theme")||"auto",o=this.formatContributorName(t),r=this.getInitials(o),c={role:"article","aria-label":`${d(e)} - Strategic insight`,"aria-describedby":"skhema-description"};Object.entries(c).forEach(([e,t])=>{this.setAttribute(e,t)}),this.shadow.innerHTML=`\n <style>${p}</style>\n \n <div class="skhema-insight-card" data-theme="${i}">\n <div class="skhema-header">\n <div class="skhema-contributor">\n <div class="skhema-avatar" title="${o}">\n ${r}\n </div>\n <div class="skhema-contributor-info">\n <div class="skhema-contributor-name">${o}</div>\n <div class="skhema-contributor-role">Strategy Insight</div>\n </div>\n </div>\n <div class="skhema-element-badge" title="${a}">\n ${a}\n </div>\n </div>\n \n <div class="skhema-content">\n <div class="skhema-content-text">${function(e){let t=function(e){let t=e;return[/https?:\/\/[^\s<>"{}|\\^`[\]]+/gi,/ftp:\/\/[^\s<>"{}|\\^`[\]]+/gi,/www\.[^\s<>"{}|\\^`[\]]+/gi,/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/gi,/(?:^|\s)([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[^\s]*)?/gi].forEach(e=>{t=t.replace(e,"")}),t=t.replace(/\s+/g," ").trim(),t}(e);return t=(e=>{const t=document.createElement("div");return t.textContent=e,t.innerHTML})(t),t=t.replace(/\n/g,"<br>"),t=t.split(/(\s+)/).map(e=>/^\s+$/.test(e)||e.includes("<")?e:e.length>30?e.replace(/(.{10})/g,"$1​"):e).join(""),t}(n)}</div>\n </div>\n \n <div class="skhema-footer">\n <div class="skhema-attribution">\n Powered by <a href="https://skhema.com" target="_blank" rel="noopener noreferrer">Skhema</a>\n </div>\n <a href="${s}" \n class="skhema-save-btn" \n target="_blank"\n rel="noopener noreferrer"\n title="Save this insight to Skhema">\n Save to Skhema\n </a>\n </div>\n </div>\n `;const h=this.shadow.querySelector(".skhema-save-btn");h&&h.addEventListener("click",e=>{this.handleSaveClick(e)})}formatContributorName(e){return e.split(/[_-]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ")}getInitials(e){return e.split(" ").map(e=>e.charAt(0)).join("").toUpperCase().substring(0,2)}renderError(e,t){const n=Array.isArray(t)?t:[String(t)];this.shadow.innerHTML=`\n <style>${p}</style>\n \n <div class="skhema-insight-card">\n <div class="skhema-error">\n <div class="skhema-error-title">Skhema Component Error: ${e}</div>\n <ul class="skhema-error-list">\n ${n.map(e=>`<li>${e}</li>`).join("")}\n </ul>\n </div>\n </div>\n `,this.dispatchEvent(new CustomEvent("skhema:error",{detail:{error:e,details:t},bubbles:!0}))}addStructuredData(){if(!this.contentData)return;const{content:e,element_type:t,contributor_id:n,source_url:a}=this.contentData,s=function(e,t,n,a){return{"@context":"https://schema.org","@type":"AnalysisContent",text:e,analysisType:t,category:d(t),contributor:n,url:m(e,t,n),provider:{"@type":"Organization",name:"Skhema",url:"https://skhema.com"},isPartOf:{"@type":"WebPage",url:a},dateCreated:(new Date).toISOString(),platform:"Skhema"}}(e,t,n,a),i=document.createElement("script");i.type="application/ld+json",i.textContent=JSON.stringify(s),i.className="skhema-structured-data",document.head.appendChild(i);const o=document.createElement("div");o.innerHTML=function(e,t,n){return`\n <div itemscope itemtype="https://schema.org/AnalysisContent" style="display:none;">\n <meta itemprop="analysisType" content="${t}">\n <meta itemprop="text" content="${e}">\n <meta itemprop="contributor" content="${n}">\n <meta itemprop="category" content="${d(t)}">\n <meta itemprop="platform" content="Skhema">\n </div>\n `}(e,t,n),o.className="skhema-structured-data",document.body.appendChild(o)}async trackLoad(){if(!r(this)||!this.contentData||this.hasTrackedLoad)return;this.hasTrackedLoad=!0;const e={contributorId:this.contentData.contributor_id,elementType:this.contentData.element_type,contentHash:this.contentData.content_hash,content:this.contentData.content,pageUrl:window.location.href,pageTitle:document.title,timestamp:Date.now(),userAgent:navigator.userAgent};await o(e),this.dispatchEvent(new CustomEvent("skhema:load",{detail:e,bubbles:!0}))}async handleSaveClick(e){this.contentData&&(r(this)&&await async function(e){try{s.addClick(e)}catch(t){console.debug("Click tracking failed:",t)}}(this.contentData),this.dispatchEvent(new CustomEvent("skhema:click",{detail:this.contentData,bubbles:!0})))}getContentData(){return this.contentData}refresh(){this.render()}}customElements.get("skhema-element")||customElements.define("skhema-element",u),"undefined"!=typeof window&&(window.SkhemaElement=u),e.SkhemaElement=u,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).SkhemaWebComponent={})}(this,function(e){"use strict";function t(){try{const e=document.cookie.split("; ").find(e=>e.startsWith("_sk="));if(!e)return[];const t=JSON.parse(decodeURIComponent(e.split("=")[1])),n=Date.now()-864e5;return t.filter(e=>e.timestamp>n)}catch{return[]}}function n(e){const n=t();n.push({contentHash:e,timestamp:Date.now()}),function(e){try{const t=e.slice(-50),n=new Date(Date.now()+864e5);document.cookie=`_sk=${encodeURIComponent(JSON.stringify(t))}; expires=${n.toUTCString()}; path=/; SameSite=Lax`}catch{}}(n)}const a=new class{constructor(){this.batch={embeds:[],clicks:[]},this.batchTimeout=null,this.BATCH_DELAY=2e3,this.MAX_BATCH_SIZE=10}addEmbedLoad(e){this.batch.embeds.push(e),this.scheduleBatchSend()}addClick(e){this.batch.clicks.push(e),this.scheduleBatchSend()}scheduleBatchSend(){this.batchTimeout&&clearTimeout(this.batchTimeout),this.batch.embeds.length>=this.MAX_BATCH_SIZE||this.batch.clicks.length>=this.MAX_BATCH_SIZE?this.sendBatch():this.batchTimeout=window.setTimeout(()=>{this.sendBatch()},this.BATCH_DELAY)}async sendBatch(){this.batchTimeout&&(clearTimeout(this.batchTimeout),this.batchTimeout=null);const e={...this.batch};this.batch={embeds:[],clicks:[]},0===e.embeds.length&&0===e.clicks.length||(e.embeds.length>0&&await this.sendEmbeds(e.embeds),e.clicks.length>0&&await this.sendClicks(e.clicks))}async sendEmbeds(e){if(0!==e.length)try{const t={embeds:e.map(e=>{return{contributor_id:e.contributorId,element_type:e.elementType,content_hash:e.contentHash,content:(t=e.content,btoa(encodeURIComponent(t).replace(/%([0-9A-F]{2})/g,(e,t)=>String.fromCharCode(parseInt(t,16)))).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")),page_url:e.pageUrl,page_title:e.pageTitle||"",timestamp:e.timestamp.toString(),user_agent:e.userAgent||""};var t}),batch_size:e.length,batch_timestamp:Date.now()};navigator.sendBeacon?navigator.sendBeacon("https://api.skhema.com/api:XGdoUqHx/component/embed",new Blob([JSON.stringify(t)],{type:"application/json"})):await s("https://api.skhema.com/api:XGdoUqHx/component/embed",t,"json")}catch(t){console.debug("Embed tracking failed:",t)}}async sendClicks(e){for(const n of e)try{const e={contributor_id:n.contributor_id,element_type:n.element_type,content_hash:n.content_hash,source_url:n.source_url,timestamp:n.timestamp};navigator.sendBeacon?navigator.sendBeacon("https://api.skhema.com/api:XGdoUqHx/component/click",new Blob([JSON.stringify(e)],{type:"application/json"})):await s("https://api.skhema.com/api:XGdoUqHx/component/click",e,"json")}catch(t){console.debug("Click tracking failed:",t)}}flush(){(this.batch.embeds.length>0||this.batch.clicks.length>0)&&this.sendBatch()}};async function s(e,t,n="json",a=3){for(let i=0;i<a;i++){try{const a={method:"POST",credentials:"omit",keepalive:!0};"json"===n?(a.headers={"Content-Type":"application/json"},a.body=JSON.stringify(t)):a.body=t;const s=await fetch(e,a);if(s.ok)return;if(s.status>=400&&s.status<500)break}catch(s){if(i===a-1)return void console.debug("Analytics failed after retries:",s)}await new Promise(e=>setTimeout(e,1e3*Math.pow(2,i)))}}async function i(e){try{if(s=e.contentHash,t().some(e=>e.contentHash===s))return void console.debug("Embed already tracked, skipping:",e.contentHash);n(e.contentHash),a.addEmbedLoad(e)}catch(i){console.debug("Analytics tracking failed:",i)}var s}function r(e){return"false"!==e.getAttribute("track-analytics")}function o(e){let t=0;const n=e.trim().substring(0,200);for(let a=0;a<n.length;a++){t=(t<<5)-t+n.charCodeAt(a),t&=t}return Math.abs(t).toString(36).substring(0,12)}"undefined"!=typeof window&&(window.addEventListener("beforeunload",()=>{a.flush()}),document.addEventListener("visibilitychange",()=>{"hidden"===document.visibilityState&&a.flush()}));const c={KEY_CHALLENGE:{value:"key_challenge",label:"Key Challenge",acronym:"KC"},SUPPORTING_FACT:{value:"supporting_fact",label:"Supporting Fact",acronym:"SF"},ASSOCIATED_IMPACT:{value:"associated_impact",label:"Associated Impact",acronym:"AI"},GUIDING_POLICY:{value:"guiding_policy",label:"Guiding Policy",acronym:"GP"},COMPETITOR_MOVE:{value:"competitor_move",label:"Competitor Move",acronym:"CM"},SCOPE:{value:"scope",label:"Scope",acronym:"SC"},CONSTRAINT:{value:"constraint",label:"Constraint",acronym:"CN"},SOLUTION_ALTERNATIVE:{value:"solution_alternative",label:"Solution Alternative",acronym:"SA"},ASSUMPTION_HYPOTHESIS:{value:"assumption_hypothesis",label:"Assumption Hypothesis",acronym:"AH"},EXPERIMENT:{value:"experiment",label:"Experiment",acronym:"EX"},ACTION:{value:"action",label:"Action",acronym:"AC"},INVESTMENT:{value:"investment",label:"Investment",acronym:"IN"},ESTIMATE:{value:"estimate",label:"Estimate",acronym:"ES"},BASELINE:{value:"baseline",label:"Baseline",acronym:"BL"},OUTCOME:{value:"outcome",label:"Outcome",acronym:"OC"},PERFORMANCE_VARIABLE:{value:"performance_variable",label:"Performance Variable",acronym:"PV"},CAPABILITY:{value:"capability",label:"Capability",acronym:"CP"},SYSTEM:{value:"system",label:"System",acronym:"SY"},PRINCIPLE:{value:"principle",label:"Principle",acronym:"PR"}};function h(e){return Object.values(c).map(e=>e.value).includes(e)}function d(e){const t=Object.values(c).find(t=>t.value===e);return t?.label||e}function l(e,t,n,a={}){const s=a.baseUrl||"https://app.skhema.com/save",i=o(e),r=encodeURIComponent(window.location.href),c=Date.now();return`${s}?contributor_id=${n}&element_type=${t}&content_hash=${i}&${new URLSearchParams({source:r,t:c.toString(),utm_source:a.utmSource||"web_component",utm_medium:a.utmMedium||"embedded",utm_campaign:a.utmCampaign||t,utm_content:n}).toString()}`}const m="\n:host {\n /* Skhema Brand Colors - matching UI library */\n --skhema-primary: hsl(344 57% 54%); /* #cd476a */\n --skhema-primary-hover: hsl(344 50% 47%); /* #b53d5e */\n --skhema-primary-pressed: hsl(343 50% 41%); /* #9d3552 */\n --skhema-secondary: hsl(345 100% 75%); /* #ff82a2 */\n --skhema-gradient: linear-gradient(135deg, hsl(344 57% 54%) 0%, hsl(345 100% 75%) 100%);\n \n /* Light mode colors */\n --skhema-bg: hsl(0 0% 100%);\n --skhema-card: hsl(0 0% 100%);\n --skhema-border: hsl(214.3 31.8% 91.4%);\n --skhema-text: hsl(222.2 84% 4.9%);\n --skhema-text-muted: hsl(215.4 16.3% 46.9%);\n --skhema-accent: hsl(210 40% 96%);\n \n /* Shadows matching UI library */\n --skhema-shadow: 0 1px 3px 0 hsl(0 0 0 / 0.1), 0 1px 2px -1px hsl(0 0 0 / 0.1);\n --skhema-shadow-md: 0 4px 6px -1px hsl(0 0 0 / 0.1), 0 2px 4px -2px hsl(0 0 0 / 0.1);\n --skhema-shadow-lg: 0 10px 15px -3px hsl(0 0 0 / 0.1), 0 4px 6px -4px hsl(0 0 0 / 0.1);\n --skhema-radius: 0.375rem;\n \n display: block;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Inter', sans-serif;\n line-height: 1.5;\n color: var(--skhema-text);\n}\n\n/* Dark mode styles - applied via data-theme attribute */\n.skhema-insight-card[data-theme=\"dark\"],\n.skhema-skeleton[data-theme=\"dark\"] {\n --skhema-bg: hsl(222.2 84% 4.9%);\n --skhema-card: hsl(222.2 84% 4.9%);\n --skhema-border: hsl(217.2 32.6% 17.5%);\n --skhema-text: hsl(210 40% 98%);\n --skhema-text-muted: hsl(215 20.2% 65.1%);\n --skhema-accent: hsl(217.2 32.6% 17.5%);\n}\n\n/* Main component card - inspired by your design */\n.skhema-insight-card {\n position: relative;\n background: var(--skhema-card);\n border: 1px solid var(--skhema-border);\n border-radius: calc(var(--skhema-radius) * 2);\n padding: 16px;\n box-shadow: var(--skhema-shadow);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n max-width: 600px;\n margin: 8px 0;\n}\n\n.skhema-insight-card:hover {\n box-shadow: var(--skhema-shadow-lg);\n border-color: var(--skhema-primary);\n transform: translateY(-1px);\n}\n\n/* Header section with contributor info */\n.skhema-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 12px;\n gap: 12px;\n}\n\n.skhema-contributor {\n display: flex;\n align-items: center;\n gap: 8px;\n flex: 1;\n}\n\n.skhema-avatar {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: var(--skhema-gradient);\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n font-size: 14px;\n color: white;\n flex-shrink: 0;\n}\n\n.skhema-contributor-info {\n min-width: 0;\n flex: 1;\n}\n\n.skhema-contributor-name {\n font-weight: 500;\n font-size: 14px;\n color: var(--skhema-text);\n margin: 0;\n line-height: 1.2;\n}\n\n.skhema-contributor-role {\n font-size: 12px;\n color: var(--skhema-text-muted);\n margin: 0;\n line-height: 1.2;\n}\n\n/* Element type badge */\n.skhema-element-badge {\n display: inline-flex;\n align-items: center;\n padding: 4px 8px;\n background: var(--skhema-accent);\n border: 1px solid var(--skhema-border);\n border-radius: var(--skhema-radius);\n font-size: 11px;\n font-weight: 500;\n color: var(--skhema-text-muted);\n text-transform: capitalize;\n white-space: nowrap;\n flex-shrink: 0;\n}\n\n/* Content section */\n.skhema-content {\n margin: 12px 0 16px 0;\n padding: 0;\n}\n\n.skhema-content-text {\n font-size: 15px;\n line-height: 1.6;\n color: var(--skhema-text);\n margin: 0;\n font-style: italic;\n position: relative;\n word-wrap: break-word;\n overflow-wrap: break-word;\n hyphens: auto;\n max-width: 100%;\n}\n\n.skhema-content-text::before {\n content: '\"';\n color: var(--skhema-primary);\n font-size: 20px;\n font-weight: 600;\n margin-right: 4px;\n}\n\n.skhema-content-text::after {\n content: '\"';\n color: var(--skhema-primary);\n font-size: 20px;\n font-weight: 600;\n margin-left: 4px;\n}\n\n/* Footer section */\n.skhema-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--skhema-border);\n}\n\n.skhema-attribution {\n font-size: 11px;\n color: var(--skhema-text-muted);\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.skhema-attribution a {\n color: var(--skhema-primary);\n text-decoration: none;\n font-weight: 500;\n}\n\n.skhema-attribution a:hover {\n text-decoration: underline;\n}\n\n/* Save button with gradient and arrow */\n.skhema-save-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n background: var(--skhema-gradient);\n color: white;\n border: none;\n padding: 8px 16px;\n border-radius: var(--skhema-radius);\n font-size: 13px;\n font-weight: 500;\n text-decoration: none;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n box-shadow: var(--skhema-shadow);\n white-space: nowrap;\n}\n\n.skhema-save-btn:hover {\n transform: translateY(-1px);\n box-shadow: var(--skhema-shadow-md);\n}\n\n.skhema-save-btn:active {\n transform: translateY(0);\n}\n\n.skhema-save-btn:focus {\n outline: 2px solid var(--skhema-primary);\n outline-offset: 2px;\n}\n\n.skhema-save-btn::after {\n content: '→';\n transition: transform 0.2s ease;\n}\n\n.skhema-save-btn:hover::after {\n transform: translateX(2px);\n}\n\n/* Error state */\n.skhema-error {\n background: hsl(0 93% 94%);\n border: 1px solid hsl(0 84% 60%);\n color: hsl(0 74% 42%);\n padding: 12px;\n border-radius: var(--skhema-radius);\n font-size: 13px;\n}\n\n.skhema-error-title {\n font-weight: 600;\n margin-bottom: 8px;\n}\n\n.skhema-error-list {\n margin: 0;\n padding-left: 16px;\n}\n\n/* Skeleton loading state */\n.skhema-skeleton {\n background: var(--skhema-card);\n border: 1px solid var(--skhema-border);\n border-radius: calc(var(--skhema-radius) * 2);\n padding: 16px;\n box-shadow: var(--skhema-shadow);\n max-width: 600px;\n margin: 8px 0;\n animation: skeletonPulse 1.5s ease-in-out infinite;\n}\n\n.skhema-skeleton-header {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 12px;\n}\n\n.skhema-skeleton-avatar {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: linear-gradient(90deg,\n var(--skhema-border) 25%,\n var(--skhema-accent) 50%,\n var(--skhema-border) 75%);\n background-size: 200% 100%;\n animation: shimmer 1.5s infinite;\n}\n\n.skhema-skeleton-text {\n flex: 1;\n}\n\n.skhema-skeleton-line {\n height: 12px;\n background: linear-gradient(90deg,\n var(--skhema-border) 25%,\n var(--skhema-accent) 50%,\n var(--skhema-border) 75%);\n background-size: 200% 100%;\n animation: shimmer 1.5s infinite;\n border-radius: var(--skhema-radius);\n margin: 6px 0;\n}\n\n.skhema-skeleton-line.short {\n width: 40%;\n}\n\n.skhema-skeleton-line.medium {\n width: 70%;\n}\n\n.skhema-skeleton-content {\n margin: 16px 0;\n}\n\n@keyframes skeletonPulse {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.8;\n }\n}\n\n@keyframes shimmer {\n 0% {\n background-position: -200% 0;\n }\n 100% {\n background-position: 200% 0;\n }\n}\n\n/* Responsive design */\n@media (max-width: 640px) {\n .skhema-insight-card {\n margin: 4px 0;\n padding: 12px;\n }\n \n .skhema-header {\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n }\n \n .skhema-footer {\n flex-direction: column;\n align-items: stretch;\n gap: 8px;\n }\n \n .skhema-save-btn {\n justify-content: center;\n }\n}\n\n/* Accessibility */\n@media (prefers-reduced-motion: reduce) {\n .skhema-insight-card,\n .skhema-save-btn {\n transition: none;\n }\n \n .skhema-save-btn::after {\n transition: none;\n }\n \n .skhema-save-btn:hover::after {\n transform: none;\n }\n}\n\n.skhema-structured-data {\n display: none !important;\n}\n";class u extends HTMLElement{constructor(){super(),this.contentData=null,this.componentConnected=!1,this.hasTrackedLoad=!1,this.themeObserver=null,this.mediaQueryListener=null,this.shadow=this.attachShadow({mode:"closed"}),this.renderSkeleton()}static get observedAttributes(){return["element-type","contributor-id","content","source-url","theme","track-analytics"]}connectedCallback(){if(!this.componentConnected){this.componentConnected=!0;try{this.addPreconnectHints(),requestAnimationFrame(()=>{this.render(),this.trackLoad(),this.setupThemeListeners()})}catch(e){this.renderError("Failed to initialize component",e)}}}disconnectedCallback(){this.cleanupThemeListeners()}attributeChangedCallback(e,t,n){t!==n&&this.componentConnected&&this.render()}render(){const e=function(e){const t=[],n=e.getAttribute("element-type"),a=e.getAttribute("contributor-id");if(n){if(!h(n)){const e=Object.values(c).map(e=>e.value).join(", ");t.push(`Invalid element-type "${n}". Valid types: ${e}`)}}else t.push("Missing required attribute: element-type");return a?0===a.trim().length&&t.push("contributor-id cannot be empty"):t.push("Missing required attribute: contributor-id"),{isValid:0===t.length,errors:t,elementType:h(n||"")?n:void 0,contributorId:a||void 0}}(this);if(!e.isValid)return void this.renderError("Invalid component attributes",e.errors);const t=this.getContent();if(!t.trim())return void this.renderError("Component requires content",["Add content between the opening and closing tags, or use the content attribute"]);const n=function(e){const t=[];return/<script[\s>]/i.test(e)&&t.push("Script tags detected"),/on\w+\s*=/i.test(e)&&t.push("Event handlers detected"),/javascript:/i.test(e)&&t.push("JavaScript protocol detected"),/data:[^,]*script/i.test(e)&&t.push("Data URL with script detected"),/<iframe[\s>]/i.test(e)&&t.push("Iframe tags detected"),(/https?:\/\//i.test(e)||/www\./i.test(e))&&t.push("URLs detected in content"),{isSecure:0===t.length,issues:t}}(t);n.isSecure?(this.contentData={contributor_id:e.contributorId,element_type:e.elementType,content:t,content_hash:o(t),source_url:this.getAttribute("source-url")||window.location.href,timestamp:(new Date).toISOString(),page_title:document.title},this.renderContent(),this.addStructuredData()):this.renderError("Content security validation failed",n.issues)}getContent(){return this.getAttribute("content")||this.textContent||""}renderContent(){if(!this.contentData)return;const{element_type:e,contributor_id:t,content:n}=this.contentData,a=d(e),s=l(n,e,t),i=this.getAttribute("theme")||"auto",r=this.getActualTheme(i),o=this.formatContributorName(t),c=this.getInitials(o),h={role:"article","aria-label":`${d(e)} - Strategic insight`,"aria-describedby":"skhema-description"};Object.entries(h).forEach(([e,t])=>{this.setAttribute(e,t)}),this.shadow.innerHTML=`\n <style>${m}</style>\n\n <div class="skhema-insight-card" data-theme="${r}">\n <div class="skhema-header">\n <div class="skhema-contributor">\n <div class="skhema-avatar" title="${o}">\n ${c}\n </div>\n <div class="skhema-contributor-info">\n <div class="skhema-contributor-name">${o}</div>\n <div class="skhema-contributor-role">Strategy Insight</div>\n </div>\n </div>\n <div class="skhema-element-badge" title="${a}">\n ${a}\n </div>\n </div>\n \n <div class="skhema-content">\n <div class="skhema-content-text">${function(e){let t=function(e){let t=e;return[/https?:\/\/[^\s<>"{}|\\^`[\]]+/gi,/ftp:\/\/[^\s<>"{}|\\^`[\]]+/gi,/www\.[^\s<>"{}|\\^`[\]]+/gi,/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/gi,/(?:^|\s)([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(?:\/[^\s]*)?/gi].forEach(e=>{t=t.replace(e,"")}),t=t.replace(/\s+/g," ").trim(),t}(e);return t=(e=>{const t=document.createElement("div");return t.textContent=e,t.innerHTML})(t),t=t.replace(/\n/g,"<br>"),t=t.split(/(\s+)/).map(e=>/^\s+$/.test(e)||e.includes("<")?e:e.length>30?e.replace(/(.{10})/g,"$1​"):e).join(""),t}(n)}</div>\n </div>\n \n <div class="skhema-footer">\n <div class="skhema-attribution">\n Powered by <a href="https://skhema.com" target="_blank" rel="noopener noreferrer">Skhema</a>\n </div>\n <a href="${s}" \n class="skhema-save-btn" \n target="_blank"\n rel="noopener noreferrer"\n title="Save this insight to Skhema">\n Save to Skhema\n </a>\n </div>\n </div>\n `;const u=this.shadow.querySelector(".skhema-save-btn");u&&u.addEventListener("click",e=>{this.handleSaveClick(e)})}getActualTheme(e){if("light"===e||"dark"===e)return e;const t=document.documentElement,n=document.body,a=t.getAttribute("data-theme")||t.getAttribute("theme")||t.className.match(/theme-(\w+)/)?.[1],s=n.getAttribute("data-theme")||n.getAttribute("theme")||n.className.match(/theme-(\w+)/)?.[1];if(t.classList.contains("dark")||n.classList.contains("dark")||t.classList.contains("dark-mode")||n.classList.contains("dark-mode")||"dark"===a||"dark"===s)return"dark";const i=window.getComputedStyle(t).getPropertyValue("color-scheme");return i&&i.includes("dark")||window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}formatContributorName(e){return e.split(/[_-]/).map(e=>e.charAt(0).toUpperCase()+e.slice(1).toLowerCase()).join(" ")}getInitials(e){return e.split(" ").map(e=>e.charAt(0)).join("").toUpperCase().substring(0,2)}addPreconnectHints(){if(!document.querySelector('link[rel="preconnect"][href*="api.skhema.com"]'))try{const e=document.createElement("link");e.rel="preconnect",e.href="https://api.skhema.com",document.head.appendChild(e);const t=document.createElement("link");t.rel="dns-prefetch",t.href="https://skhema.com",document.head.appendChild(t)}catch(e){console.debug("Failed to add preconnect hints:",e)}}renderSkeleton(){const e=this.getAttribute("theme")||"auto",t=this.getActualTheme(e);this.shadow.innerHTML=`\n <style>${m}</style>\n\n <div class="skhema-skeleton" data-theme="${t}">\n <div class="skhema-skeleton-header">\n <div class="skhema-skeleton-avatar"></div>\n <div class="skhema-skeleton-text">\n <div class="skhema-skeleton-line medium"></div>\n <div class="skhema-skeleton-line short"></div>\n </div>\n </div>\n <div class="skhema-skeleton-content">\n <div class="skhema-skeleton-line"></div>\n <div class="skhema-skeleton-line"></div>\n <div class="skhema-skeleton-line medium"></div>\n </div>\n </div>\n `}renderError(e,t){const n=Array.isArray(t)?t:[String(t)];this.shadow.innerHTML=`\n <style>${m}</style>\n\n <div class="skhema-insight-card">\n <div class="skhema-error">\n <div class="skhema-error-title">Skhema Component Error: ${e}</div>\n <ul class="skhema-error-list">\n ${n.map(e=>`<li>${e}</li>`).join("")}\n </ul>\n </div>\n </div>\n `,this.dispatchEvent(new CustomEvent("skhema:error",{detail:{error:e,details:t},bubbles:!0}))}addStructuredData(){if(!this.contentData)return;const{content:e,element_type:t,contributor_id:n,source_url:a}=this.contentData,s=function(e,t,n,a){return{"@context":"https://schema.org","@type":"AnalysisContent",text:e,analysisType:t,category:d(t),contributor:n,url:l(e,t,n),provider:{"@type":"Organization",name:"Skhema",url:"https://skhema.com"},isPartOf:{"@type":"WebPage",url:a},dateCreated:(new Date).toISOString(),platform:"Skhema"}}(e,t,n,a),i=document.createElement("script");i.type="application/ld+json",i.textContent=JSON.stringify(s),i.className="skhema-structured-data",document.head.appendChild(i);const r=document.createElement("div");r.innerHTML=function(e,t,n){return`\n <div itemscope itemtype="https://schema.org/AnalysisContent" style="display:none;">\n <meta itemprop="analysisType" content="${t}">\n <meta itemprop="text" content="${e}">\n <meta itemprop="contributor" content="${n}">\n <meta itemprop="category" content="${d(t)}">\n <meta itemprop="platform" content="Skhema">\n </div>\n `}(e,t,n),r.className="skhema-structured-data",document.body.appendChild(r)}async trackLoad(){if(!r(this)||!this.contentData||this.hasTrackedLoad)return;this.hasTrackedLoad=!0;const e={contributorId:this.contentData.contributor_id,elementType:this.contentData.element_type,contentHash:this.contentData.content_hash,content:this.contentData.content,pageUrl:window.location.href,pageTitle:document.title,timestamp:Date.now(),userAgent:navigator.userAgent};await i(e),this.dispatchEvent(new CustomEvent("skhema:load",{detail:e,bubbles:!0}))}async handleSaveClick(e){this.contentData&&(r(this)&&await async function(e){try{a.addClick(e)}catch(t){console.debug("Click tracking failed:",t)}}(this.contentData),this.dispatchEvent(new CustomEvent("skhema:click",{detail:this.contentData,bubbles:!0})))}getContentData(){return this.contentData}refresh(){this.render()}setupThemeListeners(){const e=this.getAttribute("theme");if("auto"===e||!e){if(window.matchMedia){this.mediaQueryListener=window.matchMedia("(prefers-color-scheme: dark)");const e=()=>this.updateTheme();this.mediaQueryListener.addEventListener("change",e)}this.themeObserver=new MutationObserver(()=>this.updateTheme()),this.themeObserver.observe(document.documentElement,{attributes:!0,attributeFilter:["class","data-theme","theme"]}),this.themeObserver.observe(document.body,{attributes:!0,attributeFilter:["class","data-theme","theme"]})}}cleanupThemeListeners(){this.themeObserver&&(this.themeObserver.disconnect(),this.themeObserver=null),this.mediaQueryListener&&(this.mediaQueryListener=null)}updateTheme(){if("auto"===(this.getAttribute("theme")||"auto")){const e=this.shadow.querySelector(".skhema-insight-card");if(e){const t=this.getActualTheme("auto");e.setAttribute("data-theme",t)}}}}customElements.get("skhema-element")||customElements.define("skhema-element",u),"undefined"!=typeof window&&(window.SkhemaElement=u),e.SkhemaElement=u,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
2
2
  //# sourceMappingURL=web-component.min.js.map