/** * Event-type visual language (spec §21 taxonomy). Each type maps to a CSS token `--ev-` declared in globals.css * (both themes), a short label and a lucide icon name used by `EventTypeBadge`. Unknown types fall back to `other`. */ export type EventTypeKey = | 'product' | 'pricing' | 'hiring' | 'leadership' | 'location' | 'financing' | 'ma' | 'partnership' | 'strategy' | 'technology' | 'legal' | 'marketing' | 'developer' | 'security' | 'operations' | 'sustainability' | 'investor_relations' | 'communication' | 'other'; export interface EventStyle { key: EventTypeKey; label: string; /** CSS color expression, usable in `style={{ color }}`. */ color: string; soft: string; } const TYPE_TO_KEY: Record = { PRODUCT: 'product', PRICING: 'pricing', HIRING: 'hiring', LEADERSHIP: 'leadership', LOCATION: 'location', FINANCING: 'financing', 'M&A': 'ma', MA: 'ma', M_A: 'ma', MERGER: 'ma', ACQUISITION: 'ma', PARTNERSHIP: 'partnership', STRATEGY: 'strategy', TECHNOLOGY: 'technology', LEGAL: 'legal', MARKETING: 'marketing', DEVELOPER: 'developer', SECURITY: 'security', OPERATIONS: 'operations', SUSTAINABILITY: 'sustainability', INVESTOR_RELATIONS: 'investor_relations', COMMUNICATION: 'communication', NEWS: 'communication', OTHER: 'other', }; const LABELS: Record = { product: 'Product', pricing: 'Pricing', hiring: 'Hiring', leadership: 'Leadership', location: 'Location', financing: 'Financing', ma: 'M&A', partnership: 'Partnership', strategy: 'Strategy', technology: 'Technology', legal: 'Legal', marketing: 'Marketing', developer: 'Developer', security: 'Security', operations: 'Operations', sustainability: 'Sustainability', investor_relations: 'Investor relations', communication: 'Communication', other: 'Other', }; export function eventKey(type: string | null | undefined): EventTypeKey { if (!type) return 'other'; return TYPE_TO_KEY[type.toUpperCase()] ?? 'other'; } export function eventStyle(type: string | null | undefined): EventStyle { const key = eventKey(type); return { key, label: LABELS[key], color: `var(--ev-${key})`, soft: `color-mix(in srgb, var(--ev-${key}) 12%, transparent)` }; } /** Ordered list for filters and legends (the spec taxonomy order). */ export const EVENT_TYPES: string[] = [ 'PRODUCT', 'PRICING', 'HIRING', 'LEADERSHIP', 'LOCATION', 'FINANCING', 'M&A', 'PARTNERSHIP', 'STRATEGY', 'TECHNOLOGY', 'LEGAL', 'MARKETING', 'DEVELOPER', 'SECURITY', 'OPERATIONS', 'SUSTAINABILITY', 'INVESTOR_RELATIONS', 'COMMUNICATION', 'OTHER', ]; export function eventTypeLabel(type: string | null | undefined): string { return LABELS[eventKey(type)]; } /** Subtype → careful human label. Anything unknown is humanised, never invented. */ const SUBTYPE_LABELS: Record = { PRODUCT_LAUNCH: 'Product launch', PRODUCT_REMOVAL: 'Product no longer listed', PRODUCT_RENAME: 'Product renamed', PRICE_INCREASE: 'Price increase', PRICE_DECREASE: 'Price decrease', NEW_PRICING_TIER: 'New pricing tier', PRICING_CHANGE: 'Pricing change', JOB_COUNT_INCREASE: 'Job count increase', JOB_COUNT_DECREASE: 'Job count decrease', NEW_JOB: 'New listing', JOB_REMOVED: 'Listing no longer visible', NEW_EXECUTIVE: 'New executive listed', EXECUTIVE_REMOVED: 'Executive no longer listed', LEADERSHIP_CHANGE: 'Leadership page changed', NEW_OFFICE: 'New office listed', OFFICE_REMOVED: 'Office no longer listed', NEW_LOCATION: 'New location listed', COUNTRY_EXPANSION: 'Country expansion', NEW_PARTNERSHIP: 'New partnership', ACQUISITION: 'Acquisition', DIVESTITURE: 'Divestiture', API_LAUNCH: 'API launch', DOCUMENTATION_CHANGE: 'Documentation change', DOC_CHANGE: 'Documentation change', TERMS_CHANGE: 'Terms change', BRAND_REPOSITIONING: 'Brand repositioning', NEWS_RELEASE: 'News release', SECURITY_NOTICE: 'Security notice', }; export function subtypeLabel(sub: string | null | undefined): string { if (!sub) return ''; return SUBTYPE_LABELS[sub.toUpperCase()] ?? sub.replace(/[_-]+/g, ' ').toLowerCase().replace(/^\w/, (c) => c.toUpperCase()); }