PageSourceSearch

https://readywhen.ai/scripts/connector-icons.js

js readywhen.ai collected 2026-10-05 12:29:42 UTC 9,521 bytes, 214 lines download raw bytes

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.