Files
Bar-menu-and-calendar-events/events.html
Hans Heinemann 9ab0151f55 feat: click calendar day to show events detail panel
- Clicking a day highlights it and shows a panel below the calendar
- Panel lists all events for that day (recurring + one-off + brunch)
- Click same day again or X button to dismiss
- Navigating month clears selection
2026-03-17 14:35:15 -04:00

474 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Axis — Events</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- ===================================================
ADMIN: Edit the events array below to update the
events page. Each object supports:
name (string) — event title
date (string) — "YYYY-MM-DD"
time (string) — display string e.g. "9 PM"
description (string) — short blurb
tag (string) — optional label e.g. "Live Music"
==================================================== -->
<script id="events-data" type="application/json">
[
{
"name": "Trivia Night",
"date": "2026-03-17",
"time": "8 PM 10 PM",
"description": "Teams of up to 6. Prizes include bar tabs and Axis merchandise. Arrive early to grab a table.",
"tag": "Social"
},
{
"name": "Live Music",
"date": "2026-03-20",
"time": "9 PM",
"description": "Local and touring acts across folk, rock, soul, and jazz. No cover charge, ever.",
"tag": "Live Music"
},
{
"name": "Trivia Night",
"date": "2026-03-24",
"time": "8 PM 10 PM",
"description": "Teams of up to 6. Prizes include bar tabs and Axis merchandise. Arrive early to grab a table.",
"tag": "Social"
},
{
"name": "Live Music",
"date": "2026-03-27",
"time": "9 PM",
"description": "Local and touring acts across folk, rock, soul, and jazz. No cover charge, ever.",
"tag": "Live Music"
},
{
"name": "Live Music",
"date": "2026-03-30",
"time": "9 PM",
"description": "Local and touring acts across folk, rock, soul, and jazz. No cover charge, ever.",
"tag": "Live Music"
},
{
"name": "Trivia Night",
"date": "2026-03-31",
"time": "8 PM 10 PM",
"description": "Teams of up to 6. Prizes include bar tabs and Axis merchandise. Arrive early to grab a table.",
"tag": "Social"
},
{
"name": "Live Music",
"date": "2026-04-03",
"time": "9 PM",
"description": "Local and touring acts across folk, rock, soul, and jazz. No cover charge, ever.",
"tag": "Live Music"
},
{
"name": "Trivia Night",
"date": "2026-04-07",
"time": "8 PM 10 PM",
"description": "Teams of up to 6. Prizes include bar tabs and Axis merchandise. Arrive early to grab a table.",
"tag": "Social"
},
{
"name": "Board Games & Chess Night",
"date": "2026-04-09",
"time": "7 PM",
"description": "The 2nd Thursday of every month. Bring your A-game. Classic board games, chess sets, and cold drinks. All skill levels welcome.",
"tag": "Social"
},
{
"name": "Live Music",
"date": "2026-04-10",
"time": "9 PM",
"description": "Local and touring acts across folk, rock, soul, and jazz. No cover charge, ever.",
"tag": "Live Music"
},
{
"name": "Trivia Night",
"date": "2026-04-14",
"time": "8 PM 10 PM",
"description": "Teams of up to 6. Prizes include bar tabs and Axis merchandise. Arrive early to grab a table.",
"tag": "Social"
},
{
"name": "Live Music",
"date": "2026-04-17",
"time": "9 PM",
"description": "Local and touring acts across folk, rock, soul, and jazz. No cover charge, ever.",
"tag": "Live Music"
},
{
"name": "Live Music",
"date": "2026-04-20",
"time": "9 PM",
"description": "Local and touring acts across folk, rock, soul, and jazz. No cover charge, ever.",
"tag": "Live Music"
},
{
"name": "Trivia Night",
"date": "2026-04-21",
"time": "8 PM 10 PM",
"description": "Teams of up to 6. Prizes include bar tabs and Axis merchandise. Arrive early to grab a table.",
"tag": "Social"
},
{
"name": "Live Music",
"date": "2026-04-24",
"time": "9 PM",
"description": "Local and touring acts across folk, rock, soul, and jazz. No cover charge, ever.",
"tag": "Live Music"
},
{
"name": "Trivia Night",
"date": "2026-04-28",
"time": "8 PM 10 PM",
"description": "Teams of up to 6. Prizes include bar tabs and Axis merchandise. Arrive early to grab a table.",
"tag": "Social"
},
{
"name": "Live Music",
"date": "2026-04-27",
"time": "9 PM",
"description": "Local and touring acts across folk, rock, soul, and jazz. No cover charge, ever.",
"tag": "Live Music"
},
{
"name": "Board Games & Chess Night",
"date": "2026-05-14",
"time": "7 PM",
"description": "The 2nd Thursday of every month. Bring your A-game. Classic board games, chess sets, and cold drinks. All skill levels welcome.",
"tag": "Social"
}
]
</script>
<script>
// Make events accessible to calendar
window._axisEvents = JSON.parse(document.getElementById('events-data').textContent);
</script>
<!-- ===== Navbar ===== -->
<nav class="navbar">
<a href="index.html" class="navbar__brand">Axis</a>
<button class="navbar__toggle" aria-label="Toggle menu" onclick="toggleNav()">
<span></span><span></span><span></span>
</button>
<ul class="navbar__links" id="nav-links">
<li><a href="index.html">Home</a></li>
<li><a href="menu.html">Menu</a></li>
<li><a href="events.html" class="active">Events</a></li>
</ul>
</nav>
<!-- ===== Hero ===== -->
<header class="page-hero">
<p class="page-hero__eyebrow">What's On</p>
<h1 class="page-hero__title">Upcoming Events</h1>
<p class="page-hero__sub">Live music &middot; Trivia &middot; Board games &middot; Special nights</p>
</header>
<!-- ===== Calendar Widget ===== -->
<div class="container">
<div class="cal-widget" id="cal-widget"></div>
<div class="cal-day-detail" id="cal-day-detail" style="display:none"></div>
</div>
<!-- ===== Main Content ===== -->
<main>
<div class="container">
<div class="events-grid" id="events-container">
<!-- Populated by JS below -->
</div>
</div>
</main>
<!-- ===== Footer ===== -->
<footer>
<p><span>Axis</span> &mdash; 3048 Dundas Street West &mdash; Open TueSun 5pm2am &mdash; Brunch ThuSun 9am3pm</p>
<p style="margin-top:0.4rem;">Events subject to change. Follow us for updates.</p>
</footer>
<script>
/* ---- Mobile nav ---- */
function toggleNav() {
document.getElementById('nav-links').classList.toggle('open');
}
/* ---- Calendar Widget ---- */
(function () {
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 DAY_NAMES = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
const today = new Date();
let viewYear = today.getFullYear();
let viewMonth = today.getMonth();
let selectedKey = null; // "YYYY-M-D"
/* -- helpers -- */
function is2ndThursday(y, m, d) {
if (new Date(y, m, d).getDay() !== 4) return false;
let count = 0;
for (let i = 1; i <= d; i++) {
if (new Date(y, m, i).getDay() === 4) count++;
}
return count === 2;
}
function getDots(y, m, d) {
const dow = new Date(y, m, d).getDay();
const dots = [];
if (dow === 2) dots.push({ color: 'var(--gold)', label: 'Trivia Night' });
if (dow === 1 || dow === 5) dots.push({ color: '#e07020', label: 'Live Music' });
if (is2ndThursday(y, m, d)) dots.push({ color: '#8844cc', label: 'Board Games & Chess Night' });
return dots;
}
function isBrunchDay(dow) {
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() {
const firstDow = new Date(viewYear, viewMonth, 1).getDay();
const daysInMonth = new Date(viewYear, viewMonth + 1, 0).getDate();
let html = '<div class="cal-header">';
html += '<button class="cal-nav-btn" id="cal-prev">&#8249;</button>';
html += '<span class="cal-month-label">' + MONTH_NAMES[viewMonth] + ' ' + viewYear + '</span>';
html += '<button class="cal-nav-btn" id="cal-next">&#8250;</button>';
html += '</div>';
html += '<div class="cal-grid">';
['Sun','Mon','Tue','Wed','Thu','Fri','Sat'].forEach(function (d) {
html += '<div class="cal-dow-header">' + d + '</div>';
});
for (let i = 0; i < firstDow; i++) {
html += '<div class="cal-cell cal-cell--empty"></div>';
}
for (let d = 1; d <= daysInMonth; d++) {
const dow = new Date(viewYear, viewMonth, d).getDay();
const isToday = (today.getFullYear() === viewYear && today.getMonth() === viewMonth && today.getDate() === d);
const isSelected = (selectedKey === viewYear + '-' + viewMonth + '-' + d);
const brunch = isBrunchDay(dow);
const dots = getDots(viewYear, viewMonth, d);
let cls = 'cal-cell cal-cell--clickable';
if (brunch) cls += ' cal-cell--brunch';
if (isToday) cls += ' cal-cell--today';
if (isSelected) cls += ' cal-cell--selected';
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>';
if (dots.length > 0) {
html += '<div class="cal-dots">';
dots.forEach(function (dot) {
html += '<span class="cal-dot" style="background:' + dot.color + '" title="' + dot.label + '"></span>';
});
html += '</div>';
}
html += '</div>';
}
html += '</div>';
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:#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-legend-brunch-swatch"></span> Brunch (ThuSun)</span>';
html += '</div>';
calEl.innerHTML = html;
document.getElementById('cal-prev').addEventListener('click', function () {
viewMonth--;
if (viewMonth < 0) { viewMonth = 11; viewYear--; }
if (selectedKey) { selectedKey = null; detailEl.style.display = 'none'; }
render();
});
document.getElementById('cal-next').addEventListener('click', function () {
viewMonth++;
if (viewMonth > 11) { viewMonth = 0; viewYear++; }
if (selectedKey) { selectedKey = null; detailEl.style.display = 'none'; }
render();
});
}
window._calClick = showDayDetail;
render();
})();
/* ---- Render events ---- */
(function () {
const DAYS = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
const raw = document.getElementById('events-data').textContent;
const events = JSON.parse(raw);
const container = document.getElementById('events-container');
if (!events || events.length === 0) {
container.innerHTML = '<p class="no-events">No upcoming events at this time.<br>Check back soon.</p>';
return;
}
events.sort((a, b) => new Date(a.date) - new Date(b.date));
const today = new Date();
today.setHours(0, 0, 0, 0);
const upcoming = events.filter(e => {
const [y, m, d] = e.date.split('-').map(Number);
const dt = new Date(y, m - 1, d);
return dt >= today;
});
if (upcoming.length === 0) {
container.innerHTML = '<p class="no-events">No upcoming events at this time.<br>Check back soon.</p>';
return;
}
upcoming.forEach(event => {
const [y, m, d] = event.date.split('-').map(Number);
const dt = new Date(y, m - 1, d);
const mon = MONTHS[dt.getMonth()];
const day = dt.getDate();
const dow = DAYS[dt.getDay()];
const tagHTML = event.tag
? `<span class="event-card__tag">${escHtml(event.tag)}</span>`
: '';
const card = document.createElement('article');
card.className = 'event-card';
card.innerHTML = `
<div class="event-card__date-block">
<div class="event-card__cal">
<div class="event-card__cal-month">${mon}</div>
<div class="event-card__cal-day">${day}</div>
</div>
<div class="event-card__datetime">
<div class="event-card__dow">${dow}</div>
<div class="event-card__time">${escHtml(event.time)}</div>
</div>
</div>
<h2 class="event-card__name">${escHtml(event.name)}</h2>
<p class="event-card__desc">${escHtml(event.description)}</p>
${tagHTML}
`;
container.appendChild(card);
});
})();
function escHtml(str) {
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
</script>
</body>
</html>