Skip to content

Commit fe1662f

Browse files
committed
udpdate community pages
1 parent e669726 commit fe1662f

1 file changed

Lines changed: 158 additions & 26 deletions

File tree

_layouts/communities.html

Lines changed: 158 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@
33
---
44

55
<style>
6-
/* minimal styling — adapt to your site CSS / tailwind as needed */
76
.communities-wrap { max-width: 1100px; margin: 0 auto; padding: 24px; padding-top: 90px; }
87
.controls { display:flex; gap:10px; flex-wrap:wrap; margin-bottom:18px; }
98
.controls input, .controls select { padding:8px 10px; font-size:14px; }
109
#speakers-grid { display:grid; grid-template-columns: repeat(auto-fill,minmax(180px,1fr)); gap:12px; }
10+
1111
.speaker-card {
1212
position:relative;
1313
display:flex; flex-direction:column; align-items:center;
1414
text-decoration:none; color:inherit;
1515
border:1px solid #e6e6e6; border-radius:12px; padding:14px; background:#fff;
1616
box-shadow:0 6px 18px rgba(10,10,10,0.03); transition:transform .12s ease;
17+
z-index: 1; /* keep base stacking for card itself */
1718
}
1819
.speaker-card:hover { transform:translateY(-4px); }
1920
.speaker-card img { width:80px; height:80px; border-radius:50%; object-fit:cover; margin-bottom:8px; }
@@ -23,6 +24,34 @@
2324
background:#0073b1; color:#fff; font-weight:700; font-size:12px;
2425
padding:4px 8px; border-radius:999px;
2526
}
27+
28+
/* Global tooltip (appended to body via JS) */
29+
.global-tooltip {
30+
position: absolute;
31+
pointer-events: none; /* never block mouse events */
32+
white-space: nowrap; /* do not wrap lines */
33+
z-index: 9999999; /* very high, will be above everything */
34+
background: #222;
35+
color: #fff;
36+
padding: 8px 12px;
37+
border-radius: 6px;
38+
font-size: 0.8rem;
39+
box-shadow: 0 6px 20px rgba(0,0,0,0.25);
40+
transform-origin: center top;
41+
transition: opacity .12s ease;
42+
opacity: 0;
43+
display: block; /* kept in DOM; JS sets opacity/visibility */
44+
}
45+
.global-tooltip.visible { opacity: 1; }
46+
47+
.global-tooltip strong { display:block; margin-bottom:6px; }
48+
.global-tooltip ol { margin: 0; padding-left: 18px; }
49+
.global-tooltip li { margin: 2px 0; }
50+
51+
/* small responsive fallback: if viewport too small, allow wrapping */
52+
@media (max-width: 420px) {
53+
.global-tooltip { white-space: normal; max-width: calc(100vw - 20px); }
54+
}
2655
</style>
2756

2857
<div class="communities-wrap pt-44 pb-16 px-6 max-w-4xl mx-auto">
@@ -37,25 +66,18 @@ <h2>{{ page.title | default: "Communities" }}</h2>
3766
</select>
3867
</div>
3968

40-
{%- comment -%}
41-
Build an array `speakers` where each item is:
42-
key|||display_url|||name|||count
43-
key is normalized url used for dedupe
44-
{%- endcomment -%}
45-
4669
{% assign speakers = "" | split: "|" %}
4770

4871
{% for event in site.events %}
4972
{% if event.speakers %}
5073
{% for sp in event.speakers %}
5174
{% if sp.url contains "linkedin.com" %}
52-
{% assign display_url = sp.url %}
75+
{% assign display_url = sp.url %}
5376
{% else %}
54-
{% continue %}
77+
{% continue %}
5578
{% endif %}
5679

5780
{% assign key = display_url | remove: "https://" | remove: "http://" | remove: "www." | replace: "/", "" | downcase %}
58-
5981
{% assign updated = false %}
6082
{% assign new_speakers = "" | split: "|" %}
6183

