Skip to content

Commit d15ae8e

Browse files
TineoCcursoragent
andcommitted
fix: harden NCO next-session card after review
Show static fallback without JS, pick next upcoming session per region, restrict join/calendar URLs to https, and respect reduced-motion prefs. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent ba6774f commit d15ae8e

3 files changed

Lines changed: 82 additions & 34 deletions

File tree

assets/scss/_meta_panel.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
font-size: 0.65rem;
4747
text-transform: uppercase;
4848
letter-spacing: 0.1em;
49-
color: $slate-400;
49+
color: $slate-300; // lighter than .metadata-label for hierarchy
5050
margin-bottom: 0.75rem;
5151
font-weight: 600;
5252
}

assets/scss/_nco.scss

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,3 +239,32 @@
239239
color: $primary;
240240
transform: translateY(-1px);
241241
}
242+
243+
@media (prefers-reduced-motion: reduce) {
244+
.nco-pulse-dot,
245+
.nco-loader,
246+
.nco-state {
247+
animation: none;
248+
}
249+
250+
.nco-session-row,
251+
.nco-globe-icon-wrapper,
252+
.nco-session-link,
253+
.nco-session-title-text,
254+
.nco-session-time-text,
255+
.nco-action-btn,
256+
.nco-connected-link,
257+
.nco-connected-link i,
258+
.nco-footer-btn {
259+
transition: none;
260+
}
261+
262+
.nco-session-row:hover .nco-globe-icon-wrapper {
263+
transform: none;
264+
}
265+
266+
.nco-action-btn:hover,
267+
.nco-footer-btn:hover {
268+
transform: none;
269+
}
270+
}

layouts/partials/nco-next-session.html

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
{{ end }}
77
{{ end }}
88

9-
{{ $calPage := .Site.GetPage "resources/community-calendar.md" }}
10-
{{ $calLink := $calPage.RelPermalink | default "/resources/calendar/" }}
9+
{{ $calLink := "/resources/calendar/" }}
10+
{{ with .Site.GetPage "resources/community-calendar" }}
11+
{{ $calLink = .RelPermalink }}
12+
{{ end }}
1113
{{ $nco := .Site.Data.nco }}
1214

