1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8" /> 5 <title>Project Showcase</title> 6 7 <!-- Preconnect for fonts (loads faster and avoids flicker) --> 8 <link rel="preconnect" href="https://fonts.googleapis.com"> 9 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 10 <link rel="preconnect" href="https://use.typekit.net" crossorigin> 11 12 <!-- leaflet.js --> 13 <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY=" crossorigin="" /> 14
14<script src="https://unpkg.com/[email protected]/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
14 15
15<script src="https://cdn.jsdelivr.net/npm/@turf/turf@6/turf.min.js"></script>
15 16 17 <!-- Bootstrap --> 18 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.min.css"> 19 <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"> 20
20<script src="https://code.jquery.com/jquery-3.6.0.min.js" crossorigin="anonymous"></script>
20 21
21<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
21 22 23 <!-- Google Fonts (Raleway) with swap --> 24 <link href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,400;0,500;0,600;0,700;1,400;1,700&display=swap" rel="stylesheet"> 25 26 <!-- Your CSS --> 27 <link rel="stylesheet" href="/css/style.css"> 28 29 <!-- Fav Icon --> 30 <link rel="icon" type="image/x-icon" href="/imgs/favicon.ico"> 31 32 <!-- Preload Proxima Nova core weights for faster swap --> 33 <link rel="preload" as="font" type="font/woff2" crossorigin 34 href="https://use.typekit.net/af/d7ff92/00000000000000007735e609/31/l?subset_id=2&fvd=n4&v=3"> 35 <link rel="preload" as="font" type="font/woff2" crossorigin 36 href="https://use.typekit.net/af/e37e5a/00000000000000007735e60d/31/l?subset_id=2&fvd=n6&v=3"> 37 38 <!-- Typekit Fonts (Proxima Nova / Futura PT) --> 39 <style> 40 @font-face { 41 font-family: proxima-nova; 42 src: url(https://use.typekit.net/af/d7ff92/00000000000000007735e609/31/l?subset_id=2&fvd=n4&v=3) format("woff2"), 43 url(https://use.typekit.net/af/d7ff92/00000000000000007735e609/31/d?subset_id=2&fvd=n4&v=3) format("woff"); 44 font-weight: 400; 45 font-style: normal; 46 font-display: swap; 47 } 48 49 @font-face { 50 font-family: proxima-nova; 51 src: url(https://use.typekit.net/af/e37e5a/00000000000000007735e60d/31/l?subset_id=2&fvd=n6&v=3) format("woff2"), 52 url(https://use.typekit.net/af/e37e5a/00000000000000007735e60d/31/d?subset_id=2&fvd=n6&v=3) format("woff"); 53 font-weight: 600; 54 font-style: normal; 55 font-display: swap; 56 } 57 58 @font-face { 59 font-family: futura-pt; 60 src: url(https://use.typekit.net/af/26b618/000000000000000077586b59/31/l?subset_id=2&fvd=n4&v=3) format("woff2"), 61 url(https://use.typekit.net/af/26b618/000000000000000077586b59/31/d?subset_id=2&fvd=n4&v=3) format("woff"); 62 font-weight: 400; 63 font-style: normal; 64 font-display: swap; 65 } 66 67 body { 68 font-family: proxima-nova, "Helvetica Neue", Arial, sans-serif; 69 background-color: #fff; 70 } 71 </style> 72</head> 73<body class="bg-white" style="border: 20px solid #fafafa;"> 74 75 <!-- Shared Header --> 76 <!-- Start of global wrapper --> 77 78 <header style="background-color:#fafafa; "> 79 <div style=" max-width:1500px; margin-left:auto; margin-right:auto; padding-top: 24px; padding-bottom: 24px;"> 80 81 <nav class="navbar navbar-expand-lg" style="background-color:#fafafa; padding:0 0 0 0; z-index:1000;"> 82 <div class="container-fluid"> 83 84 <!-- Left: Logo + Brand --> 85 <a class="navbar-brand d-flex align-items-center fw-semibold" href="/"> 86 <img src="/imgs/left_logo.png" class="Header-branding-logo me-2" alt="Logo left"> 87 <img src="/imgs/right_logo.png" class="Header-branding-logo me-2" alt="Logo right"> 88 </a> 89 90 91 92 <!-- Right: Nav links --> 93 <div id="nav" class="collapse navbar-collapse me-5"> 94 <ul class="navbar-nav ms-auto align-items-lg-center"> 95 <li class="nav-item"><a class="nav-link Header-Nav-Item" href="/">ABOUT</a></li> 96 <li class="nav-item"><a class="nav-link Header-Nav-Item-Selected" href="/project-showcase">NYBPC</a></li> 97 <li class="nav-item"><a class="nav-link Header-Nav-Item" href="/students">STUDENTS</a></li> 98 <li class="nav-item"><a class="nav-link Header-Nav-Item" href="/judges">JUDGES</a></li> 99 <li class="nav-item"><a class="nav-link Header-Nav-Item" href="/supporters">SUPPORTERS</a></li> 100 <li class="nav-item"><a class="nav-link Header-Nav-Item" href="/news">NEWS</a></li> 101 <li id="signup" class="nav-item"><a class="nav-link Header-Nav-Item" href="/signup">SIGN UP</a></li> 102 <li class="nav-item dropdown" id="profileDropdown" style="display:none;"> 103 <div class="dropdown"> 104 <button class="btn dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> 105 PROFILE 106 </button> 107 108 <ul class="dropdown-menu" aria-labelledby="dropdownMenuButton"> 109 <li><a class="dropdown-item" id="profileLink" href="#">My Profile</a></li> 110 <li><a class="dropdown-item" id="logout" href="/logout">Logout</a></li> 111 112 </ul> 113 </div> 114 </li> 115 116 </ul> 117 </div> 118 119 </div> 120 </nav> 121 122 <!-- second navbar once user is signed in, grab quick-links --> 123 <nav id="quicklinksContainer" class="second-bar" style="display:none; border: none; z-index:1;"> 124 <ul id="quicklinks" style="border: none; padding: 0px; margin:0px;"> 125 </ul> 126 <ul class="navbar-nav"> 127 <li class="nav-item dropdown" id="editDropdown" style="display:none;"> 128 <button class="btn dropdown-toggle Header-Nav-Item" 129 style="color: white;" 130 type="button" 131 data-bs-toggle="dropdown" 132 aria-expanded="false"> 133 EDIT 134 </button> 135 <ul class="dropdown-menu" id="editDropdownMenu"></ul> 136 </li> 137 </ul> 138 </nav> 139 140 </div> 141 </header> 142
143<script> 144 145document.addEventListener("DOMContentLoaded", async () => { 146 const quicklinks = document.getElementById("quicklinks"); 147 bar = document.querySelector(".second-bar"); 148 try { 149 const res = await fetch("/users/quicklinks", { credentials: "include" }); 150 const data = await res.json(); 151 152 quicklinks.innerHTML = ""; 153 154 data.quickLinks.forEach(link => { 155 const li = document.createElement("li"); 156 li.style.listStyle = "none"; 157 158 const a = document.createElement("a"); 159 a.href = link.href; 160 a.textContent = link.text; 161 a.style.color = "white"; 162 a.style.textDecoration = "none"; 163 a.style.fontSize = "16px"; 164 a.style.display = "inline-block"; 165 a.style.whiteSpace = "nowrap"; 166 a.style.padding = "4px 10px"; 167 168 li.appendChild(a); 169 quicklinks.appendChild(li); 170 }); 171 quicklinks.style.display = "flex"; 172 quicklinks.style.flexWrap = "wrap"; 173 quicklinks.style.alignItems = "center"; 174 quicklinks.style.border = "none"; 175 quicklinks.style.padding = "6px 0"; 176 quicklinks.style.listStyle = "none"; 177 quicklinks.style.gap = "4px"; 178 179 document.getElementById("quicklinksContainer").style.display = "block" 180 } catch(err) { 181 console.error("Failed to load quick links", err); 182 } 183 184 fetch("/users/profile-link", { credentials: "include" }) 185 .then(res => res.json()) 186 .then(data => { 187 const profileLink = document.getElementById("profileLink"); 188 if (data.loggedIn) { 189 profileLink.href = data.profileHref; 190 document.getElementById("signup").style.display = "none"; 191 document.getElementById("profileDropdown").style.display = "block"; 192 } 193 }); 194 195}); 196</script>
196 197 198 199 200 <!-- Main Content --> 201 <main class="container" style="max-width: 1500px; width: auto; height: auto; padding: 0;"> 202 <div class="row "> 203 <div class="col-8"> 204 <style> 205 #map { 206 height: 700px; 207 width: 100%; 208 background: #eee; 209 } 210</style> 211 212<div id="map"></div> 213
214<script> 215 const map = L.map('map').setView([43.0, -75.0], 6); 216 217 L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 218 maxZoom: 18 219 }).addTo(map); 220 221 const regions = { 222 "North Country": ["Jefferson", "Lewis", "St Lawrence", "Franklin", "Hamilton", "Essex", "Clinton"], 223 "Mohawk Valley": ["Oneida", "Herkimer", "Fulton", "Montgomery", "Otsego", "Schoharie"], 224 "Western New York": ["Niagara", "Erie", "Allegany", "Cattaraugus", "Chautauqua"], 225 "Central New York": ["Oswego", "Cayuga", "Cortland", "Madison", "Onondaga"], 226 "New York City": ["Bronx", "Kings", "New York", "Richmond", "Queens"], 227 "Long Island": ["Nassau", "Suffolk"], 228 "Finger Lakes": ["Orleans", "Monroe", "Wayne", "Genesee", "Wyoming", "Livingston", "Ontario", "Yates", "Seneca"], 229 "Southern Tier": ["Steuben", "Schuyler", "Chemung", "Tompkins", "Tioga", "Broome", "Chenango", "Delaware"], 230 "Mid-Hudson": ["Putnam", "Westchester", "Rockland", "Sullivan", "Ulster", "Orange", "Dutchess"], 231 "Capital District": ["Warren", "Washington", "Saratoga", "Schenectady", "Rensselaer", "Albany", "Greene", "Columbia"] 232 }; 233 234 const regionColors = { 235 "Finger Lakes": "#2ca02c", 236 "Southern Tier": "#ff7f0e", 237 "Central New York": "#9467bd", 238 "Mohawk Valley": "#8c564b", 239 "Western New York": "#e377c2", 240 "North Country": "#94c765", 241 "Mid-Hudson": "#bcbd22", 242 "New York City": "#17becf", 243 "Long Island": "#d62728", 244 "Capital District": "#33ffbb" 245 }; 246 247 function onRegionClick(regionName) { 248 console.log("Clicked region:", regionName); 249 window.selectedRegion = regionName; 250 window.dispatchEvent(new CustomEvent("region-selected", { 251 detail: { region: regionName } 252 })); 253 } 254 255 const midHudsonBounds = [ 256 [40.95, -74], 257 [40.95, -73.5], 258 [42.15, -73.5], 259 [42.15, -74.6], 260 [41.85, -75.35], 261 [40.95, -74] 262 ]; 263 264 const midHudsonPolygon = L.polygon(midHudsonBounds, { 265 color: regionColors["Mid-Hudson"], 266 weight: 2, 267 fillColor: regionColors["Mid-Hudson"], 268 fillOpacity: 0.5 269 }).addTo(map); 270 271 midHudsonPolygon.bindTooltip("Mid-Hudson"); 272 midHudsonPolygon.on("click", () => { 273 onRegionClick("Mid-Hudson"); 274 }); 275 276 fetch('/Counties.json') 277 .then(res => res.json()) 278 .then(data => { 279 L.geoJSON(data, { 280 style: { 281 color: "#444", 282 weight: 1, 283 fillOpacity: 0.2 284 } 285 }).addTo(map); 286 287 //add region polygons 288 for (let regionName in regions) { 289 if (regionName === "Mid-Hudson") { 290 continue; 291 } 292 const countyNames = regions[regionName]; 293 294 //map regions 295 const countyFeatures = data.features.filter( 296 f => countyNames.includes(f.properties.NAME) 297 ); 298 299 if (countyFeatures.length === 0) { 300 console.warn(`No counties found for region: ${regionName}`); 301 continue; 302 } 303 304 try { 305 let merged = countyFeatures[0]; 306 307 for (let i = 1; i < countyFeatures.length; i++) { 308 merged = turf.union(merged, countyFeatures[i]); 309 } 310 311 L.geoJSON(merged, { 312 style: { 313 color: regionColors[regionName] || "#000", 314 weight: 2, 315 fillColor: regionColors[regionName] || "#000", 316 fillOpacity: 0.5 317 }, 318 onEachFeature: function(feature, layer) { 319 layer.bindTooltip(regionName); 320 321 layer.on("click", () => { 322 onRegionClick(regionName); 323 }); 324 } 325 }).addTo(map); 326 327 midHudsonPolygon.bringToFront(); 328 329 } catch (err) { 330 console.error(`Error creating region ${regionName}:`, err); 331 } 332 } 333 }) 334 .catch(err => { 335 console.error("Error loading Counties.json:", err); 336 }); 337</script>
337 338 </div> 339 340 <div class="col-4"> 341 <p>Click on a region to view more information.</p> 342 <label><strong>Select Year</strong></label> 343 <select id="year-select" class="form-select mb-3"> 344 <option value="">All Years</option> 345 </select> 346 347 <div class="btn-group mb-3" role="group"> 348 <button class="btn btn-primary showcase-btn" data-type="companies">Companies</button> 349 <button class="btn btn-outline-primary showcase-btn" data-type="students">Schools and Students</button> 350 <button class="btn btn-outline-primary showcase-btn" data-type="event">Event Info</button> 351 </div> 352 <div id="company-list"></div> 353 </div> 354</div> 355 356 </main> 357 358 <!-- Shared Footer --> 359 <footer class="py-3"> 360 <div class="Footer-Container mx-auto border-top" style="max-width:1500px; width:100%; padding:30px 0;"> 361 <div class="row g-0" style="text-align: center;"> 362 <div class="row g-0" style="max-width:1500px; margin:0 auto; padding:50px 0; align-items:flex-start;"> 363 364 <!-- Left Text Column --> 365 <div class="col-md-6 px-4"> 366 <h2 style="color:#002d70; font-weight:700; text-transform:uppercase;text-align: left;">SPONSOR TODAY!</h2> 367 <p style="font-family:Raleway; font-size:18px; color:#000; line-height:1.7em; text-align: left;"> 368 Please complete the form below and weâll be in touch to discuss sponsorship options. 369 </p> 370 <h4 style="color:#002d70; font-weight:700; text-transform:uppercase; margin-top:60px;text-align: left;"> 371 THANK YOU FOR YOUR SUPPORT! 372 </h4> 373 </div> 374 375 <!-- Right Form Column --> 376 <div class="col-md-6 px-4"> 377 <form class="row g-3"> 378 379 <!-- Name (split into two fields) --> 380 <div class="col-12"> 381<label class="form-label sponsor-label"> 382 Name <span>(required)</span> 383</label> <div class="row g-2"> 384 <div class="col-md-6"> 385 <input type="text" class="form-control sponsor-input" placeholder="First Name" required> 386 </div> 387 <div class="col-md-6"> 388 <input type="text" class="form-control sponsor-input" placeholder="Last Name" required> 389 </div> 390 </div> 391 </div> 392 393 <!-- Email --> 394 <div class="col-12"> 395<label class="form-label sponsor-label"> 396 Email <span>(required)</span> 397</label> 398 <input type="email" class="form-control sponsor-input" placeholder="Email" required> 399 </div> 400 401 <!-- Phone --> 402 <div class="col-12"> 403<label class="form-label sponsor-label"> 404 Phone <span>(required)</span> 405</label> <input type="tel" class="form-control sponsor-input" placeholder="Phone" required> 406 </div> 407 408 <!-- Submit Button --> 409 <div class="col-12"> 410 <button type="submit" class="btn sponsor-btn w-100">SUBMIT</button> 411 </div> 412 413 </form> 414 </div> 415 416</div> 417 418 </div> 419 <div class="row g-0"> 420 <div class="col-12 text-center" 421 style=" 422 font-family: Raleway, sans-serif; 423 font-weight: 700; 424 font-style: normal; 425 font-size: 22px; 426 letter-spacing: 0.05em; 427 line-height: 1.6em; 428 text-transform: uppercase; 429 color: #002d70; 430 padding: 25px 0; 431 "> 432 Need more time? <br class="d-md-none" /> 433 <span class="sponsorship-link"> 434 Download our Sponsorship Summary 435</span> 436 437 </div> 438</div> 439 440 <div class="row g-0"> 441 <div class="col-12" style="text-align: center;"><a href="/s/NYBPC_Sponsorship.pdf" 442 class="sponsor-summary-btn" 443 target="_blank"> 444 SPONSORSHIP SUMMARY 445</a> 446</div> 447 </div> 448 449 450
451<script> 452document.addEventListener("DOMContentLoaded", async () => { 453 try { 454 const res = await fetch("/events/years/state-released"); 455 const years = await res.json(); 456 457 const select = document.getElementById("year-select"); 458 459 select.innerHTML = '<option value="">All Years</option>'; 460 461 years.forEach((year, index) => { 462 const option = document.createElement("option"); 463 option.value = year; 464 option.textContent = year; 465 466 // default select most recent year 467 if (index === 0) { 468 option.selected = true; 469 } 470 471 select.appendChild(option); 472 }); 473 474 } catch (err) { 475 console.error("Error loading years:", err); 476 } 477}); 478</script>
478 479 480 481
482<script> 483$(document).ready(function () { 484 485 let selectedRegionEventId = null; 486 let selectedYear = $("#year-select").val(); 487 488 $("#year-select").on("change", function () { 489 selectedYear = $(this).val(); 490 491 if (!selectedYear) { 492 selectedRegionEventId = null; 493 setButtonsEnabled(false); 494 return; 495 } 496 497 setButtonsEnabled(true); 498 499 // If a region is already selected, reload companies automatically 500 if (selectedRegionName) { 501 $("#company-list").html(`<p>Reloading data for ${selectedRegionName} (${selectedYear})...</p>`); 502 loadEventIdForRegion(selectedRegionName); 503 } 504 }); 505 506 function setButtonsEnabled(enabled) { 507 $(".showcase-btn").prop("disabled", !enabled); 508 509 if (!enabled) { 510 $("#company-list").html("<p>Please select a year to view data.</p>"); 511 } 512 } 513 514 function highlightActiveButton(activeType) { 515 $(".showcase-btn").each(function () { 516 if ($(this).data("type") === activeType) { 517 $(this).removeClass("btn-outline-primary").addClass("btn-primary"); 518 } else { 519 $(this).removeClass("btn-primary").addClass("btn-outline-primary"); 520 } 521 }); 522 } 523 524 window.addEventListener("region-selected", async (e) => { 525 if (!selectedYear) { 526 $("#company-list").html("<p>Please select a year first.</p>"); 527 return; 528 } 529 const regionName = e.detail.region; 530 531 $("#company-list").html(`<p>Loading event for ${regionName}...</p>`); 532 533 try { 534 //get event id based on region and year 535 const res = await fetch(`/showcase/event/${selectedYear}/${regionName}`); 536 const data = await res.json(); 537 538 if (!data.event_id) { 539 $("#company-list").html(`<p>No event found for ${regionName}.</p>`); 540 selectedRegionEventId = null; 541 return; 542 } 543 544 selectedRegionEventId = data.event_id; 545 546 //select companies option first 547 highlightActiveButton("companies"); 548 loadCompanies(); 549 550 } catch (err) { 551 console.error(err); 552 $("#company-list").html("<p>Failed to load region data.</p>"); 553 } 554 }); 555 556 // Button handler 557 $(".showcase-btn").on("click", function () { 558 const type = $(this).data("type"); 559 560 highlightActiveButton(type); 561 562 if (!selectedRegionEventId) { 563 $("#company-list").html("<p>Select a region first.</p>"); 564 return; 565 } 566 567 if (type === "companies") loadCompanies(); 568 if (type === "students") loadStudents(); 569 if (type === "event") loadEventInfo(); 570 }); 571 572 async function loadCompanies() { 573 try { 574 const res = await fetch(`/showcase/companies/${selectedRegionEventId}`); 575 const data = await res.json(); 576 577 if (!data.companies || data.companies.length === 0) { 578 $("#company-list").html("<p>No companies participating.</p>"); 579 return; 580 } 581 582 let html = `<h4>Companies</h4><ul>`; 583 data.companies.forEach(team => { 584 html += `<li>${team.team_name}</li>`; 585 }); 586 html += `</ul>`; 587 588 $("#company-list").html(html); 589 590 } catch (err) { 591 console.error(err); 592 $("#company-list").html("<p>Error loading companies.</p>"); 593 } 594 } 595 596 async function loadStudents() { 597 console.log("Load:", selectedRegionEventId); 598 try { 599 const res = await fetch(`/showcase/school/${selectedRegionEventId}`); 600 const data = await res.json(); 601 602 $("#company-list").html(` 603 <h4>Schools & Students</h4> 604 <p><strong>Students:</strong> ${data.student}</p> 605 <p><strong>Schools:</strong> ${data.schools.length}</p> 606 <ul>${data.schools.map(s => `<li>${s}</li>`).join("")}</ul> 607 `); 608 609 } catch (err) { 610 console.error(err); 611 $("#company-list").html("<p>Error loading schools/students.</p>"); 612 } 613 } 614 615 async function loadEventInfo() { 616 try { 617 const res = await fetch(`/showcase/winners/${selectedRegionEventId}`); 618 const data = await res.json(); 619 620 if (!data.winners || data.winners.length === 0) { 621 $("#company-list").html("<p>No event information available.</p>"); 622 return; 623 } 624 625 // Group winners by track
626 const grouped = data.winners.reduce((acc, w) => { 627 acc[w.track] ??= []; 628 acc[w.track].push(w); 629 return acc; 630 }, {}); 631 632 let html = `<h4>Event Winners</h4>`; 633 634 Object.entries(grouped).forEach(([track, winners]) => { 635 html += `<h5 class="mt-3">${track}</h5><ul>`; 636 winners.forEach(w => { 637 const placeLabel = 638 w.place === 1 ? "1st Place" : 639 w.place === 2 ? "2nd Place" : 640 w.place === 3 ? "3rd Place" : 641 "Concept Stage Award"; 642 643 html += ` 644 <li> 645 <strong>${placeLabel}</strong><br> 646 ${w.name} <em>(${w.school})</em> 647 </li> 648 `; 649 }); 650 html += `</ul>`; 651 }); 652 653 $("#company-list").html(html); 654 655 } catch (err) { 656 console.error(err); 657 $("#company-list").html("<p>Error loading event info.</p>"); 658 } 659 } 660 if (!selectedYear) { 661 setButtonsEnabled(false); 662 } 663 664 665}); 666</script>
666 667 </footer> 668 669 <!--
669<script src="/js/app.js"></script>
669 --> 670</body> 671</html>
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.