Merge pull request #5 from coding-with-hans-heinemann/hans/calendar-day-click

feat: click calendar day to show events detail
This commit is contained in:
2026-03-17 14:36:38 -04:00
committed by GitHub
2 changed files with 229 additions and 17 deletions

View File

@@ -147,6 +147,10 @@
} }
] ]
</script> </script>
<script>
// Make events accessible to calendar
window._axisEvents = JSON.parse(document.getElementById('events-data').textContent);
</script>
<!-- ===== Navbar ===== --> <!-- ===== Navbar ===== -->
<nav class="navbar"> <nav class="navbar">
@@ -171,6 +175,7 @@
<!-- ===== Calendar Widget ===== --> <!-- ===== Calendar Widget ===== -->
<div class="container"> <div class="container">
<div class="cal-widget" id="cal-widget"></div> <div class="cal-widget" id="cal-widget"></div>
<div class="cal-day-detail" id="cal-day-detail" style="display:none"></div>
</div> </div>
<!-- ===== Main Content ===== --> <!-- ===== Main Content ===== -->
@@ -197,11 +202,15 @@
/* ---- Calendar Widget ---- */ /* ---- Calendar Widget ---- */
(function () { (function () {
const calEl = document.getElementById('cal-widget'); const calEl = document.getElementById('cal-widget');
const detailEl = document.getElementById('cal-day-detail');
const MONTH_NAMES = ['January','February','March','April','May','June','July','August','September','October','November','December']; const MONTH_NAMES = ['January','February','March','April','May','June','July','August','September','October','November','December'];
const DAY_NAMES = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
const today = new Date(); const today = new Date();
let viewYear = today.getFullYear(); let viewYear = today.getFullYear();
let viewMonth = today.getMonth(); let viewMonth = today.getMonth();
let selectedKey = null; // "YYYY-M-D"
/* -- helpers -- */
function is2ndThursday(y, m, d) { function is2ndThursday(y, m, d) {
if (new Date(y, m, d).getDay() !== 4) return false; if (new Date(y, m, d).getDay() !== 4) return false;
let count = 0; let count = 0;
@@ -221,10 +230,100 @@
} }
function isBrunchDay(dow) { function isBrunchDay(dow) {
// Thu=4, Fri=5, Sat=6, Sun=0
return dow === 0 || dow === 4 || dow === 5 || dow === 6; return dow === 0 || dow === 4 || dow === 5 || dow === 6;
} }
/* -- build event list for a given day -- */
function getEventsForDay(y, m, d) {
const dow = new Date(y, m, d).getDay();
const dateStr = y + '-' + String(m + 1).padStart(2,'0') + '-' + String(d).padStart(2,'0');
const items = [];
// Brunch
if (isBrunchDay(dow)) {
items.push({ name: 'Brunch', time: '9 AM 3 PM', tag: 'Brunch', color: '#2a7a4a' });
}
// Trivia
if (dow === 2) {
items.push({ name: 'Trivia Night', time: '8 PM 10 PM', tag: 'Social', color: 'var(--gold)',
desc: 'Teams of up to 6. Prizes include bar tabs and Axis merchandise.' });
}
// Live Music
if (dow === 1 || dow === 5) {
items.push({ name: 'Live Music', time: '9 PM', tag: 'Live Music', color: '#e07020',
desc: 'Local and touring acts — folk, rock, soul, and jazz. No cover.' });
}
// Board Games
if (is2ndThursday(y, m, d)) {
items.push({ name: 'Board Games & Chess Night', time: '7 PM', tag: 'Social', color: '#8844cc',
desc: 'Classic board games, chess sets, and cold drinks. All skill levels welcome.' });
}
// One-off events from JSON
if (window._axisEvents) {
window._axisEvents.forEach(function(e) {
if (e.date === dateStr) {
// avoid duplicating recurring events already added above
const alreadyAdded = items.some(function(i) { return i.name === e.name; });
if (!alreadyAdded) {
items.push({ name: e.name, time: e.time, tag: e.tag, desc: e.description, color: '#c8922a' });
}
}
});
}
return items;
}
/* -- show day detail panel -- */
function showDayDetail(y, m, d) {
const key = y + '-' + m + '-' + d;
// Toggle off if same day clicked again
if (selectedKey === key) {
selectedKey = null;
detailEl.style.display = 'none';
render();
return;
}
selectedKey = key;
render(); // re-render to update selected highlight
const items = getEventsForDay(y, m, d);
const dow = new Date(y, m, d).getDay();
const label = DAY_NAMES[dow] + ', ' + MONTH_NAMES[m] + ' ' + d;
let inner = '<div class="cal-detail-header">';
inner += '<span class="cal-detail-title">' + label + '</span>';
inner += '<button class="cal-detail-close" onclick="closeDayDetail()" aria-label="Close">&times;</button>';
inner += '</div>';
if (items.length === 0) {
inner += '<p class="cal-detail-empty">No events scheduled for this day.</p>';
} else {
inner += '<ul class="cal-detail-list">';
items.forEach(function(item) {
inner += '<li class="cal-detail-item">';
inner += '<span class="cal-detail-dot" style="background:' + item.color + '"></span>';
inner += '<div class="cal-detail-info">';
inner += '<div class="cal-detail-name">' + escHtml(item.name) + '</div>';
if (item.time) inner += '<div class="cal-detail-time">' + escHtml(item.time) + '</div>';
if (item.desc) inner += '<div class="cal-detail-desc">' + escHtml(item.desc) + '</div>';
inner += '</div></li>';
});
inner += '</ul>';
}
detailEl.innerHTML = inner;
detailEl.style.display = 'block';
// Smooth scroll to detail panel
detailEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
window.closeDayDetail = function() {
selectedKey = null;
detailEl.style.display = 'none';
render();
};
/* -- render calendar -- */
function render() { function render() {
const firstDow = new Date(viewYear, viewMonth, 1).getDay(); const firstDow = new Date(viewYear, viewMonth, 1).getDay();
const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate(); const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
@@ -236,28 +335,28 @@
html += '</div>'; html += '</div>';
html += '<div class="cal-grid">'; html += '<div class="cal-grid">';
// Day-of-week headers
['Sun','Mon','Tue','Wed','Thu','Fri','Sat'].forEach(function (d) { ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'].forEach(function (d) {
html += '<div class="cal-dow-header">' + d + '</div>'; html += '<div class="cal-dow-header">' + d + '</div>';
}); });
// Empty leading cells
for (let i = 0; i < firstDow; i++) { for (let i = 0; i < firstDow; i++) {
html += '<div class="cal-cell cal-cell--empty"></div>'; html += '<div class="cal-cell cal-cell--empty"></div>';
} }
// Day cells
for (let d = 1; d <= daysInMonth; d++) { for (let d = 1; d <= daysInMonth; d++) {
const dow = new Date(viewYear, viewMonth, d).getDay(); const dow = new Date(viewYear, viewMonth, d).getDay();
const isToday = (today.getFullYear() === viewYear && today.getMonth() === viewMonth && today.getDate() === d); const isToday = (today.getFullYear() === viewYear && today.getMonth() === viewMonth && today.getDate() === d);
const isSelected = (selectedKey === viewYear + '-' + viewMonth + '-' + d);
const brunch = isBrunchDay(dow); const brunch = isBrunchDay(dow);
const dots = getDots(viewYear, viewMonth, d); const dots = getDots(viewYear, viewMonth, d);
let cls = 'cal-cell'; let cls = 'cal-cell cal-cell--clickable';
if (brunch) cls += ' cal-cell--brunch'; if (brunch) cls += ' cal-cell--brunch';
if (isToday) cls += ' cal-cell--today'; if (isToday) cls += ' cal-cell--today';
if (isSelected) cls += ' cal-cell--selected';
html += '<div class="' + cls + '">'; const vy = viewYear, vm = viewMonth, vd = d;
html += '<div class="' + cls + '" onclick="window._calClick(' + vy + ',' + vm + ',' + vd + ')">';
html += '<span class="cal-day-num">' + d + '</span>'; html += '<span class="cal-day-num">' + d + '</span>';
if (dots.length > 0) { if (dots.length > 0) {
html += '<div class="cal-dots">'; html += '<div class="cal-dots">';
@@ -269,14 +368,13 @@
html += '</div>'; html += '</div>';
} }
html += '</div>'; // .cal-grid html += '</div>';
// Legend
html += '<div class="cal-legend">'; html += '<div class="cal-legend">';
html += '<span class="cal-legend-item"><span class="cal-dot" style="background:var(--gold)"></span> Trivia Night (Tue)</span>'; html += '<span class="cal-legend-item"><span class="cal-dot" style="background:var(--gold)"></span> Trivia Night (Tue)</span>';
html += '<span class="cal-legend-item"><span class="cal-dot" style="background:#e07020"></span> Live Music (Mon &amp; Fri)</span>'; html += '<span class="cal-legend-item"><span class="cal-dot" style="background:#e07020"></span> Live Music (Mon &amp; Fri)</span>';
html += '<span class="cal-legend-item"><span class="cal-dot" style="background:#8844cc"></span> Board Games &amp; Chess (2nd Thu)</span>'; html += '<span class="cal-legend-item"><span class="cal-dot" style="background:#8844cc"></span> Board Games &amp; Chess (2nd Thu)</span>';
html += '<span class="cal-legend-item"><span class="cal-legend-brunch-swatch"></span> Brunch hours (ThuSun)</span>'; html += '<span class="cal-legend-item"><span class="cal-legend-brunch-swatch"></span> Brunch (ThuSun)</span>';
html += '</div>'; html += '</div>';
calEl.innerHTML = html; calEl.innerHTML = html;
@@ -284,15 +382,18 @@
document.getElementById('cal-prev').addEventListener('click', function () { document.getElementById('cal-prev').addEventListener('click', function () {
viewMonth--; viewMonth--;
if (viewMonth < 0) { viewMonth = 11; viewYear--; } if (viewMonth < 0) { viewMonth = 11; viewYear--; }
if (selectedKey) { selectedKey = null; detailEl.style.display = 'none'; }
render(); render();
}); });
document.getElementById('cal-next').addEventListener('click', function () { document.getElementById('cal-next').addEventListener('click', function () {
viewMonth++; viewMonth++;
if (viewMonth > 11) { viewMonth = 0; viewYear++; } if (viewMonth > 11) { viewMonth = 0; viewYear++; }
if (selectedKey) { selectedKey = null; detailEl.style.display = 'none'; }
render(); render();
}); });
} }
window._calClick = showDayDetail;
render(); render();
})(); })();

111
style.css
View File

@@ -974,3 +974,114 @@ footer span {
font-size: 0.72rem; font-size: 0.72rem;
} }
} }
/* ---------- Calendar Day Click ---------- */
.cal-cell--clickable {
cursor: pointer;
transition: background 0.15s, box-shadow 0.15s;
}
.cal-cell--clickable:hover {
background: rgba(200, 146, 42, 0.1);
}
.cal-cell--selected {
background: rgba(200, 146, 42, 0.18) !important;
box-shadow: inset 0 0 0 2px var(--gold);
}
/* ---------- Day Detail Panel ---------- */
.cal-day-detail {
background: var(--bg-card);
border: 1px solid var(--gold-dim);
border-top: 3px solid var(--gold);
border-radius: 4px;
padding: 1.5rem;
margin-top: 1rem;
animation: slideDown 0.2s ease;
}
@keyframes slideDown {
from { opacity: 0; transform: translateY(-8px); }
to { opacity: 1; transform: translateY(0); }
}
.cal-detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.25rem;
}
.cal-detail-title {
font-family: var(--font-display);
font-size: 1.1rem;
color: var(--gold);
letter-spacing: 0.05em;
}
.cal-detail-close {
background: none;
border: none;
color: var(--text-muted);
font-size: 1.4rem;
cursor: pointer;
line-height: 1;
padding: 0 4px;
transition: color 0.2s;
}
.cal-detail-close:hover {
color: var(--text);
}
.cal-detail-list {
list-style: none;
display: flex;
flex-direction: column;
gap: 1rem;
}
.cal-detail-item {
display: flex;
align-items: flex-start;
gap: 0.75rem;
}
.cal-detail-dot {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
margin-top: 4px;
}
.cal-detail-info {
flex: 1;
}
.cal-detail-name {
font-weight: 600;
font-size: 0.95rem;
color: var(--text);
}
.cal-detail-time {
font-size: 0.8rem;
color: var(--gold);
margin-top: 2px;
}
.cal-detail-desc {
font-size: 0.8rem;
color: var(--text-muted);
margin-top: 4px;
line-height: 1.45;
}
.cal-detail-empty {
color: var(--text-muted);
font-size: 0.875rem;
text-align: center;
padding: 1rem 0;
}