1// Shared connector-icon resolver. Looks for any element with 2// <span class="connector-tile__icon" data-slug="..." data-name="..."></span> 3// <div class="cx-hero__icon" data-slug="..." data-name="..." data-size="56"></div> 4// and renders an icon using a tiered fallback chain: 5// 0. Local verified brand SVG (public/icons/brands/) â vendored, 6// version-controlled, manually verified. See LOCAL_ICONS below. 7// 1. glincker/thesvg (full-colour brand SVGs) 8// 2. svgl open-source SVG logos 9// 3. DuckDuckGo favicon by inferred domain 10// 4. Branded initials tile (slug-hash colour) 11// Tiers 1-4 are the original Lovable hand-over (runtime CDN guessing). 12// Tier 0 is the verified repo: an icon is only authoritative when its 13// slug is in LOCAL_ICONS, otherwise it keeps the CDNâinitials chain. 14 15(function () { 16 // Verified, vendored brand icons. MUST mirror the keys of BRAND_ICONS 17 // in scripts/brand-icons-data.mjs (that file is the source of truth + 18 // provenance/licence record). Kept inline here because this resolver 19 // is loaded as a plain <script defer> on every page (no module load). 20 // Root-absolute so it resolves identically from / and /connectors/. 21 const LOCAL_ICONS = { 22 asana: "asana.svg", 23 clickup: "clickup.svg", 24 coda: "coda.svg", 25 confluence: "confluence.svg", 26 gong: "gong.svg", 27 granola: "granola.svg", 28 "microsoft-copilot": "microsoft-copilot.svg", 29 notion: "notion.svg", 30 slack: "slack.svg", 31 dust: "dust.png", 32 lindy: "lindy.png", 33 alfred: "alfred.png", 34 fireflies: "fireflies.png", 35 fyxer: "fyxer.png", 36 guru: "guru.png", 37 littlebird: "littlebird.png", 38 monday: "monday.png", 39 motion: "motion.png", 40 rovo: "rovo.png", 41 glean: "glean.png", 42 fathom: "fathom.png", 43 }; 44 const CDN_SLUG_OVERRIDES = { 45 "microsoft-teams-meetings": "microsoft-teams", 46 "zoom-team-chat": "zoom", 47 "webex-messaging": "webex", 48 "rocket-chat": "rocketdotchat", 49 "whatsapp-business": "whatsapp", 50 "outlook": "microsoft-outlook", 51 "outlook-calendar": "microsoft-outlook", 52 "apple-calendar": "apple", 53 "icloud-drive": "icloud", 54 "onedrive": "microsoft-onedrive", 55 "sharepoint": "microsoft-sharepoint", 56 "dropbox-paper": "dropbox", 57 "excel-online": "microsoft-excel", 58 "microsoft-lists": "microsoft-todo", 59 "microsoft-planner": "microsoft-todo", 60 "zoho-crm": "zoho", 61 "microsoft-dynamics-365": "microsoft", 62 "apollo": "apollodotio", 63 "lucidchart": "lucid", 64 "adobe-creative-cloud": "adobe", 65 }; 66 const SVGL_SLUG_OVERRIDES = { 67 "microsoft-teams-meetings": "microsoft-teams", 68 "zoom-team-chat": "zoom", 69 "webex-messaging": "webex", 70 "outlook": "outlook", 71 "outlook-calendar": "outlook", 72 "apple-calendar": "apple-calendar", 73 "icloud-drive": "icloud", 74 "dropbox-paper": "dropbox", 75 "excel-online": "excel", 76 "microsoft-lists": "microsoft", 77 "microsoft-planner": "microsoft", 78 "microsoft-dynamics-365": "microsoft", 79 "read-ai": "readai", 80 "otter-ai": "otter", 81 "zoho-crm": "zoho", 82 "help-scout": "helpscout", 83 "adobe-creative-cloud": "adobe", 84 }; 85 const DOMAINS = { 86 slack: "slack.com", "microsoft-teams": "microsoft.com", 87 "microsoft-teams-meetings": "microsoft.com", discord: "discord.com", 88 "google-chat": "workspace.google.com", "zoom-team-chat": "zoom.us", 89 "webex-messaging": "webex.com", mattermost: "mattermost.com", 90 "rocket-chat": "rocket.chat", twist: "twist.com", telegram: "telegram.org", 91 "whatsapp-business": "business.whatsapp.com", front: "front.com", 92 intercom: "intercom.com", gmail: "gmail.com", outlook: "outlook.com", 93 "proton-mail": "proton.me", spike: "spikenow.com", 94 superhuman: "superhuman.com", hey: "hey.com", 95 "google-calendar": "calendar.google.com", "outlook-calendar": "outlook.com", 96 "apple-calendar": "apple.com", calendly: "calendly.com", "cal-com": "cal.com", 97 reclaim: "reclaim.ai", clockwise: "getclockwise.com", vimcal: "vimcal.com", 98 motion: "usemotion.com", savvycal: "savvycal.com", granola: "granola.ai", 99 fathom: "fathom.video", "otter-ai": "otter.ai", fireflies: "fireflies.ai", 100 "read-ai": "read.ai", tldv: "tldv.io", avoma: "avoma.com", gong: "gong.io", 101 chorus: "chorus.ai", krisp: "krisp.ai", grain: "grain.com", loom: "loom.com", 102 zoom: "zoom.us", "google-meet": "meet.google.com", webex: "webex.com", 103 whereby: "whereby.com", linear: "linear.app", jira: "atlassian.com", 104 asana: "asana.com", monday: "monday.com", clickup: "clickup.com", 105 trello: "trello.com", height: "height.app", shortcut: "shortcut.com", 106 basecamp: "basecamp.com", wrike: "wrike.com", 107 "microsoft-planner": "microsoft.com", smartsheet: "smartsheet.com",
108 notion: "notion.so", confluence: "atlassian.com", 109 "google-docs": "docs.google.com", coda: "coda.io", 110 "dropbox-paper": "dropbox.com", quip: "quip.com", slite: "slite.com", 111 slab: "slab.com", tettra: "tettra.com", almanac: "almanac.io", 112 "google-drive": "drive.google.com", dropbox: "dropbox.com", 113 onedrive: "onedrive.live.com", sharepoint: "microsoft.com", box: "box.com", 114 "icloud-drive": "icloud.com", pcloud: "pcloud.com", 115 salesforce: "salesforce.com", hubspot: "hubspot.com", 116 pipedrive: "pipedrive.com", attio: "attio.com", folk: "folk.app", 117 close: "close.com", "zoho-crm": "zoho.com", copper: "copper.com", 118 insightly: "insightly.com", "microsoft-dynamics-365": "microsoft.com", 119 outreach: "outreach.io", salesloft: "salesloft.com", apollo: "apollo.io", 120 clari: "clari.com", lemlist: "lemlist.com", zendesk: "zendesk.com", 121 freshdesk: "freshworks.com", servicenow: "servicenow.com", 122 kustomer: "kustomer.com", gorgias: "gorgias.com", 123 "help-scout": "helpscout.com", freshservice: "freshworks.com", 124 mailchimp: "mailchimp.com", activecampaign: "activecampaign.com", 125 klaviyo: "klaviyo.com", marketo: "marketo.com", pardot: "salesforce.com", 126 "google-analytics": "analytics.google.com", mixpanel: "mixpanel.com", 127 amplitude: "amplitude.com", github: "github.com", gitlab: "gitlab.com", 128 bitbucket: "bitbucket.org", sentry: "sentry.io", datadog: "datadoghq.com", 129 pagerduty: "pagerduty.com", vercel: "vercel.com", netlify: "netlify.com", 130 figma: "figma.com", miro: "miro.com", figjam: "figma.com", 131 lucidchart: "lucid.app", whimsical: "whimsical.com", 132 "adobe-creative-cloud": "adobe.com", lattice: "lattice.com", 133 "15five": "15five.com", leapsome: "leapsome.com", 134 bamboohr: "bamboohr.com", workday: "workday.com", 135 rippling: "rippling.com", gusto: "gusto.com", deel: "deel.com", 136 greenhouse: "greenhouse.io", lever: "lever.co", ashby: "ashbyhq.com", 137 workable: "workable.com", recruitee: "recruitee.com", 138 quickbooks: "quickbooks.intuit.com", xero: "xero.com", stripe: "stripe.com", 139 brex: "brex.com", ramp: "ramp.com", airtable: "airtable.com", 140 "google-sheets": "sheets.google.com", "excel-online": "microsoft.com", 141 "microsoft-lists": "microsoft.com", typeform: "typeform.com", 142 "google-forms": "forms.google.com", surveymonkey: "surveymonkey.com", 143 }; 144 const TILE_PALETTE = ["#1A2E22", "#1A1208", "#3F4A55", "#5B3A5B", "#8A6B2E", "#2E5F5A", "#A0522D", "#1F2D4A"]; 145 146 function initials(name) { 147 const p = name.replace(/[^a-zA-Z0-9 ]/g, "").trim().split(/\s+/); 148 return ((p[0]?.[0] || "") + (p[1]?.[0] || "")).toUpperCase(); 149 } 150 function brandColor(slug) { 151 let h = 0; 152 for (let i = 0; i < slug.length; i++) h = (h * 31 + slug.charCodeAt(i)) >>> 0; 153 return TILE_PALETTE[h % TILE_PALETTE.length]; 154 } 155 function iconSources(slug) { 156 const cdn = CDN_SLUG_OVERRIDES[slug] || slug; 157 const svgl = SVGL_SLUG_OVERRIDES[slug] || slug; 158 const domain = DOMAINS[slug]; 159 const s = []; 160 if (LOCAL_ICONS[slug]) { 161 s.push(`/public/icons/brands/${LOCAL_ICONS[slug]}`); 162 } 163 s.push( 164 `https://cdn.jsdelivr.net/gh/glincker/thesvg@main/public/icons/${cdn}/default.svg`, 165 `https://cdn.jsdelivr.net/gh/pheralb/svgl@main/static/library/${svgl}.svg`, 166 ); 167 if (domain) { 168 s.push(`https://icons.duckduckgo.com/ip3/${domain}.ico`); 169 s.push(`https://www.google.com/s2/favicons?domain=${domain}&sz=64`); 170 } 171 return s; 172 } 173 174 function render(host) { 175 const slug = host.dataset.slug; 176 const name = host.dataset.name || slug; 177 const size = parseInt(host.dataset.size || "32", 10); 178 if (!slug) return; 179 const sources = iconSources(slug); 180 let idx = 0; 181 const img = document.createElement("img"); 182 img.alt = ""; 183 img.setAttribute("aria-hidden", "true"); 184 img.loading = "lazy"; 185 img.decoding = "async"; 186 img.style.width = size + "px"; 187 img.style.height = size + "px"; 188 img.src = sources[0]; 189 img.addEventListener("error", () => { 190 idx++; 191 if (idx < sources.length) { 192 img.src = sources[idx]; 193 } else { 194 host.innerHTML = ""; 195 host.classList.add("connector-tile__icon--fallback"); 196 host.style.width = size + "px"; 197 host.style.height = size + "px"; 198 host.style.backgroundColor = brandColor(slug); 199 host.style.fontSize = Math.round(size * 0.36) + "px"; 200 host.textContent = initials(name); 201 } 202 }); 203 host.appendChild(img); 204 } 205
206 function init() { 207 document.querySelectorAll(".connector-tile__icon[data-slug], .cx-hero__icon[data-slug]").forEach(render); 208 } 209 if (document.readyState === "loading") { 210 document.addEventListener("DOMContentLoaded", init); 211 } else { 212 init(); 213 } 214})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.