1 2(function() { 3 'use strict'; 4 5 var ORIGIN = 'https://harlequinsheepsociety.net'; 6 var US_TOPO_URL = 'https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json'; 7 var CA_TOPO_URL = 'https://cdn.jsdelivr.net/npm/[email protected]/canada.json'; 8 var CURRENT_YEAR = String(new Date().getFullYear()); 9 10 var US_FIPS_TO_ABBR = { 11 "01":"AL","02":"AK","04":"AZ","05":"AR","06":"CA","08":"CO","09":"CT", 12 "10":"DE","11":"DC","12":"FL","13":"GA","15":"HI","16":"ID","17":"IL", 13 "18":"IN","19":"IA","20":"KS","21":"KY","22":"LA","23":"ME","24":"MD", 14 "25":"MA","26":"MI","27":"MN","28":"MS","29":"MO","30":"MT","31":"NE", 15 "32":"NV","33":"NH","34":"NJ","35":"NM","36":"NY","37":"NC","38":"ND", 16 "39":"OH","40":"OK","41":"OR","42":"PA","44":"RI","45":"SC","46":"SD", 17 "47":"TN","48":"TX","49":"UT","50":"VT","51":"VA","53":"WA","54":"WV", 18 "55":"WI","56":"WY" 19 }; 20 21 // Canada province abbreviations (ISO 3166-2:CA codes) 22 var CA_PROVINCES = ["AB","BC","MB","NB","NL","NS","NT","NU","ON","PE","QC","SK","YT"]; 23 24 var SAGE = '#6b7c5c'; 25 var SAGE_DARK = '#4a5940'; 26 var CREAM = '#f5f2eb'; 27 var BORDER = '#d4dbc8'; 28 var TEXT = '#2c3a1e'; 29 var TEXT_MID = '#4a5940'; 30 var TEXT_LIGHT = '#6b7c5c'; 31 var GOLD = '#b8860b'; 32 var WHITE = '#ffffff'; 33 var RAINBOW = 'linear-gradient(90deg,#e63946,#f4a261,#e9c46a,#2a9d8f,#457b9d,#6a4c93)'; 34 35 var CSS = [ 36 '#hssr-directory * { box-sizing: border-box; }', 37 '#hssr-directory { font-family: Georgia,"Times New Roman",serif; color:' + TEXT + '; background:' + CREAM + '; border-radius:12px; padding:0 0 24px 0; max-width:100%; }', 38 '.hssr-rainbow-bar { height:4px; background:' + RAINBOW + '; border-radius:12px 12px 0 0; }', 39 '.hssr-header { background:' + SAGE + '; border-radius:0; padding:20px 24px 16px; text-align:center; }', 40 '.hssr-header h2 { color:' + WHITE + '; font-size:1.5rem; margin:0 0 4px 0; font-weight:700; }', 41 '.hssr-header p { color:rgba(255,255,255,0.85); font-size:0.9rem; margin:0; }', 42 '.hssr-map-wrap { padding:12px 16px 4px; }', 43 '.hssr-map-wrap svg { width:100%; height:auto; display:block; cursor:default; }', 44 '.hssr-map-wrap svg path { transition:fill 0.15s; }', 45 '.hssr-map-tabs { display:flex; justify-content:center; gap:8px; padding:0 16px 8px; }', 46 '.hssr-map-tab { font-size:0.8rem; padding:4px 14px; border-radius:999px; border:1.5px solid ' + BORDER + '; background:' + WHITE + '; color:' + TEXT_MID + '; cursor:pointer; font-family:inherit; }', 47 '.hssr-map-tab.active { background:' + SAGE + '; color:' + WHITE + '; border-color:' + SAGE + '; }', 48 '.hssr-map-legend { display:flex; align-items:center; gap:8px; justify-content:center; padding:0 16px 8px; font-size:0.75rem; color:' + TEXT_LIGHT + '; flex-wrap:wrap; }', 49 '.hssr-legend-swatch { display:inline-block; width:12px; height:12px; border-radius:2px; }', 50 '.hssr-new-dot { display:inline-block; width:8px; height:8px; border-radius:50%; background:#16a34a; margin-right:4px; animation:hssr-pulse 1.5s infinite; }', 51 '@keyframes hssr-pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:0.5;transform:scale(1.4)} }', 52 '.hssr-search-wrap { padding:8px 24px 4px; }', 53 '.hssr-search { width:100%; max-width:420px; display:block; margin:0 auto; padding:9px 14px; border:1.5px solid ' + BORDER + '; border-radius:8px; font-size:0.9rem; background:' + WHITE + '; color:' + TEXT + '; outline:none; font-family:inherit; }', 54 '.hssr-search:focus { border-color:' + SAGE + '; }', 55 '.hssr-stats { text-align:center; font-size:0.8rem; color:' + TEXT_LIGHT + '; padding:4px 24px 10px; }', 56 '.hssr-state-section { padding:0 16px 8px; }', 57 '.hssr-state-header { display:flex; align-items:center; gap:10px; padding:8px 8px 4px; }', 58 '.hssr-state-name { font-size:1rem; font-weight:700; color:' + SAGE_DARK + '; flex:1; }', 59 '.hssr-state-count { background:' + SAGE + '; color:' + WHITE + '; border-radius:999px; font-size:0.72rem; font-weight:600; padding:1px 8px; }', 60 '.hssr-divider { height:2px; background:' + RAINBOW + '; border-radius:2px; margin:0 8px 8px; opacity:0.5; }', 61 '.hssr-cards { display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:10px; padding:0 8px; }', 62 '.hssr-card { background:' + WHITE + '; border:1.5px solid ' + BORDER + '; border-radius:10px; padding:12px 14px; transition:border-color 0.15s; }', 63 '.hssr-card:hover { border-color:' + SAGE + '; }', 64 '.hssr-card-farm { font-weight:700; font-size:0.95rem; color:' + SAGE_DARK + '; margin-bottom:2px; }', 65 '.hssr-card-name { font-size:0.82rem; color:' + TEXT_MID + '; margin-bottom:4px; }', 66 '.hssr-card-location { font-size:0.78rem; color:' + TEXT_LIGHT + '; margin-bottom:5px; }', 67 '.hssr-card-links { display:flex; flex-wrap:wrap; gap:5px; }', 68 '.hssr-card-link { font-size:0.75rem; color:' + SAGE + '; text-decoration:none; border:1px solid ' + BORDER + '; border-radius:4px; padding:2px 7px; }', 69 '.hssr-card-link:hover { background:' + CREAM + '; }', 70 '.hssr-lifetime-badge { display:inline-block; background:' + GOLD + '; color:' + WHITE + '; font-size:0.68rem; font-weight:700; border-radius:4px; padding:1px 6px; margin-bottom:3px; letter-spacing:0.03em; }', 71 '.hssr-new-badge { display:inline-block; background:#16a34a; color:' + WHITE + '; font-size:0.65rem; font-weight:700; border-radius:4px; padding:1px 6px; margin-bottom:3px; margin-left:4px; letter-spacing:0.03em; }', 72 '.hssr-footer { text-align:center; padding:14px 24px 4px; font-size:0.78rem; color:' + TEXT_LIGHT + '; }', 73 '.hssr-footer a { color:' + SAGE + '; text-decoration:none; font-weight:600; }', 74 '.hssr-footer a:hover { text-decoration:underline; }', 75 '.hssr-loading { text-align:center; padding:36px; color:' + TEXT_LIGHT + '; font-size:0.9rem; }', 76 '.hssr-error { text-align:center; padding:20px; color:#c0392b; font-size:0.88rem; }', 77 '@media (max-width:520px) { .hssr-cards { grid-template-columns:1fr; } .hssr-header h2 { font-size:1.2rem; } }' 78 ].join('\n'); 79 80 // ââ Helpers ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 81 82 function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); } 83 84 function isLifetime(m) { 85 var t = (m.memberType || '').toLowerCase(); 86 return t.indexOf('lifetime') !== -1; 87 } 88 89 function isNewMember(m) { 90 return m.membershipYear && String(m.membershipYear) === CURRENT_YEAR; 91 } 92 93 function formatName(m) { 94 var parts = []; 95 if (m.firstName || m.lastName) parts.push(((m.firstName||'') + ' ' + (m.lastName||'')).trim()); 96 if (m.member2First || m.member2Last) parts.push(((m.member2First||'') + ' ' + (m.member2Last||'')).trim()); 97 return parts.join(' & '); 98 } 99 100 function formatLocation(m) { 101 var parts = []; 102 if (m.city) parts.push(m.city); 103 if (m.state) parts.push(m.state); 104 if (m.country && m.country.toUpperCase() !== 'US' && m.country.toUpperCase() !== 'USA') parts.push(m.country); 105 return parts.join(', '); 106 } 107 108 function isCanadian(m) { 109 var c = (m.country || '').toUpperCase(); 110 return c === 'CA' || c === 'CAN' || c === 'CANADA' || CA_PROVINCES.indexOf(m.state || '') !== -1; 111 } 112 113 function regionLabel(m) { 114 return m.state || (isCanadian(m) ? 'Canada' : 'Unknown'); 115 } 116 117 function renderCard(m) { 118 var farm = m.farmName || formatName(m) || 'HSSR Member'; 119 var name = formatName(m); 120 var loc = formatLocation(m); 121 var lifetime = isLifetime(m); 122 var newMem = isNewMember(m); 123 var links = ''; 124 if (m.websiteUrl) { 125 var url = m.websiteUrl.startsWith('http') ? m.websiteUrl : 'https://' + m.websiteUrl; 126 links += '<a class="hssr-card-link" href="' + url + '" target="_blank" rel="noopener">Website</a>'; 127 } 128 if (m.email && (m.listingPreference||'').toLowerCase().indexOf('email') !== -1) { 129 links += '<a class="hssr-card-link" href="mailto:' + esc(m.email) + '">Email</a>'; 130 } 131 return '<div class="hssr-card">' + 132 '<div style="display:flex;align-items:center;gap:4px;flex-wrap:wrap;margin-bottom:2px;">' + 133 (lifetime ? '<span class="hssr-lifetime-badge">★ Lifetime Member</span>' : '') + 134 (newMem ? '<span class="hssr-new-badge">✦ New ' + CURRENT_YEAR + '</span>' : '') + 135 '</div>' + 136 '<div class="hssr-card-farm">' + esc(farm) + '</div>' + 137 (name ? '<div class="hssr-card-name">' + esc(name) + '</div>' : '') + 138 (loc ? '<div class="hssr-card-location">📍 ' + esc(loc) + '</div>' : '') + 139 (links ? '<div class="hssr-card-links">' + links + '</div>' : '') + 140 '</div>'; 141 } 142 143 function groupByState(members) { 144 var groups = {};
145 members.forEach(function(m) { 146 var r = regionLabel(m); 147 if (!groups[r]) groups[r] = []; 148 groups[r].push(m); 149 }); 150 Object.keys(groups).forEach(function(r) { 151 groups[r].sort(function(a, b) { 152 var aL = isLifetime(a) ? 0 : 1, bL = isLifetime(b) ? 0 : 1; 153 if (aL !== bL) return aL - bL; 154 var aF = (a.farmName || a.lastName || '').toLowerCase(); 155 var bF = (b.farmName || b.lastName || '').toLowerCase(); 156 return aF < bF ? -1 : aF > bF ? 1 : 0; 157 }); 158 }); 159 return groups; 160 } 161 162 function renderDirectory(members, query, filterState) { 163 var filtered = members; 164 if (filterState) { 165 filtered = filtered.filter(function(m) { return (m.state || '') === filterState; }); 166 } 167 if (query) { 168 var q = query.toLowerCase(); 169 filtered = filtered.filter(function(m) { 170 return (m.farmName||'').toLowerCase().indexOf(q) !== -1 || 171 (m.firstName||'').toLowerCase().indexOf(q) !== -1 || 172 (m.lastName||'').toLowerCase().indexOf(q) !== -1 || 173 (m.state||'').toLowerCase().indexOf(q) !== -1 || 174 (m.city||'').toLowerCase().indexOf(q) !== -1; 175 }); 176 } 177 var groups = groupByState(filtered); 178 var states = Object.keys(groups).sort(); 179 var usStates = states.filter(function(s) { return s.length === 2 && s === s.toUpperCase() && CA_PROVINCES.indexOf(s) === -1; }); 180 var caProvinces = states.filter(function(s) { return CA_PROVINCES.indexOf(s) !== -1; }); 181 var others = states.filter(function(s) { return usStates.indexOf(s) === -1 && caProvinces.indexOf(s) === -1; }); 182 var sorted = usStates.sort().concat(caProvinces.sort()).concat(others.sort()); 183 184 var newCount = filtered.filter(isNewMember).length; 185 var html = '<div class="hssr-stats">' + filtered.length + ' breeder' + (filtered.length !== 1 ? 's' : '') + 186 (newCount > 0 ? ' <span class="hssr-new-dot"></span>' + newCount + ' new in ' + CURRENT_YEAR : '') + 187 (filterState ? ' in ' + esc(filterState) + ' <button id="hssr-clear-filter" style="background:none;border:none;color:' + SAGE + ';cursor:pointer;font-size:0.78rem;text-decoration:underline;padding:0;font-family:inherit;">clear filter</button>' : '') + 188 '</div>'; 189 sorted.forEach(function(region) { 190 var rm = groups[region]; 191 var hasNew = rm.some(isNewMember); 192 html += '<div class="hssr-state-section">' + 193 '<div class="hssr-state-header">' + 194 (hasNew ? '<span class="hssr-new-dot" title="New member(s) in ' + CURRENT_YEAR + '"></span>' : '') + 195 '<span class="hssr-state-name">' + esc(region) + '</span>' + 196 '<span class="hssr-state-count">' + rm.length + '</span></div>' + 197 '<div class="hssr-divider"></div><div class="hssr-cards">'; 198 rm.forEach(function(m) { html += renderCard(m); }); 199 html += '</div></div>'; 200 }); 201 if (sorted.length === 0) { 202 html += '<div class="hssr-loading">No breeders found' + (filterState ? ' in ' + esc(filterState) : '') + '.</div>'; 203 } 204 return html; 205 } 206 207 // ââ Map rendering with d3-geo + TopoJSON ââââââââââââââââââââââââââââââââââââ 208 209 function loadScript(src, cb) { 210 var s = document.createElement('script'); 211 s.src = src; 212 s.onload = cb; 213 s.onerror = function() { cb(new Error('Failed to load ' + src)); }; 214 document.head.appendChild(s); 215 } 216 217 function fillColor(count, max) { 218 if (count === 0) return '#e5e7eb'; 219 var t = Math.min(count / Math.max(max, 1), 1); 220 var r = Math.round(187 + (21 - 187) * t); 221 var g = Math.round(247 + (128 - 247) * t); 222 var b = Math.round(208 + (61 - 208) * t); 223 return 'rgb(' + r + ',' + g + ',' + b + ')'; 224 } 225 226 function renderMap(svgEl, topoData, counts, newRegions, onRegionClick, viewBox, objectKey, idToAbbr) { 227 var d3geo = window.d3 || {}; 228 var topojson = window.topojson; 229 if (!d3geo.geoPath || !topojson) return; 230 231 var vb = viewBox || '0 0 960 600'; 232 svgEl.setAttribute('viewBox', vb); 233 234 var w = parseInt(vb.split(' ')[2]) || 960; 235 var h = parseInt(vb.split(' ')[3]) || 600; 236 237 var projection = d3geo.geoAlbersUsa 238 ? d3geo.geoAlbersUsa().scale(900).translate([480, 300]) 239 : d3geo.geoMercator().fitSize([w, h], topojson.feature(topoData, topoData.objects[objectKey])); 240 241 var pathGen = d3geo.geoPath().projection(projection); 242 var obj = topoData.objects[objectKey]; 243 if (!obj) return; 244 var collection = topojson.feature(topoData, obj); 245 var features = collection.features; 246 var max = Math.max.apply(null, Object.values(counts).concat([1])); 247 248 var svgNS = 'http://www.w3.org/2000/svg'; 249
250 features.forEach(function(f) { 251 var d = pathGen(f); 252 if (!d) return; 253 var abbr = idToAbbr(f) || ''; 254 var count = counts[abbr] || 0; 255 var hasNew = newRegions[abbr] || false; 256 257 var path = document.createElementNS(svgNS, 'path'); 258 path.setAttribute('d', d); 259 path.setAttribute('fill', fillColor(count, max)); 260 path.setAttribute('stroke', '#9ca3af'); 261 path.setAttribute('stroke-width', '0.6'); 262 if (count > 0) { 263 path.style.cursor = 'pointer'; 264 path.addEventListener('mouseenter', function() { 265 path.setAttribute('fill', '#f97316'); 266 path.setAttribute('stroke', '#ea580c'); 267 path.setAttribute('stroke-width', '1.5'); 268 }); 269 path.addEventListener('mouseleave', function() { 270 path.setAttribute('fill', fillColor(count, max)); 271 path.setAttribute('stroke', '#9ca3af'); 272 path.setAttribute('stroke-width', '0.6'); 273 }); 274 path.addEventListener('click', function() { onRegionClick(abbr); }); 275 var title = document.createElementNS(svgNS, 'title'); 276 title.textContent = abbr + ': ' + count + ' breeder' + (count !== 1 ? 's' : '') + (hasNew ? ' (' + CURRENT_YEAR + ' new member)' : '') + ' â click to filter'; 277 path.appendChild(title); 278 } 279 svgEl.appendChild(path); 280 281 // Pulsing dot for new members 282 if (hasNew && count > 0) { 283 var centroid = pathGen.centroid(f); 284 if (centroid && isFinite(centroid[0]) && isFinite(centroid[1])) { 285 var circle = document.createElementNS(svgNS, 'circle'); 286 circle.setAttribute('cx', centroid[0]); 287 circle.setAttribute('cy', centroid[1]); 288 circle.setAttribute('r', '5'); 289 circle.setAttribute('fill', '#16a34a'); 290 circle.setAttribute('opacity', '0.85'); 291 circle.setAttribute('pointer-events', 'none'); 292 // CSS animation via style attribute 293 circle.style.animation = 'hssr-pulse 1.5s infinite'; 294 svgEl.appendChild(circle); 295 } 296 } 297 298 // State abbreviation label 299 var centroid2 = pathGen.centroid(f); 300 if (abbr && centroid2 && isFinite(centroid2[0]) && isFinite(centroid2[1])) { 301 var text = document.createElementNS(svgNS, 'text'); 302 text.setAttribute('x', centroid2[0]); 303 text.setAttribute('y', centroid2[1]); 304 text.setAttribute('text-anchor', 'middle'); 305 text.setAttribute('dominant-baseline', 'central'); 306 text.setAttribute('font-size', '9'); 307 text.setAttribute('font-weight', '600'); 308 text.setAttribute('fill', count > 0 ? '#166534' : '#9ca3af'); 309 text.setAttribute('pointer-events', 'none'); 310 text.textContent = abbr; 311 svgEl.appendChild(text); 312 } 313 }); 314 } 315 316 // ââ Main init ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 317 318 function init() { 319 var container = document.getElementById('hssr-directory'); 320 if (!container) return; 321 322 // Inject styles 323 var style = document.createElement('style'); 324 style.textContent = CSS; 325 document.head.appendChild(style); 326 327 container.innerHTML = '<div class="hssr-rainbow-bar"></div>' + 328 '<div class="hssr-header"><h2>Find a Harlequin Sheep Breeder</h2>' + 329 '<p>Active HSSR members across the United States and Canada</p></div>' + 330 '<div class="hssr-loading">Loading member directory…</div>'; 331 332 var apiUrl = ORIGIN + '/api/trpc/members.directory?batch=1&input=%7B%220%22%3A%7B%22json%22%3Anull%7D%7D'; 333 334 var membersData = null, usTopoData = null, caTopoData = null; 335 var query = '', filterState = '', activeTab = 'us'; // 'us' | 'ca' 336 337 function buildCounts(tab) { 338 var counts = {}, newRegions = {}; 339 if (!membersData) return { counts: counts, newRegions: newRegions }; 340 membersData.forEach(function(m) { 341 var s = m.state || ''; 342 if (!s) return; 343 var isCa = isCanadian(m); 344 if (tab === 'us' && isCa) return; 345 if (tab === 'ca' && !isCa) return; 346 counts[s] = (counts[s] || 0) + 1; 347 if (isNewMember(m)) newRegions[s] = true; 348 }); 349 return { counts: counts, newRegions: newRegions }; 350 } 351 352 function tryRender() { 353 if (!membersData) return; 354 renderFull(); 355 } 356 357 function renderFull() { 358 var members = membersData; 359 var usMembers = members.filter(function(m) { return !isCanadian(m); }); 360 var caMembers = members.filter(isCanadian); 361 var hasCanada = caMembers.length > 0; 362 363 var tabsHtml = ''; 364 if (hasCanada) { 365 tabsHtml = '<div class="hssr-map-tabs">' + 366 '<button class="hssr-map-tab' + (activeTab === 'us' ? ' active' : '') + '" id="hssr-tab-us">United States</button>' + 367 '<button class="hssr-map-tab' + (activeTab === 'ca' ? ' active' : '') + '" id="hssr-tab-ca">Canada</button>' + 368 '</div>'; 369 } 370 371 var mapHtml = '<div class="hssr-map-wrap"><svg id="hssr-map-svg" style="max-height:320px"></svg></div>' + 372 tabsHtml + 373 '<div class="hssr-map-legend">' + 374 '<span class="hssr-legend-swatch" style="background:#e5e7eb"></span> No breeders ' + 375 '<span class="hssr-legend-swatch" style="background:rgb(187,247,208)"></span> 1â2 ' + 376 '<span class="hssr-legend-swatch" style="background:rgb(104,188,135)"></span> 3â5 ' + 377 '<span class="hssr-legend-swatch" style="background:rgb(21,128,61)"></span> 6+ ' + 378 '<span class="hssr-new-dot"></span> New in ' + CURRENT_YEAR + ' ' + 379 '<span style="font-size:0.72rem">Click a state to filter</span></div>'; 380 381 // Filter directory to current tab 382 var tabMembers = activeTab === 'ca' ? caMembers : usMembers; 383 384 container.innerHTML = '<div class="hssr-rainbow-bar"></div>' + 385 '<div class="hssr-header"><h2>Find a Harlequin Sheep Breeder</h2>' + 386 '<p>Active HSSR members across the United States and Canada</p></div>' +
387 mapHtml + 388 '<div class="hssr-search-wrap"><input id="hssr-search-input" class="hssr-search" type="text" placeholder="Search by farm, name, or state…" value="' + esc(query) + '"></div>' + 389 '<div id="hssr-dir-body"></div>' + 390 '<div class="hssr-footer"><a href="' + ORIGIN + '/directory" target="_blank">View full directory on HSSR website →</a></div>'; 391 392 // Tab click handlers 393 var tabUs = document.getElementById('hssr-tab-us'); 394 var tabCa = document.getElementById('hssr-tab-ca'); 395 if (tabUs) tabUs.addEventListener('click', function() { activeTab = 'us'; filterState = ''; renderFull(); }); 396 if (tabCa) tabCa.addEventListener('click', function() { activeTab = 'ca'; filterState = ''; renderFull(); }); 397 398 // Render map 399 var svgEl = document.getElementById('hssr-map-svg'); 400 var built = buildCounts(activeTab); 401 if (svgEl && window.topojson && window.d3) { 402 if (activeTab === 'us' && usTopoData) { 403 renderMap(svgEl, usTopoData, built.counts, built.newRegions, function(abbr) { 404 filterState = (filterState === abbr) ? '' : abbr; 405 updateDir(); 406 }, '0 0 960 600', 'states', function(f) { 407 var fips = String(f.id || '').padStart(2, '0'); 408 return US_FIPS_TO_ABBR[fips] || ''; 409 }); 410 } else if (activeTab === 'ca' && caTopoData) { 411 // Try common TopoJSON object key names for Canada 412 var caKey = caTopoData.objects ? Object.keys(caTopoData.objects)[0] : null; 413 if (caKey) { 414 renderMap(svgEl, caTopoData, built.counts, built.newRegions, function(abbr) { 415 filterState = (filterState === abbr) ? '' : abbr; 416 updateDir(); 417 }, '0 0 1000 800', caKey, function(f) { 418 // Try common property names for province abbreviation 419 var props = f.properties || {}; 420 return props.PRUID && CA_PROVINCES[parseInt(props.PRUID) - 10] 421 ? CA_PROVINCES[parseInt(props.PRUID) - 10] 422 : props.PREABBR || props.abbr || props.code || props.PROV || ''; 423 }); 424 } 425 } 426 } 427 428 // Render directory body 429 function updateDir() { 430 var body = document.getElementById('hssr-dir-body'); 431 if (body) body.innerHTML = renderDirectory(tabMembers, query, filterState); 432 var clearBtn = document.getElementById('hssr-clear-filter'); 433 if (clearBtn) clearBtn.addEventListener('click', function() { filterState = ''; updateDir(); }); 434 } 435 updateDir(); 436 437 // Search input 438 var input = document.getElementById('hssr-search-input'); 439 if (input) { 440 input.addEventListener('input', function() { query = this.value; updateDir(); }); 441 } 442 } 443 444 // Fetch member data 445 fetch(apiUrl) 446 .then(function(r) { return r.json(); }) 447 .then(function(data) { 448 membersData = (data[0] && data[0].result && data[0].result.data && data[0].result.data.json) || []; 449 tryRender(); 450 }) 451 .catch(function() { 452 container.innerHTML = '<div class="hssr-rainbow-bar"></div>' + 453 '<div class="hssr-header"><h2>HSSR Member Directory</h2></div>' + 454 '<div class="hssr-error">Unable to load the member directory. Please visit <a href="' + ORIGIN + '/directory">harlequinsheepsociety.net/directory</a> directly.</div>'; 455 }); 456 457 // Load d3-geo and topojson from CDN, then fetch both TopoJSON files 458 loadScript('https://cdn.jsdelivr.net/npm/d3-geo@3/dist/d3-geo.umd.min.js', function(err) { 459 if (err) return; 460 loadScript('https://cdn.jsdelivr.net/npm/topojson-client@3/dist/topojson-client.min.js', function(err2) { 461 if (err2) return; 462 // Fetch US topo 463 fetch(US_TOPO_URL) 464 .then(function(r) { return r.json(); }) 465 .then(function(topo) { 466 usTopoData = topo; 467 if (membersData) renderFull(); 468 }) 469 .catch(function() { /* map won't show */ }); 470 // Fetch Canada topo 471 fetch(CA_TOPO_URL) 472 .then(function(r) { return r.json(); }) 473 .then(function(topo) { 474 caTopoData = topo; 475 if (membersData && activeTab === 'ca') renderFull(); 476 }) 477 .catch(function() { /* Canada map won't show, list still works */ }); 478 }); 479 }); 480 } 481 482 if (document.readyState === 'loading') { 483 document.addEventListener('DOMContentLoaded', init); 484 } else { 485 init(); 486 } 487})();
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.