1315
{{ $ncoData := dict
@@ -21,22 +23,23 @@
2123

2224
<div class="metadata-container nco-container my-4" id="nco-root" data-nco='{{ $ncoData | jsonify }}' aria-label="Kubernetes New Contributor Orientation">
2325

24-
<div class="d-flex flex-column align-items-center justify-content-center py-5" id="nco-loading" aria-live="polite">
26+
{{/* Loading is hidden until JS runs so no-JS users keep the static fallback. */}}
27+
<div class="d-none flex-column align-items-center justify-content-center py-5" id="nco-loading" aria-live="polite">
2528
<div class="nco-loader mb-3"></div>
2629
<span class="small fw-bold text-uppercase nco-muted">Fetching next session...</span>
2730
</div>
2831

2932
<div class="nco-state d-none" id="nco-card">
3033
<div class="mb-4" id="nco-date-header">
3134
<span class="metadata-sublabel">
32-
<span class="nco-pulse-dot"></span>Next Session Date
35+
<span class="nco-pulse-dot"></span><span id="nco-date-sublabel">Next Session Date</span>
3336
</span>
3437
<h3 class="fw-bold h3 mt-1 mb-0" id="nco-date-display"></h3>
3538
</div>
3639
<div class="d-flex flex-column mb-4" id="nco-sessions-grid"></div>
3740
</div>
3841

39-
<div class="nco-state d-none" id="nco-fallback">
42+
<div class="nco-state" id="nco-fallback">
4043
<div class="mb-4">
4144
<span class="metadata-sublabel" id="nco-fallback-sublabel">Typical schedule (unconfirmed)</span>
4245
<h3 class="fw-bold h3 mt-1 mb-0">{{ $nco.schedule }}</h3>
@@ -133,24 +136,30 @@ <h3 class="fw-bold h3 mt-1 mb-0">{{ $nco.schedule }}</h3>
133136
<script>
134137
(function () {
135138
var root = document.getElementById('nco-root');
139+
if (!root) return;
136140
var d = JSON.parse(root.dataset.nco);
137141

138142
var loadingEl = document.getElementById('nco-loading');
139143
var cardEl = document.getElementById('nco-card');
140144
var fallbackEl = document.getElementById('nco-fallback');
141145
var dateHeader = document.getElementById('nco-date-header');
146+
var dateSublabel = document.getElementById('nco-date-sublabel');
142147
var dateEl = document.getElementById('nco-date-display');
143148
var gridEl = document.getElementById('nco-sessions-grid');
144149
var fbSublabel = document.getElementById('nco-fallback-sublabel');
145150
var fbMessage = document.getElementById('nco-fallback-message');
146151

147-
var ncoMatcher = /new contributor orientation|nco/i;
152+
var ncoMatcher = /new contributor orientation|\bnco\b/i;
148153
var JOIN_HOSTS = ['zoom.us', 'meet.google.com'];
149154
var CAL_HOSTS = ['google.com', 'calendar.google.com'];
150155

151156
function show(el) {
152-
[loadingEl, cardEl, fallbackEl].forEach(function (e) { e.classList.add('d-none'); });
157+
[loadingEl, cardEl, fallbackEl].forEach(function (e) {
158+
e.classList.add('d-none');
159+
e.classList.remove('d-flex');
160+
});
153161
el.classList.remove('d-none');
162+
if (el === loadingEl) el.classList.add('d-flex');
154163
}
155164

156165
function el(tag, className) {
@@ -178,7 +187,7 @@ <h3 class="fw-bold h3 mt-1 mb-0">{{ $nco.schedule }}</h3>
178187
if (!raw || typeof raw !== 'string') return '';
179188
try {
180189
var u = new URL(raw);
181-
if (u.protocol !== 'http:' && u.protocol !== 'https:') return '';
190+
if (u.protocol !== 'https:') return '';
182191
if (allowedHosts && !hostAllowed(u.hostname, allowedHosts)) return '';
183192
return u.href;
184193
} catch (e) {
@@ -214,7 +223,7 @@ <h3 class="fw-bold h3 mt-1 mb-0">{{ $nco.schedule }}</h3>
214223
if (ev.location) candidates.push(ev.location);
215224
if (ev.hangoutLink) candidates.push(ev.hangoutLink);
216225
if (ev.description) {
217-
var re = /https?:\/\/[^\s"'<>]+/gi;
226+
var re = /https:\/\/[^\s"'<>]+/gi;
218227
var m;
219228
while ((m = re.exec(ev.description)) !== null) {
220229
candidates.push(m[0].replace(/[.,);]+$/, ''));
@@ -238,7 +247,7 @@ <h3 class="fw-bold h3 mt-1 mb-0">{{ $nco.schedule }}</h3>
238247
return a;
239248
}
240249

241-
function appendRow(parent, ev, region, now) {
250+
function appendRow(parent, ev, region, now, showDate) {
242251
var isEMEA = region === 'emea';
243252
var label = isEMEA ? d.emeaLabel : d.amerLabel;
244253
var iconClass = isEMEA ? 'fas fa-globe-europe' : 'fas fa-globe-americas';
@@ -271,7 +280,11 @@ <h3 class="fw-bold h3 mt-1 mb-0">{{ $nco.schedule }}</h3>
271280
var joinLink = ended ? '' : extractJoinLink(ev);
272281
var calEvLink = safeHttpUrl(ev.htmlLink || '', CAL_HOSTS);
273282

274-
var timeStr = start.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
283+
var timeStr = '';
284+
if (showDate) {
285+
timeStr = start.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' }) + ' \u00b7 ';
286+
}
287+
timeStr += start.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' });
275288
if (ev.end) {
276289
timeStr += ' - ' + end.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit', timeZoneName: 'short' });
277290
} else {
@@ -331,6 +344,8 @@ <h3 class="fw-bold h3 mt-1 mb-0">{{ $nco.schedule }}</h3>
331344

332345
if (!d.apiKey) { runFallback('error'); return; }
333346

347+
show(loadingEl);
348+
334349
var now = new Date();
335350
var timeMin = new Date(now.getTime() - 86400000).toISOString();
336351
var url = 'https://www.googleapis.com/calendar/v3/calendars/'
@@ -372,45 +387,49 @@ <h3 class="fw-bold h3 mt-1 mb-0">{{ $nco.schedule }}</h3>
372387
});
373388

374389
var now = new Date();
375-
var anchor = null;
376-
for (var i = 0; i < events.length; i++) {
377-
if (eventEnd(events[i]) > now) {
378-
anchor = events[i];
379-
break;
380-
}
381-
}
382-
383-
if (!anchor) {
384-
runFallback('empty');
385-
return;
386-
}
387-
388-
var dayKey = utcDayKey(eventStart(anchor));
389390
var emeaEvent = null;
390391
var amerEvent = null;
391392

392393
for (var j = 0; j < events.length; j++) {
393394
var ev = events[j];
394-
if (utcDayKey(eventStart(ev)) !== dayKey) continue;
395+
if (eventEnd(ev) <= now) continue;
395396
var region = regionOf(ev);
396397
if (region === 'emea' && !emeaEvent) emeaEvent = ev;
397398
else if (region === 'amer' && !amerEvent) amerEvent = ev;
399+
if (emeaEvent && amerEvent) break;
398400
}
399401

400402
if (!emeaEvent && !amerEvent) {
401403
runFallback('empty');
402404
return;
403405
}
404406

405-
var dayDate = eventStart(anchor);
406-
dateHeader.style.display = '';
407-
dateEl.textContent = dayDate.toLocaleDateString(undefined, {
408-
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'
409-
});
407+
var sameDay = false;
408+
var headerDate = null;
409+
if (emeaEvent && amerEvent) {
410+
sameDay = utcDayKey(eventStart(emeaEvent)) === utcDayKey(eventStart(amerEvent));
411+
headerDate = eventStart(emeaEvent) < eventStart(amerEvent)
412+
? eventStart(emeaEvent)
413+
: eventStart(amerEvent);
414+
} else {
415+
sameDay = true;
416+
headerDate = eventStart(emeaEvent || amerEvent);
417+
}
418+
419+
if (dateHeader) dateHeader.style.display = '';
420+
if (sameDay) {
421+
if (dateSublabel) dateSublabel.textContent = 'Next Session Date';
422+
dateEl.textContent = headerDate.toLocaleDateString(undefined, {
423+
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'
424+
});
425+
} else {
426+
if (dateSublabel) dateSublabel.textContent = 'Upcoming Sessions';
427+
dateEl.textContent = 'Different dates — see each region below';
428+
}
410429

411430
while (gridEl.firstChild) gridEl.removeChild(gridEl.firstChild);
412-
appendRow(gridEl, emeaEvent, 'emea', now);
413-
appendRow(gridEl, amerEvent, 'amer', now);
431+
appendRow(gridEl, emeaEvent, 'emea', now, !sameDay);
432+
appendRow(gridEl, amerEvent, 'amer', now, !sameDay);
414433
show(cardEl);
415434
})
416435
.catch(function () { runFallback('error'); });

0 commit comments

Comments
 (0)