@@ -66,6 +88,8 @@ <h2>{{ page.title | default: "Communities" }}</h2>
6688
{% assign ex_display = parts[1] %}
6789
{% assign ex_name = parts[2] %}
6890
{% assign ex_count = parts[3] %}
91+
{% assign ex_history = parts[4] | default: "" %}
92+
6993
{% if ex_key == key %}
7094
{% assign cur_name = sp.name %}
7195
{% if cur_name.size > ex_name.size %}
@@ -74,7 +98,11 @@ <h2>{{ page.title | default: "Communities" }}</h2>
7498
{% assign chosen_name = ex_name %}
7599
{% endif %}
76100
{% assign new_count = ex_count | plus: 1 %}
77-
{% assign updated_item = ex_key | append: "|||" | append: ex_display | append: "|||" | append: chosen_name | append: "|||" | append: new_count %}
101+
102+
{% assign history_entry = event.title | append: " (" | append: event.event_date | append: ") - " | append: sp.topic %}
103+
{% assign new_history = ex_history | append: ";;;" | append: history_entry %}
104+
105+
{% assign updated_item = ex_key | append: "|||" | append: ex_display | append: "|||" | append: chosen_name | append: "|||" | append: new_count | append: "|||" | append: new_history %}
78106
{% assign new_speakers = new_speakers | push: updated_item %}
79107
{% assign updated = true %}
80108
{% else %}
@@ -84,7 +112,8 @@ <h2>{{ page.title | default: "Communities" }}</h2>
84112
{% endfor %}
85113

86114
{% if updated == false %}
87-
{% assign new_item = key | append: "|||" | append: display_url | append: "|||" | append: sp.name | append: "|||" | append: "1" %}
115+
{% assign history_entry = event.title | append: " (" | append: event.event_date | append: ") - " | append: sp.topic %}
116+
{% assign new_item = key | append: "|||" | append: display_url | append: "|||" | append: sp.name | append: "|||" | append: "1" | append: "|||" | append: history_entry %}
88117
{% assign new_speakers = new_speakers | push: new_item %}
89118
{% endif %}
90119

@@ -102,14 +131,8 @@ <h2>{{ page.title | default: "Communities" }}</h2>
102131
{% assign display_url = parts[1] %}
103132
{% assign name = parts[2] %}
104133
{% assign count = parts[3] %}
105-
106-
{%- comment -%}
107-
Image filename resolution order (priority jpeg > jpg > png):
108-
1. slug-first.jpeg / .jpg / .png
109-
2. full-name.jpeg / .jpg / .png
110-
3. first-word.jpeg / .jpg / .png
111-
4. placeholder.png
112-
{%- endcomment -%}
134+
{% assign history_raw = parts[4] %}
135+
{% assign history_list = history_raw | split: ";;;" %}
113136

114137
{% assign slug = display_url | split: "/" | last %}
115138
{% assign slug_first = slug | split: "-" | first %}
@@ -128,8 +151,12 @@ <h2>{{ page.title | default: "Communities" }}</h2>
128151
{% assign file_jpg_first = "../assets/lombokdev/speakers/" | append: first_word | append: ".jpg" %}
129152
{% assign file_png_first = "../assets/lombokdev/speakers/" | append: first_word | append: ".png" %}
130153

