diff --git a/assets/mckinley.jpg b/assets/mckinley.jpg new file mode 100644 index 0000000..bab4d3b Binary files /dev/null and b/assets/mckinley.jpg differ diff --git a/assets/peter.jpg b/assets/peter.jpg new file mode 100644 index 0000000..08f3f6d Binary files /dev/null and b/assets/peter.jpg differ diff --git a/index.html b/index.html index 26e4286..bd33b33 100644 --- a/index.html +++ b/index.html @@ -206,8 +206,10 @@ font-size: 3.4rem; font-weight: 800; margin-bottom: 22px; + overflow: hidden; box-shadow: 0 4px 16px rgba(27, 58, 92, 0.18); } + .avatar-circle img { width: 100%; height: 100%; object-fit: cover; } .team-name { font-size: 1.25rem; font-weight: 800; color: #1a2533; } .team-title { font-size: 0.92rem; color: #6b7a8d; margin-top: 6px; } @@ -284,6 +286,7 @@ font-size: 0.95rem; line-height: 1.65; color: #3d4c5c; max-width: 60ch; margin: 0 0 24px; } + .drill-view[data-view="conferences"] .narrative { max-width: none; } .drill-actions { display: flex; align-items: center; gap: 12px; } .drill-next, .drill-back { font-family: inherit; font-size: 0.85rem; font-weight: 700; cursor: pointer; @@ -303,6 +306,208 @@ } .drill-view .open-link:hover { background: #24824a; } + /* ---------- Conferences ---------- */ + .conf-outreach { display: flex; gap: 16px; align-items: flex-start; } + .conf-event-group { + width: 280px; flex-shrink: 0; border: 1px solid #e0e7ef; border-radius: 10px; + background: #fafbfc; padding: 16px; + } + .conf-list { + display: flex; flex-direction: column; gap: 8px; + max-height: 64vh; overflow-y: auto; padding-right: 4px; + } + .conf-card { + background: #fff; border: 1px solid #e0e7ef; border-radius: 8px; + padding: 12px 14px; cursor: pointer; transition: border-color 0.15s; + } + .conf-card:hover { border-color: #2d6a9f; } + .conf-card.selected { border-color: #2d6a9f; box-shadow: 0 0 0 2px rgba(45,106,159,0.15); } + .conf-parent-header { margin: 0 0 14px; padding-bottom: 14px; border-bottom: 1px solid #e0e7ef; } + .conf-parent-header .name { font-weight: 800; font-size: 1.05rem; color: #1b3a5c; } + .conf-parent-header .dates { font-size: 0.82rem; color: #6b7a8d; margin-top: 2px; } + .conf-parent-header .conf-plain-link { display: inline-block; margin-top: 8px; font-size: 0.8rem; margin-right: 14px; } + .conf-parent-header .conf-itinerary-toggle { + display: inline-block; margin-top: 8px; font-size: 0.8rem; font-weight: 700; + color: #1a5fb4; text-decoration: underline; background: none; border: none; + cursor: pointer; font-family: inherit; padding: 0; + } + .conf-parent-header .conf-itinerary-toggle.active { color: #1b3a5c; } + .conf-list-caption { font-size: 0.74rem; color: #9aa7b4; font-style: italic; margin: -4px 0 10px; } + .conf-card-anchor { font-weight: 800; font-size: 0.86rem; color: #1a2533; line-height: 1.35; } + .conf-itin-fixed { font-size: 0.86rem; color: #1b3a5c; font-weight: 700; margin-top: 6px; } + .conf-card-title { font-size: 0.78rem; color: #6b7a8d; margin-top: 4px; line-height: 1.4; } + .conf-itin-row { border: 1px solid #e0e7ef; border-radius: 10px; margin-top: 14px; overflow: hidden; } + .conf-itin-row-head { display: flex; align-items: center; gap: 18px; padding: 18px 20px; cursor: pointer; background: #fff; transition: background 0.15s; } + .conf-itin-row-head:hover { background: #f7f9fc; } + .conf-itin-row.expanded .conf-itin-row-head { background: #f3f8ff; border-bottom: 1px solid #e0e7ef; } + .conf-itin-cal-icon { + width: 64px; flex-shrink: 0; border-radius: 12px; overflow: hidden; + border: 1px solid #d9e6fb; box-shadow: 0 2px 6px rgba(27,58,92,0.1); background: #fff; + } + .conf-itin-cal-month { + background: linear-gradient(90deg, #1b3a5c 0%, #2d6a9f 100%); color: #fff; + font-size: 0.68rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em; + text-align: center; padding: 3px 0; + } + .conf-itin-cal-day { font-size: 1.7rem; font-weight: 800; color: #1b3a5c; text-align: center; padding: 4px 0 6px; } + .conf-itin-cal-icon.orange { border-color: #f3ddb8; box-shadow: 0 2px 6px rgba(180,110,30,0.12); } + .conf-itin-cal-icon.orange .conf-itin-cal-month { background: linear-gradient(90deg, #e8a24d 0%, #f0bd7a 100%); } + .conf-itin-cal-icon.orange .conf-itin-cal-day { color: #a3651f; } + .conf-itin-when-col { min-width: 0; width: 170px; flex-shrink: 0; } + .conf-itin-what-col { min-width: 0; flex: 1; border-left: 1px solid #e0e7ef; padding-left: 18px; } + .conf-itin-row-line { font-size: 0.86rem; color: #3d4a5c; line-height: 1.5; } + .conf-itin-row-name { font-weight: 800; color: #1b3a5c; margin-top: 0; } + .conf-itin-row-body { padding: 18px 20px; display: none; } + .conf-itin-row.expanded .conf-itin-row-body { display: block; } + .conf-itin-summary { font-size: 0.85rem; color: #3d4a5c; line-height: 1.55; margin-bottom: 14px; } + .conf-itin-summary-fixed { + display: flex; align-items: center; gap: 14px; + color: #1b3a5c; font-weight: 700; background: #eef4fd; border: 1px solid #d9e6fb; + border-radius: 10px; padding: 10px 16px 10px 10px; + } + .conf-itin-clock-icon { + width: 64px; height: 64px; flex-shrink: 0; border-radius: 12px; + background: #fff; border: 1px solid #d9e6fb; + box-shadow: 0 2px 6px rgba(27,58,92,0.1); + display: flex; align-items: center; justify-content: center; + } + .conf-itin-clock-icon svg { width: 30px; height: 30px; color: #2d6a9f; } + .conf-itin-summary-fixed-text { flex: 1; } + .conf-itin-commitments { display: flex; flex-direction: column; gap: 10px; margin-bottom: 4px; } + .conf-itin-commitment { + display: flex; align-items: center; gap: 18px; border: 1px solid #e0e7ef; + border-radius: 10px; padding: 14px 18px; background: #fff; + } + .conf-itin-banner { + background: linear-gradient(90deg, #1b3a5c 0%, #2d6a9f 100%); color: #fff; + font-size: 0.88rem; font-weight: 700; border-radius: 8px; padding: 12px 18px; + margin: 18px 0 4px; + } + .conf-detail { flex: 1; min-width: 0; } + .conf-detail-empty { + color: #9aa7b4; font-style: italic; padding: 60px 24px; text-align: center; + border: 1px dashed #e0e7ef; border-radius: 8px; + } + .conf-detail-meta { font-size: 0.85rem; color: #6b7a8d; margin-bottom: 14px; margin-top: 4px; } + .conf-detail-meta a { color: #1b3a5c; font-weight: 600; text-decoration: none; } + .conf-detail-meta a:hover { text-decoration: underline; } + .conf-abstract { font-size: 0.92rem; color: #3d4a5c; line-height: 1.6; margin-bottom: 4px; } + .conf-highlight { + background: #e8f0fe; border: 1px solid #cfe0fb; border-radius: 8px; + padding: 12px 16px; margin: 16px 0 4px; font-size: 0.9rem; color: #1b3a5c; + } + .conf-section-title { + font-size: 0.92rem; font-weight: 800; color: #1b3a5c; + margin: 22px 0 10px; display: flex; align-items: center; gap: 10px; + } + .conf-source-tag { + font-size: 0.72rem; font-weight: 700; color: #6b7a8d; text-transform: uppercase; + letter-spacing: 0.03em; background: #f7f9fc; border: 1px solid #e0e7ef; + border-radius: 20px; padding: 3px 10px; + } + .conf-filter-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 16px 0 6px; } + .conf-filter-row .ab-search { flex: 1; min-width: 200px; } + .conf-pres-toolbar { margin: 4px 0 14px; } + .conf-pres-split { display: flex; gap: 14px; align-items: flex-start; } + .conf-pres-list { + width: 280px; flex-shrink: 0; display: flex; flex-direction: column; gap: 6px; + max-height: 60vh; overflow-y: auto; padding-right: 4px; + } + .conf-pres-item { + border: 1px solid #e0e7ef; border-radius: 6px; padding: 10px 12px; cursor: pointer; + background: #fff; transition: border-color 0.15s; + } + .conf-pres-item:hover { border-color: #2d6a9f; } + .conf-pres-item.selected { border-color: #2d6a9f; background: #f3f8ff; box-shadow: 0 0 0 2px rgba(45,106,159,0.12); } + .conf-pres-item-name-row { display: flex; align-items: center; gap: 6px; } + .conf-pres-item-name { font-size: 0.92rem; font-weight: 800; color: #1a2533; line-height: 1.3; } + .conf-pres-item-sessions { + font-size: 0.74rem; color: #6b7a8d; margin-top: 4px; + white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + } + .conf-value-tags { display: flex; gap: 4px; flex-shrink: 0; } + .conf-value-tag-badge { + width: 18px; height: 18px; border-radius: 50%; flex-shrink: 0; + background: #eef4fd; border: 1px solid #d9e6fb; color: #2d6a9f; + display: flex; align-items: center; justify-content: center; + } + .conf-value-tag-badge svg { width: 11px; height: 11px; } + .conf-value-tags-detail { display: flex; gap: 14px; margin: 10px 0 6px; } + .conf-value-tag { display: flex; flex-direction: column; align-items: center; gap: 5px; width: 56px; } + .conf-value-tag-icon { + width: 40px; height: 40px; border-radius: 12px; background: #fff; + border: 1px solid #d9e6fb; box-shadow: 0 2px 6px rgba(27,58,92,0.1); color: #2d6a9f; + display: flex; align-items: center; justify-content: center; + } + .conf-value-tag-icon svg { width: 22px; height: 22px; } + .conf-value-tag-label { + font-size: 0.6rem; font-weight: 800; text-transform: uppercase; + letter-spacing: 0.03em; color: #1a2533; text-align: center; + } + .conf-itin-value-row { display: flex; gap: 20px; margin: 20px 0; flex-wrap: wrap; justify-content: center; } + .conf-itin-value-btn { + display: flex; flex-direction: column; align-items: center; gap: 7px; + background: none; border: none; font-family: inherit; padding: 0; cursor: pointer; + } + .conf-itin-value-icon { + width: 60px; height: 60px; border-radius: 16px; background: #fff; + border: 1px solid #d9e6fb; box-shadow: 0 2px 6px rgba(27,58,92,0.1); color: #2d6a9f; + display: flex; align-items: center; justify-content: center; + transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.1s; + } + .conf-itin-value-icon svg { width: 32px; height: 32px; } + .conf-itin-value-btn:hover .conf-itin-value-icon { transform: translateY(-2px); } + .conf-itin-value-btn.active .conf-itin-value-icon { + background: linear-gradient(135deg, #1b3a5c 0%, #2d6a9f 100%); border-color: #1b3a5c; color: #fff; + } + .conf-itin-value-label { + font-size: 0.72rem; font-weight: 800; text-transform: uppercase; + letter-spacing: 0.03em; color: #1a2533; + } + .conf-itin-value-count { font-size: 0.68rem; color: #6b7a8d; } + .conf-pres-detail { flex: 1; min-width: 0; } + .conf-pres-links { display: flex; gap: 18px; margin: 4px 0 4px; } + .conf-plain-link { color: #1a5fb4; text-decoration: underline; font-size: 0.85rem; font-weight: 600; } + .conf-plain-link:hover { color: #123f7a; } + .conf-pres-abstract { font-size: 0.88rem; color: #3d4c5c; line-height: 1.55; white-space: pre-line; } + .conf-pres-notes { + font-size: 0.78rem; color: #8a6416; background: #fff8ec; border: 1px solid #f0dfb8; + border-radius: 6px; padding: 8px 12px; margin-top: 12px; + } + .conf-outreach-log-scroll { max-height: 200px; overflow-y: auto; } + .conf-toggle-section { margin-top: 14px; padding-top: 14px; border-top: 1px solid #eaeff5; } + .conf-toggle-btn { + font-family: inherit; font-size: 0.78rem; font-weight: 700; color: #1b3a5c; + background: #f0f5fc; border: 1px solid #d9e6fb; border-radius: 20px; + padding: 6px 14px; cursor: pointer; + } + .conf-toggle-btn:hover { background: #e2edfb; } + .conf-toggle-body { display: none; margin-top: 12px; } + .conf-toggle-body.open { display: block; } + .conf-fit-angle { font-size: 0.85rem; color: #3d4a5c; font-style: italic; margin: 8px 0 14px; line-height: 1.5; } + .conf-fit-angle-label { + display: block; font-size: 0.62rem; font-weight: 800; text-transform: uppercase; + letter-spacing: 0.04em; color: #1a2533; font-style: normal; margin-bottom: 3px; + } + .conf-roles-list { display: flex; flex-direction: column; gap: 10px; } + .conf-role-item { border: 1px solid #e0e7ef; border-radius: 6px; padding: 10px 12px; background: #fafbfc; } + .conf-role-badge { + display: inline-block; font-size: 0.68rem; font-weight: 700; text-transform: uppercase; + letter-spacing: 0.03em; padding: 2px 9px; border-radius: 20px; margin-right: 8px; + } + .conf-role-badge-chair { background: #e3f2fb; color: #2d6a9f; } + .conf-role-badge-presenter { background: #e5f5ee; color: #1a8a60; } + .conf-role-session { font-size: 0.85rem; font-weight: 700; color: #1a2533; } + .conf-role-when { font-size: 0.74rem; color: #9aa7b4; margin-left: 8px; font-weight: 400; text-transform: none; letter-spacing: normal; } + .conf-role-title { font-size: 0.85rem; color: #3d4a5c; font-style: italic; margin-top: 6px; } + .conf-empty-state { + border: 1px dashed #e0e7ef; border-radius: 8px; padding: 18px 20px; + color: #6b7a8d; font-size: 0.88rem; background: #fafbfc; + } + .conf-empty-state code { + background: #f0f3f7; border-radius: 4px; padding: 1px 6px; font-size: 0.85em; + } + .tab-content { display: none; } .tab-content.active { display: block; } @@ -364,9 +569,9 @@ padding: 18px; border-radius: 8px; overflow: hidden; } .comp-card.highlight { border-top: 3px solid #c0303a; } - .comp-card.fit-strong, .ab-card.fit-strong { background: #f2faf5; } - .comp-card.fit-moderate, .ab-card.fit-moderate { background: #fdf9f0; } - .comp-card.fit-weak, .ab-card.fit-weak { background: #f6f7f9; } + .comp-card.fit-strong, .ab-card.fit-strong, .conf-pres-item.fit-strong { background: #f2faf5; } + .comp-card.fit-moderate, .ab-card.fit-moderate, .conf-pres-item.fit-moderate { background: #fdf9f0; } + .comp-card.fit-weak, .ab-card.fit-weak, .conf-pres-item.fit-weak { background: #f6f7f9; } .comp-name { display: block; font-size: 1rem; font-weight: 800; color: #fff; margin: -18px -18px 12px -18px; padding: 12px 18px 10px; @@ -414,10 +619,10 @@ @media (max-width: 900px) { .ab-section-grid { flex-direction: column; } } - .comp-loc-row { display: flex; align-items: center; gap: 24px; margin: 4px 0 16px; } + .comp-loc-row { display: flex; align-items: center; gap: 24px; margin: 0 0 10px; } .comp-loc-row .comp-loc { margin-bottom: 0; flex-shrink: 0; } - .ab-fit-inline { flex: 1; min-width: 100px; padding-left: 10px; border-left: 1px solid #e0e7ef; } - .ab-fit-inline-label { font-size: 0.62rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em; color: #1a2533; margin-bottom: 14px; text-align: center; } + .ab-fit-inline { width: 100%; margin: 8px 0 14px; padding-top: 10px; border-top: 1px solid #e0e7ef; } + .ab-fit-inline-label { font-size: 0.62rem; font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em; color: #1a2533; margin-bottom: 10px; text-align: center; } .ab-fit-inline-track { position: relative; height: 6px; border-radius: 3px; background: linear-gradient(90deg, #b3bfcc 0%, #c97000 50%, #1a8a60 100%); } .ab-fit-inline-marker { position: absolute; top: -5px; width: 0; height: 0; @@ -538,6 +743,9 @@ + @@ -545,12 +753,12 @@
-
PG
+
Peter Gaal
Peter Gaal
CEO — R&D and Sales
-
MB
+
Mckinley Black
Mckinley Black
Sales and Marketing Manager
@@ -606,6 +814,3548 @@ + +
@@ -625,6 +4375,22 @@ Open Dash ↗
+ +
+

Conferences

+

Every conference where TXproducts has a speaking or attending presence, with its full presentation directory alongside it.

+
+
+
+
+
+
+
+
+
+ +
+
@@ -672,22 +4438,23 @@ document.getElementById("hero-title").textContent = HERO[key][0]; document.getElementById("hero-subtitle").textContent = HERO[key][1]; document.querySelectorAll(".hero-nav").forEach(function (n) { n.style.display = "none"; n.classList.remove("open"); }); - var heroNavId = HERO_NAV_BY_TAB[key]; - if (heroNavId) { document.getElementById(heroNavId).style.display = "flex"; } + var heroNavIds = HERO_NAV_BY_TAB[key] || []; + heroNavIds.forEach(function (id) { document.getElementById(id).style.display = "flex"; }); if (DRILL_CONFIG[key]) { setDrillView(key, DRILL_DEFAULT_VIEW[key]); } }); }); /* ---- Drill-downs: Marketing -> Social Media (dropdown) -> LinkedIn -> Dash Sales -> Market Intelligence -> site (goes there directly, one child today) ---- */ - var HERO_NAV_BY_TAB = { marketing: "hero-nav-social" }; + var HERO_NAV_BY_TAB = { marketing: ["hero-nav-social", "hero-nav-conferences"] }; var DRILL_DEFAULT_VIEW = { marketing: "marketing-root", sales: "market-intelligence" }; var DRILL_CONFIG = { marketing: { crumbsEl: "mkt-crumbs", crumbs: { "marketing-root": [["Marketing", "marketing-root"]], - "linkedin": [["Marketing", "marketing-root"], ["Social Media", "linkedin"], ["LinkedIn", "linkedin"]] + "linkedin": [["Marketing", "marketing-root"], ["Social Media", "linkedin"], ["LinkedIn", "linkedin"]], + "conferences": [["Marketing", "marketing-root"], ["Conferences", "conferences"]] } }, sales: { @@ -748,6 +4515,774 @@ document.addEventListener("click", function () { document.querySelectorAll(".hero-nav").forEach(function (n) { n.classList.remove("open"); }); }); + document.getElementById("hero-nav-conferences-btn").addEventListener("click", function () { + setDrillView("marketing", "conferences"); + }); + + /* ---- Conferences (Directory pattern: conference cards on the left, drill into + its own presentation sub-directory on the right) ---- */ + var CONF_ROOT = JSON.parse(document.getElementById("conferences-data").textContent); + var CONF_DATA = CONF_ROOT.conferences; + var confSelectedId = CONF_DATA.length ? CONF_DATA[0].id : null; + var confPresSearch = ""; + var confTopicFilter = "all"; + var confActiveFit = "all"; + var confPresSelectedIdx = null; + var confViewMode = "intro"; // "intro" | "directory" | "itinerary" — "intro" (the SPIE/Peter narrative) is the default landing view + var confListVisible = false; + + function confEsc(s) { + var d = document.createElement("div"); + d.textContent = s || ""; + return d.innerHTML; + } + function confLink(url, label) { + return url ? "" + confEsc(label) + " ↗" : ""; + } + function confPlainLink(url, label) { + return url ? "" + confEsc(label) + "" : ""; + } + + // Local equivalents of the Address Book's abFitClass/buildFitGauge (same CSS classes, + // same markup) — duplicated rather than shared because renderConferences() runs + // immediately on load, before the Address Book section further down defines its + // FIT_CLASS_BY_RATING/FIT_RATING_PCT vars. + var CONF_FIT_CLASS = { "Strong": " fit-strong", "Moderate": " fit-moderate", "Weak": " fit-weak" }; + function confFitClass(rating) { return CONF_FIT_CLASS[rating] || ""; } + var CONF_FIT_PCT = { "Weak": 12, "Moderate": 50, "Strong": 88 }; + function confFitGauge(rating) { + var pct = CONF_FIT_PCT[rating]; + if (pct === undefined) { + return "
Partnership fit: not assessed
"; + } + return "
" + + "
Partnership fit: " + confEsc(rating) + "
" + + "
" + + "
"; + } + + // Sleek outlined icons (stroke-only, currentColor) for the four strategic value tags. + // Money: coin with a dollar sign. Idea: a light bulb. Connector: three linked nodes. + // Collaborator: two overlapping people. + var VALUE_TAG_META = { + money: { + label: "Money", + icon: "" + }, + idea: { + label: "Idea", + icon: "" + }, + connector: { + label: "Connector", + icon: "" + }, + collaborator: { + label: "Collaborator", + icon: "" + } + }; + + // Compact icon-only badges for list rows (name + quick scan). + function buildValueTagBadges(tags) { + if (!tags || !tags.length) { return ""; } + return "
" + tags.map(function (t) { + var meta = VALUE_TAG_META[t]; + if (!meta) { return ""; } + return "" + meta.icon + ""; + }).join("") + "
"; + } + + // Larger labeled icons for the full detail card. + function buildValueTagIcons(tags) { + if (!tags || !tags.length) { return ""; } + return "
" + tags.map(function (t) { + var meta = VALUE_TAG_META[t]; + if (!meta) { return ""; } + return ( + "
" + + "
" + meta.icon + "
" + + "
" + confEsc(meta.label) + "
" + + "
" + ); + }).join("") + "
"; + } + + function renderConferences() { + var header = document.getElementById("conf-parent-header"); + if (header && CONF_ROOT.parent_event) { + header.innerHTML = + "
" + confEsc(CONF_ROOT.parent_event.name) + "
" + + "
" + confEsc(CONF_ROOT.parent_event.dates) + "
" + + confPlainLink(CONF_ROOT.parent_event.source_url, "Website") + + ""; + document.getElementById("conf-itinerary-toggle").addEventListener("click", function () { + confViewMode = confViewMode === "itinerary" ? "intro" : "itinerary"; + renderConferences(); + }); + } + + var listCaption = document.getElementById("conf-list-caption"); + if (listCaption) { + listCaption.innerHTML = + ""; + document.getElementById("conf-list-toggle").addEventListener("click", function () { + confListVisible = !confListVisible; + renderConferences(); + }); + } + var listEl = document.getElementById("conf-list"); + listEl.style.display = confListVisible ? "flex" : "none"; + listEl.innerHTML = ""; + CONF_DATA.forEach(function (c) { + var card = document.createElement("div"); + card.className = "conf-card" + (c.id === confSelectedId ? " selected" : ""); + var confLabel = c.conference_number ? "Conference " + c.conference_number + " — " : ""; + var presCount = (c.presenters || []).length; + card.innerHTML = + "
" + confEsc(confLabel + c.name) + "
" + + "
" + confEsc(c.dates) + " · " + presCount + " presenter" + (presCount === 1 ? "" : "s") + "
"; + card.addEventListener("click", function () { + confSelectedId = c.id; + confPresSearch = ""; + confTopicFilter = "all"; + confActiveFit = "all"; + confPresSelectedIdx = null; + confViewMode = "directory"; + renderConferences(); + }); + listEl.appendChild(card); + }); + + if (confViewMode === "itinerary") { + renderItinerary(); + return; + } + if (confViewMode === "intro") { + renderConfIntro(); + return; + } + + var detail = document.getElementById("conf-detail"); + var selected = CONF_DATA.filter(function (c) { return c.id === confSelectedId; })[0]; + if (!selected) { + detail.innerHTML = "
No conference selected.
"; + return; + } + detail.innerHTML = buildConfDetail(selected); + + var searchEl = document.getElementById("conf-pres-search"); + if (searchEl) { + searchEl.value = confPresSearch; + searchEl.addEventListener("input", function () { + confPresSearch = searchEl.value; + renderConfPresentations(selected); + }); + } + var topicEl = document.getElementById("conf-topic-filter"); + if (topicEl) { + topicEl.value = confTopicFilter; + topicEl.addEventListener("change", function () { + confTopicFilter = topicEl.value; + renderConfPresentations(selected); + }); + } + document.querySelectorAll("#conf-detail .conf-fit-chip").forEach(function (chip) { + chip.classList.toggle("active", chip.getAttribute("data-fit") === confActiveFit); + chip.addEventListener("click", function () { + confActiveFit = chip.getAttribute("data-fit"); + document.querySelectorAll("#conf-detail .conf-fit-chip").forEach(function (c) { c.classList.remove("active"); }); + chip.classList.add("active"); + renderConfPresentations(selected); + }); + }); + var clearBtn = document.getElementById("conf-clear-filters"); + if (clearBtn) { + clearBtn.addEventListener("click", function (e) { + e.preventDefault(); + confPresSearch = ""; + confTopicFilter = "all"; + confActiveFit = "all"; + confPresSelectedIdx = null; + renderConferences(); + }); + } + renderConfPresentations(selected); + } + + var confItinSelectedKey = null; + var confItinExpandedId = null; // which conference's row is expanded (null = all collapsed) + + var ITIN_INTRO = "Peter is in San Diego for the full run of SPIE Optics + Photonics 2026, splitting his time across four events."; + var ITIN_CLOSING = "Below is a selected short list of presenters considered to be valuable contacts from Peter's Choice Conference List."; + var ITIN_FIXED_COMMITMENTS = [ + { + date: "August 25", time: "11:10 AM - 11:40 AM", location: "Conv. Ctr. Room 15B", + what_label: "Presenting his own paper - Session 4 (14223),", + what_detail: "“Anharmonic phonon mixing and structural breakdown in laser-excited nanocrystals”" + }, + { + date: "August 25", time: "1:40 PM - 3:10 PM", location: "Conv. Ctr. Room 15B", + what_label: "Session Chair - Session 5 (14223),", + what_detail: "Nonlinear Excitation Regimes in Materials III" + } + ]; + + // Old-school, literal grouping: one card per (day, conference) pair, not one per + // conference. A conference active on two different days becomes two separate cards, each + // showing only that day's own stops — no multi-day content hiding behind one row. + function sanitizeIdPart(s) { return (s || "").replace(/[^a-zA-Z0-9]/g, ""); } + + function getItinCards() { + var cards = []; + var byKey = {}; + var itin = CONF_ROOT.itinerary; + (itin && itin.days || []).forEach(function (day) { + day.stops.forEach(function (stop) { + var confKey = stop.conference_number || stop.conference_name; + var cardKey = sanitizeIdPart(day.date) + "-" + sanitizeIdPart(confKey); + if (!byKey[cardKey]) { + byKey[cardKey] = { + id: cardKey, date: day.date, conference_number: stop.conference_number, + conference_name: stop.conference_name, room: stop.room, stops: [] + }; + cards.push(byKey[cardKey]); + } + byKey[cardKey].stops.push(stop); + }); + }); + return cards; + } + + // "10:00 AM - 10:20 AM" + "3:00 PM - 3:20 PM" (in stop order, already chronological) -> "10:00 AM - 3:20 PM" + function timeSpanFor(stops) { + if (!stops.length) { return ""; } + var firstStart = stops[0].time.split(" - ")[0]; + var lastParts = stops[stops.length - 1].time.split(" - "); + var lastEnd = lastParts[lastParts.length - 1]; + return firstStart + " - " + lastEnd; + } + + // "August 23, 2026" -> "August 23" (matches the "Month Day" shape buildCalendarIcon expects) + function dateAnchorFromDayString(dayDate) { + return (dayDate || "").split(",")[0]; + } + + // Flatten one card's own stops (already scoped to a single day + conference) into an + // ordered list: each shortlist person becomes its own clickable entry (resolves to a full + // presenter record), each fixed-only stop (no shortlist person — e.g. "Peter chairs this + // session") becomes a non-clickable divider so the where-am-I-next flow isn't lost. + // Person entries only — Peter's own fixed commitments live in the narrative/summary area + // above the list now (see getCardFixedNotes), not mixed in as non-clickable divider rows. + function getCardEntries(card) { + var entries = []; + card.stops.forEach(function (stop) { + var meta = card.date + " · " + stop.time + " · " + stop.room; + (stop.shortlist || []).forEach(function (s) { + entries.push({ kind: "person", key: "person-" + entries.length, meta: meta, name: s.name, stop: stop }); + }); + }); + return entries; + } + + function getCardFixedNotes(card) { + return card.stops.filter(function (s) { return s.fixed_note; }).map(function (s) { return s.fixed_note; }); + } + + function resolveItinPresenter(name, stop) { + for (var i = 0; i < CONF_DATA.length; i++) { + var c = CONF_DATA[i]; + var isMatch = stop.conference_number ? c.conference_number === stop.conference_number : c.name === stop.conference_name; + if (!isMatch) { continue; } + var found = (c.presenters || []).filter(function (x) { return x.name === name; })[0]; + if (found) { return found; } + } + return null; + } + + // A compact calendar-icon showing the actual start day (month header + big day number), + // rather than a generic icon that looks the same regardless of date. "orange" variant is + // for general event info; the default (blue) is reserved for Peter's own commitments, so + // the two read as visually distinct categories at a glance. + function buildCalendarIcon(dateAnchor, variant) { + var parts = (dateAnchor || "").split(" "); + var month = (parts[0] || "").slice(0, 3).toUpperCase(); + var day = parts[1] || ""; + return ( + "
" + + "
" + confEsc(month) + "
" + + "
" + confEsc(day) + "
" + + "
" + ); + } + + // A 64px icon matching buildCalendarIcon's footprint, used to flag Peter's own fixed + // commitments inline with the same visual weight as the day cards' calendar icons. + function buildClockIcon() { + return ( + "
" + + "" + + "" + + "" + + "" + + "
" + ); + } + + // Shared 3-column layout: calendar icon | Time/Date/Location | what's happening (label + // plus an optional second line, e.g. a paper title). Used by both the fixed-commitment + // cards and the four event rows so they read as one consistent visual language. + function buildItinCols(icon, time, date, location, whatLabel, whatDetail) { + return ( + icon + + "
" + + (time ? "
" + confEsc(time) + "
" : "") + + "
" + confEsc(date) + "
" + + "
" + confEsc(location) + "
" + + "
" + + "
" + + "
" + confEsc(whatLabel) + "
" + + (whatDetail ? "
" + confEsc(whatDetail) + "
" : "") + + "
" + ); + } + + function findConfForCard(card) { + return CONF_DATA.filter(function (x) { + return card.conference_number ? x.conference_number === card.conference_number : x.name === card.conference_name; + })[0]; + } + + // Default landing view for the Conferences tab: just the SPIE org narrative + Peter's + // affiliation, before he clicks into either his itinerary or a specific conference. + function renderConfIntro() { + var detail = document.getElementById("conf-detail"); + var pe = CONF_ROOT.parent_event || {}; + detail.innerHTML = + "
" + + "
" + confEsc(pe.name) + "
" + + "
" + confEsc(pe.dates) + "
" + + (pe.about ? "
" + confEsc(pe.about) + "
" : "") + + (pe.our_affiliation ? "
" + confEsc(pe.our_affiliation) + "
" : "") + + "
"; + } + + var confItinValueFilter = null; // "money" | "idea" | "connector" | "collaborator" | null + var confItinValueSelectedKey = null; + var VALUE_TAG_ORDER = ["money", "idea", "connector", "collaborator"]; + + // Everyone on Peter's shortlist across the whole week, deduped by name, with the day/stop + // context they were curated under (used both for the value-tag counts and the combined view). + function getAllShortlistPeople() { + var out = []; + var seen = {}; + var itin = CONF_ROOT.itinerary; + (itin && itin.days || []).forEach(function (day) { + day.stops.forEach(function (stop) { + (stop.shortlist || []).forEach(function (s) { + if (seen[s.name]) { return; } + seen[s.name] = true; + out.push({ name: s.name, presenter: resolveItinPresenter(s.name, stop), day: day, stop: stop }); + }); + }); + }); + return out; + } + + function getItinPeopleWithTag(tag) { + return getAllShortlistPeople().filter(function (e) { + return e.presenter && (e.presenter.value_tags || []).indexOf(tag) !== -1; + }); + } + + // Big clickable icon row: click one to swap the day-card list below for a combined view of + // everyone on the shortlist carrying that tag, regardless of which day/conference they're on. + function buildValueTagFilterRow() { + var all = getAllShortlistPeople(); + return "
" + VALUE_TAG_ORDER.map(function (tag) { + var meta = VALUE_TAG_META[tag]; + var count = all.filter(function (e) { return e.presenter && (e.presenter.value_tags || []).indexOf(tag) !== -1; }).length; + return ( + "" + ); + }).join("") + "
"; + } + + function renderItinValueFilterBody(tag, containerEl) { + var people = getItinPeopleWithTag(tag); + if (!people.length) { + containerEl.innerHTML = "
No one tagged " + confEsc(VALUE_TAG_META[tag].label) + " yet.
"; + return; + } + if (confItinValueSelectedKey === null || !people.some(function (e) { return e.name === confItinValueSelectedKey; })) { + confItinValueSelectedKey = people[0].name; + } + containerEl.innerHTML = + "
" + + "
" + + "
" + + "
"; + var listEl = document.getElementById("conf-itin-value-list"); + people.forEach(function (e) { + var confLabel = e.stop.conference_number ? "Conference " + e.stop.conference_number : e.stop.conference_name; + var meta = dateAnchorFromDayString(e.day.date) + " · " + e.stop.time + " · " + confLabel; + var item = document.createElement("div"); + item.className = "conf-pres-item" + (e.name === confItinValueSelectedKey ? " selected" : ""); + item.innerHTML = + "
" + confEsc(e.name) + "
" + buildValueTagBadges(e.presenter.value_tags) + "
" + + "
" + confEsc(meta) + "
"; + item.addEventListener("click", function () { + confItinValueSelectedKey = e.name; + renderItinValueFilterBody(tag, containerEl); + }); + listEl.appendChild(item); + }); + var detailEl = document.getElementById("conf-itin-value-detail"); + var selected = people.filter(function (e) { return e.name === confItinValueSelectedKey; })[0]; + if (selected && selected.presenter) { + detailEl.innerHTML = buildPresenterDetailHtml(selected.presenter); + wireToggleButtons("#conf-itin-value-detail"); + } + } + + function renderItinerary() { + var detail = document.getElementById("conf-detail"); + var cards = getItinCards(); + + var rowsHtml = cards.map(function (card) { + var confLabel = card.conference_number ? "Conference " + card.conference_number + " — " : ""; + var isExpanded = confItinExpandedId === card.id; + return ( + "
" + + "
" + + buildItinCols(buildCalendarIcon(dateAnchorFromDayString(card.date), "orange"), timeSpanFor(card.stops), card.date, card.room, confLabel + card.conference_name) + + "
" + + "
" + + "
" + ); + }).join(""); + + var commitmentsHtml = ITIN_FIXED_COMMITMENTS.map(function (fc) { + return ( + "
" + + buildItinCols(buildCalendarIcon(fc.date), fc.time, fc.date, fc.location, fc.what_label, fc.what_detail) + + "
" + ); + }).join(""); + + detail.innerHTML = + "
" + + "
Peter's Itinerary
" + + "
" + confEsc(ITIN_INTRO) + "
" + + "
Fixed Commitments
" + + "
" + commitmentsHtml + "
" + + "
" + confEsc(ITIN_CLOSING) + "
" + + buildValueTagFilterRow() + + (confItinValueFilter ? "
" : rowsHtml) + + "
"; + + document.querySelectorAll(".conf-itin-value-btn").forEach(function (btn) { + btn.addEventListener("click", function () { + var tag = btn.getAttribute("data-value-tag"); + confItinValueFilter = confItinValueFilter === tag ? null : tag; + confItinValueSelectedKey = null; + confItinExpandedId = null; + renderItinerary(); + }); + }); + + if (confItinValueFilter) { + var valueBodyEl = document.getElementById("conf-itin-value-body"); + if (valueBodyEl) { renderItinValueFilterBody(confItinValueFilter, valueBodyEl); } + return; + } + + document.querySelectorAll(".conf-itin-row-head").forEach(function (head) { + head.addEventListener("click", function () { + var id = head.parentElement.getAttribute("data-itin-card"); + confItinExpandedId = confItinExpandedId === id ? null : id; + confItinSelectedKey = null; + renderItinerary(); + }); + }); + + if (confItinExpandedId) { + var card = cards.filter(function (x) { return x.id === confItinExpandedId; })[0]; + var bodyEl = document.getElementById("conf-itin-body-" + confItinExpandedId); + if (card && bodyEl) { renderItinCardBody(card, bodyEl); } + } + } + + function renderItinCardBody(card, bodyEl) { + var entries = getCardEntries(card); + var parentConf = findConfForCard(card); + var fixedNotes = getCardFixedNotes(card); + var summaryHtml = ""; + if (parentConf && parentConf.itin_summary) { summaryHtml += "
" + confEsc(parentConf.itin_summary) + "
"; } + if (fixedNotes.length) { + summaryHtml += "
" + buildClockIcon() + "
" + fixedNotes.map(function (n) { return confEsc(n); }).join("
") + "
"; + } + if (!entries.length) { + bodyEl.innerHTML = summaryHtml + "
No stops curated yet for this day.
"; + return; + } + if (confItinSelectedKey === null || !entries.some(function (e) { return e.key === confItinSelectedKey; })) { + confItinSelectedKey = entries.length ? entries[0].key : null; + } + + bodyEl.innerHTML = + summaryHtml + + "
" + + "
" + + "
" + + "
"; + + var listEl = document.getElementById("conf-itin-list"); + entries.forEach(function (e) { + var entryPresenter = resolveItinPresenter(e.name, e.stop); + var item = document.createElement("div"); + item.className = "conf-pres-item" + (e.key === confItinSelectedKey ? " selected" : ""); + item.innerHTML = + "
" + confEsc(e.name) + "
" + buildValueTagBadges(entryPresenter ? entryPresenter.value_tags : null) + "
" + + "
" + confEsc(e.meta) + "
"; + item.addEventListener("click", function () { + confItinSelectedKey = e.key; + renderItinCardBody(card, bodyEl); + }); + listEl.appendChild(item); + }); + + var selectedEntry = entries.filter(function (e) { return e.key === confItinSelectedKey; })[0]; + var detailEl = document.getElementById("conf-itin-detail"); + if (!selectedEntry) { + detailEl.innerHTML = "
No one on the shortlist yet.
"; + return; + } + var p = resolveItinPresenter(selectedEntry.name, selectedEntry.stop); + if (!p) { + detailEl.innerHTML = "
Could not find \"" + confEsc(selectedEntry.name) + "\" in the presenter directory.
"; + return; + } + detailEl.innerHTML = buildPresenterDetailHtml(p); + wireToggleButtons("#conf-itin-detail"); + } + + var ROLE_TYPE_LABEL = { chair: "Chair", presenter: "Presenter" }; + + // Derive topic-filter options straight from real session names in the data (stripping + // trailing "I"/"II"/"III"/"IV" so e.g. "Mirror Optics I/II/III" collapse to one option). + // This guarantees every option actually matches something — a hand-maintained topics + // list can silently drift out of sync with the real session names (as happened here). + function getConfTopics(c) { + var seen = {}; + var out = []; + (c.presenters || []).forEach(function (p) { + (p.roles || []).forEach(function (r) { + if (!r.session) { return; } + var base = r.session.replace(/^Session \d+:\s*/, "").replace(/\s+(I|II|III|IV|V)$/, "").trim(); + if (base && !seen[base]) { seen[base] = true; out.push(base); } + }); + }); + return out; + } + + function buildConfDetail(c) { + var topicOptions = getConfTopics(c).map(function (t) { + return ""; + }).join(""); + + return ( + "
" + + "
" + confEsc(c.name) + "
" + + "
" + confEsc(c.dates) + (c.itin_time ? ", " + confEsc(c.itin_time) : "") + " · " + confEsc(c.location) + "
" + + "
" + confEsc(c.abstract) + "
" + + (c.our_presenter_note ? "
" + confEsc(c.our_presenter_note) + "
" : "") + + "
" + + "" + + "" + + "Clear filters" + + "
" + + "
Presenters " + + "Fit:" + + "" + + "" + + "" + + "" + + "
" + + "
" + + "
" + ); + } + + function personHaystack(p) { + var roleBits = (p.roles || []).map(function (r) { + return [r.session, r.title, r.abstract].join(" "); + }).join(" "); + return [p.name, p.affiliation, p.bio, roleBits].join(" ").toLowerCase(); + } + + function renderConfPresentations(c) { + var body = document.getElementById("conf-pres-body"); + var countTag = document.getElementById("conf-pres-count-tag"); + if (!body) { return; } + var prevListEl = document.getElementById("conf-pres-list"); + var prevScrollTop = prevListEl ? prevListEl.scrollTop : 0; + var all = c.presenters || []; + var q = confPresSearch.trim().toLowerCase(); + var filtered = []; + all.forEach(function (p, idx) { + var hay = personHaystack(p); + var matchesSearch = !q || hay.indexOf(q) !== -1; + var matchesTopic = confTopicFilter === "all" || hay.indexOf(confTopicFilter.toLowerCase()) !== -1; + var matchesFit = confActiveFit === "all" || p.fit_rating === confActiveFit; + if (matchesSearch && matchesTopic && matchesFit) { filtered.push({ p: p, idx: idx }); } + }); + + if (!all.length) { + countTag.textContent = ""; + body.innerHTML = "
Awaiting the presenter list for this conference. Fill in conferences/speakers_template.json (or the .yaml version) and send it back — it drops straight into this directory.
"; + return; + } + countTag.textContent = filtered.length + " of " + all.length + " shown"; + if (!filtered.length) { + body.innerHTML = "
No presenters match these filters" + (confPresSearch ? " (\"" + confEsc(confPresSearch) + "\")" : "") + ".
"; + return; + } + + if (confPresSelectedIdx === null || !filtered.some(function (f) { return f.idx === confPresSelectedIdx; })) { + confPresSelectedIdx = filtered[0].idx; + } + + body.innerHTML = + "
" + + "
" + + "
" + + "
"; + + var listEl = document.getElementById("conf-pres-list"); + filtered.forEach(function (f) { + var sessions = (f.p.roles || []).map(function (r) { return r.session; }) + .filter(function (s, i, arr) { return s && arr.indexOf(s) === i; }) + .join(" · "); + var item = document.createElement("div"); + item.className = "conf-pres-item" + confFitClass(f.p.fit_rating) + (f.idx === confPresSelectedIdx ? " selected" : ""); + item.innerHTML = + "
" + confEsc(f.p.name) + "
" + buildValueTagBadges(f.p.value_tags) + "
" + + "
" + confEsc(sessions) + "
"; + item.addEventListener("click", function () { + confPresSelectedIdx = f.idx; + renderConfPresentations(c); + }); + listEl.appendChild(item); + }); + listEl.scrollTop = prevScrollTop; + + var p = all[confPresSelectedIdx]; + document.getElementById("conf-pres-detail").innerHTML = buildPresenterDetailHtml(p); + wireToggleButtons("#conf-pres-detail"); + } + + // Shared by the normal presenter directory AND the itinerary view — same detail card + // rendering either way, since the itinerary is just a curated lens on the same people. + function buildPresenterDetailHtml(p) { + var website = confPlainLink(p.website, "Website"); + var linkedin = confPlainLink(p.linkedin, "LinkedIn"); + + var rolesHtml = (p.roles || []).map(function (r, i) { + var when = [r.date, r.time].filter(Boolean).join(" · "); + var badgeClass = "conf-role-badge-" + r.type; + var titleLine = r.type === "presenter" ? "
“" + confEsc(r.title || "(untitled)") + "”
" : ""; + var abstractToggle = ""; + if (r.abstract) { + var key = "abstract" + i; + abstractToggle = + "" + + "
" + confEsc(r.abstract) + "
"; + } + var notesLine = r.notes ? "
" + confEsc(r.notes) + "
" : ""; + return ( + "
" + + "" + confEsc(ROLE_TYPE_LABEL[r.type] || r.type) + "" + + "" + confEsc(r.session || "") + "" + + (when ? "" + confEsc(when) + "" : "") + + titleLine + abstractToggle + notesLine + + "
" + ); + }).join(""); + + var aboutToggle = ""; + if (p.bio) { + var firstName = p.name ? p.name.split(" ")[0] : "Presenter"; + aboutToggle = + "
" + + "" + + "
" + confEsc(p.bio) + "
" + + "
"; + } + + var logEntries = p.outreach_log || []; + var logHtml = logEntries.length + ? logEntries.map(function (l) { return "
" + confEsc(l.date) + "" + confEsc(l.note) + "
"; }).join("") + : "
No conversation yet.
"; + var outreachLogBox = + "
" + + "
Outreach Log
" + + "
" + logHtml + "
" + + "
"; + + var notesBox = + "
Notes
" + (p.notes ? confEsc(p.notes) : "No notes yet.") + "
"; + + var partnershipAngleBox = ""; + if (p.partnership_angle && p.partnership_angle.length) { + partnershipAngleBox = + "
" + + "
Partnership Angle (where we'd fit)
" + + p.partnership_angle.map(function (a) { + return "
" + confEsc(a) + "
"; + }).join("") + + "
"; + } + + return ( + "
" + + "
" + confEsc(p.name) + "
" + + confFitGauge(p.fit_rating) + + buildValueTagIcons(p.value_tags) + + (p.fit_angle ? "
Why " + confEsc(p.fit_rating || "this") + " fit" + confEsc(p.fit_angle) + "
" : "") + + partnershipAngleBox + + "
Association:" + confEsc(p.affiliation || "—") + "
" + + (website || linkedin ? "
" + website + linkedin + "
" : "") + + "
Conference Roles
" + + "
" + rolesHtml + "
" + + aboutToggle + + outreachLogBox + + notesBox + + "
" + ); + } + + function wireToggleButtons(containerSelector) { + document.querySelectorAll(containerSelector + " .conf-toggle-btn").forEach(function (btn) { + btn.addEventListener("click", function () { + var key = btn.getAttribute("data-toggle"); + var bodyEl = document.querySelector(containerSelector + " [data-toggle-body=\"" + key + "\"]"); + var isOpen = bodyEl.classList.toggle("open"); + var label = btn.textContent.replace(/^[▾▴]\s*(Show|Hide)\s*/, ""); + btn.innerHTML = (isOpen ? "▴ Hide " : "▾ Show ") + label; + }); + }); + } + + renderConferences(); /* ---- Address Book ---- */ var AB_SOURCE_CLASS = { "Marketing Intelligence": "mi", "Outreach": "boss", "Engaged Non-Follower": "eng" }; @@ -898,7 +5433,8 @@ var summary = "
" + "
" + abEsc(e.company || "(no company)") + "
" + - "
" + locLine + "
" + buildFitGauge(e.fit_rating) + "
" + + buildFitGauge(e.fit_rating) + + "
" + locLine + "
" + "
" + abEsc(desc || "Not researched yet — what they do goes here.") + "
" + "
" + tags + "
" + "
" +