131-
<a class="speaker-card" href="{{ display_url }}" target="_blank" rel="noopener noreferrer"
132-
data-name="{{ name | escape }}" data-url="{{ display_url | escape }}" data-count="{{ count }}">
154+
<a class="speaker-card"
155+
href="{{ display_url }}" target="_blank" rel="noopener noreferrer"
156+
data-name="{{ name | escape }}"
157+
data-url="{{ display_url | escape }}"
158+
data-count="{{ count }}"
159+
data-history="{{ history_raw | escape }}">
133160
<img src="{{ file_jpeg_slug }}" alt="{{ name | escape }}"
134161
onerror="this.onerror=null;
135162
this.src='{{ file_jpg_slug }}';
@@ -163,8 +190,8 @@ <h2>{{ page.title | default: "Communities" }}</h2>
163190
function filterCards() {
164191
const term = (searchInput.value || '').trim().toLowerCase();
165192
getCards().forEach(card => {
166-
const name = card.dataset.name.toLowerCase();
167-
const url = card.dataset.url.toLowerCase();
193+
const name = (card.dataset.name || '').toLowerCase();
194+
const url = (card.dataset.url || '').toLowerCase();
168195
const visible = name.includes(term) || url.includes(term);
169196
card.style.display = visible ? '' : 'none';
170197
});
@@ -190,8 +217,113 @@ <h2>{{ page.title | default: "Communities" }}</h2>
190217

191218
searchInput.addEventListener('input', filterCards);
192219
sortSelect.addEventListener('change', e => sortCards(e.target.value));
193-
194-
// initial sort
195220
sortCards('az');
221+
222+
/* ---------------------------
223+
Global tooltip appended to body
224+
--------------------------- */
225+
const tooltip = document.createElement('div');
226+
tooltip.className = 'global-tooltip';
227+
tooltip.setAttribute('role', 'tooltip');
228+
tooltip.style.display = 'none';
229+
document.body.appendChild(tooltip);
230+
231+
let activeCard = null;
232+
let hideTimeout = null;
233+
234+
function showTooltipFor(card) {
235+
const historyRaw = card.getAttribute('data-history') || '';
236+
const items = historyRaw.split(';;;').map(s => s.trim()).filter(Boolean);
237+
if (!items.length) {
238+
hideTooltip();
239+
return;
240+
}
241+
242+
// build content via DOM nodes (safe against HTML injection)
243+
tooltip.innerHTML = ''; // clear
244+
const strong = document.createElement('strong');
245+
strong.textContent = 'Speaking at:';
246+
tooltip.appendChild(strong);
247+
248+
const ol = document.createElement('ol');
249+
items.forEach(it => {
250+
const li = document.createElement('li');
251+
li.textContent = it;
252+
ol.appendChild(li);
253+
});
254+
tooltip.appendChild(ol);
255+
256+
// ensure it's visible in DOM to measure
257+
tooltip.style.display = 'block';
258+
// allow CSS transition: set visible class next tick
259+
window.requestAnimationFrame(() => tooltip.classList.add('visible'));
260+
261+
// position below the card (absolute to the document)
262+
const rect = card.getBoundingClientRect();
263+
264+
// measure tooltip width after it's rendered
265+
const ttRect = tooltip.getBoundingClientRect();
266+
const ttWidth = ttRect.width;
267+
const scrollX = window.scrollX || window.pageXOffset;
268+
const scrollY = window.scrollY || window.pageYOffset;
269+
270+
// center horizontally on card, but clamp to viewport
271+
let left = scrollX + rect.left + (rect.width / 2) - (ttWidth / 2);
272+
const minLeft = scrollX + 8;
273+
const maxLeft = scrollX + window.innerWidth - ttWidth - 8;
274+
if (left < minLeft) left = minLeft;
275+
if (left > maxLeft) left = maxLeft;
276+
277+
// position just below the card
278+
const top = scrollY + rect.bottom + 8;
279+
280+
tooltip.style.left = left + 'px';
281+
tooltip.style.top = top + 'px';
282+
tooltip.style.pointerEvents = 'none';
283+
284+
activeCard = card;
285+
}
286+
287+
function hideTooltip() {
288+
if (!tooltip) return;
289+
tooltip.classList.remove('visible');
290+
// allow transition; hide completely after short delay
291+
clearTimeout(hideTimeout);
292+
hideTimeout = setTimeout(() => {
293+
tooltip.style.display = 'none';
294+
tooltip.innerHTML = '';
295+
}, 120);
296+
activeCard = null;
297+
}
298+
299+
// attach pointer events to cards
300+
getCards().forEach(card => {
301+
card.addEventListener('pointerenter', () => {
302+
// cancel any pending hide
303+
clearTimeout(hideTimeout);
304+
showTooltipFor(card);
305+
});
306+
card.addEventListener('pointerleave', () => {
307+
hideTooltip();
308+
});
309+
// keyboard accessibility: show tooltip on focus
310+
card.addEventListener('focus', () => {
311+
clearTimeout(hideTimeout);
312+
showTooltipFor(card);
313+
});
314+
card.addEventListener('blur', () => hideTooltip());
315+
});
316+
317+
// hide tooltip on scroll / resize (reposition would be nicer, but simple hide is robust)
318+
let lastScroll = 0;
319+
window.addEventListener('scroll', () => {
320+
// only hide if user scrolled more than a few px to avoid flicker
321+
if (Math.abs(window.scrollY - lastScroll) > 2) {
322+
hideTooltip();
323+
}
324+
lastScroll = window.scrollY;
325+
}, { passive: true });
326+
window.addEventListener('resize', hideTooltip);
327+
196328
})();
197329
</script>

0 commit comments

Comments
 (0)