Skip to content
Open
16 changes: 8 additions & 8 deletions frontend/about.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ title: About Us
<figure>
<a href="{{ '/assets/pictures/standard-4-player-chess.jpg' | url }}">
<img
class="w-full h-auto rounded-lg object-contain"
class="w-full h-auto rounded-lg object-contain modal-image"
src="{{ '/assets/pictures/standard-4-player-chess.jpg' | url }}"
alt="Standard 4 Player Chess" />
</a>
Expand All @@ -467,7 +467,7 @@ title: About Us
<figure>
<a href="{{ '/assets/pictures/harmegedo-the-board-of-lords-at-the-garden-room.jpg' | url }}">
<img
class="w-full h-auto rounded-lg object-contain"
class="w-full h-auto rounded-lg object-contain modal-image"
src="{{ '/assets/pictures/harmegedo-the-board-of-lords-at-the-garden-room.jpg' | url }}"
alt="Harmegedo The Board of Lords at the Garden Room"
/>
Expand All @@ -480,14 +480,14 @@ title: About Us
<div class="flex gap-4">
<a href="{{ '/assets/pictures/never-underestimate-the-power-of-jedi-master-big-bird-1.jpg' | url }}">
<img
class="w-full h-auto rounded-lg object-contain"
class="w-full h-auto rounded-lg object-contain modal-image"
src="{{ '/assets/pictures/never-underestimate-the-power-of-jedi-master-big-bird-1.jpg' | url }}"
alt="A yellow character resembling Big Bird dressed as a Jedi Master, looking at a chess board."
/>
</a>
<a href="{{ '/assets/pictures/never-underestimate-the-power-of-jedi-master-big-bird-2.jpg' | url }}">
<img
class="w-full h-auto rounded-lg object-contain"
class="w-full h-auto rounded-lg object-contain modal-image"
src="{{ '/assets/pictures/never-underestimate-the-power-of-jedi-master-big-bird-2.jpg' | url }}"
alt="Another view of Jedi Master Big Bird playing chess."
/>
Expand All @@ -500,25 +500,25 @@ title: About Us
<div class="grid grid-cols-2 gap-4">
<a href="{{ '/assets/pictures/quaternity-1.png' | url }}">
<img
class="w-full h-auto rounded-lg object-contain"
class="w-full h-auto rounded-lg object-contain modal-image"
src="{{ '/assets/pictures/quaternity-1.png' | url }}" alt="Quaternity board with pieces in starting position."
/>
</a>
<a href="{{ '/assets/pictures/quaternity-2.png' | url }}">
<img
class="w-full h-auto rounded-lg object-contain"
class="w-full h-auto rounded-lg object-contain modal-image"
src="{{ '/assets/pictures/quaternity-2.png' | url }}" alt="Close-up of Quaternity game in progress."
/>
</a>
<a href="{{ '/assets/pictures/quaternity-3.png' | url }}">
<img
class="w-full h-auto rounded-lg object-contain"
class="w-full h-auto rounded-lg object-contain modal-image"
src="{{ '/assets/pictures/quaternity-3.png' | url }}" alt="A different view of a Quaternity game in progress."
/>
</a>
<a href="{{ '/assets/pictures/quaternity-4.png' | url }}">
<img
class="w-full h-auto rounded-lg object-contain"
class="w-full h-auto rounded-lg object-contain modal-image"
src="{{ '/assets/pictures/quaternity-4.png' | url }}" alt="The Quaternity game box and components."
/>
</a>
Expand Down
105 changes: 105 additions & 0 deletions frontend/assets/scripts/image.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// ===========================================
// Image Modal (Scoped in IIFE to avoid conflicts)
// ===========================================
(() => {
/**
* Creates and opens an accessible image modal.
* @param {HTMLImageElement} imgEl - The clicked image element.
*/
function openModal(imgEl) {
// Save the previously focused element
const previousActiveElement = document.activeElement;

// 1. Overlay
const overlay = document.createElement('div');
overlay.className = 'fixed inset-0 bg-black/85 flex items-center justify-center z-[9999]';
overlay.setAttribute('role', 'dialog');
overlay.setAttribute('aria-modal', 'true');
overlay.setAttribute('aria-label', imgEl.alt || 'Image viewer');

// 2. Container
const modalContainer = document.createElement('div');
modalContainer.className = 'relative flex items-center justify-center h-[80%] w-full p-4';

// 3. Image
const modalImage = document.createElement('img');
modalImage.src = imgEl.src;
modalImage.alt = imgEl.alt;
modalImage.className = 'max-w-full max-h-full object-contain rounded-2xl shadow-[0_4px_25px_rgba(0,0,0,0.4)] animate-fade-in';

// 4. Close Button
const closeButton = document.createElement('button');
closeButton.className =
'absolute top-4 right-6 text-white text-3xl bg-transparent border-none cursor-pointer transition-transform duration-200 hover:scale-125';
closeButton.setAttribute('aria-label', 'Close image viewer');
closeButton.textContent = '×';

// --- Assemble and Append ---
modalContainer.append(modalImage, closeButton);
overlay.appendChild(modalContainer);
document.body.appendChild(overlay);

// Disable body scroll while modal is active
document.body.style.overflow = 'hidden';

// --- Focus Handling ---
closeButton.focus();

/**
* Close modal and restore focus + scroll
*/
function closeModal() {
document.body.style.overflow = ''; // restore scroll
overlay.remove();
document.removeEventListener('keydown', keyHandler);
if (previousActiveElement) previousActiveElement.focus(); // return focus
}

// --- Event Listeners ---
closeButton.addEventListener('click', closeModal);

// Simplified backdrop close
overlay.addEventListener('click', (e) => {
if (e.target === overlay) closeModal();
});

/**
* Handles ESC + focus trapping
* @param {KeyboardEvent} e
*/
function keyHandler(e) {
if (e.key === 'Escape') {
closeModal();
return;
}

// Focus trap: keep tabbing inside modal
if (e.key === 'Tab') {
const focusableEls = overlay.querySelectorAll('button, [tabindex]:not([tabindex="-1"])');
const focusable = Array.from(focusableEls);
if (!focusable.length) return;

const firstEl = focusable[0];
const lastEl = focusable[focusable.length - 1];

if (e.shiftKey && document.activeElement === firstEl) {
e.preventDefault();
lastEl.focus();
} else if (!e.shiftKey && document.activeElement === lastEl) {
e.preventDefault();
firstEl.focus();
}
}
}
document.addEventListener('keydown', keyHandler);
}

// --- Global Setup (Event Delegation) ---
document.addEventListener('click', (e) => {
if (e.target.matches('img.modal-image')) {
const link = e.target.closest('a');
if (link) e.preventDefault(); // prevent navigation
openModal(e.target);
}
});
})();
5 changes: 5 additions & 0 deletions frontend/assets/styles/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@
--footer-copyright-color: var(--text-color);
}

/* Modal image cursor */
.modal-image {
cursor: zoom-in;
}

/* Theme */
.theme-toggle {
display: flex;
Expand Down
15 changes: 14 additions & 1 deletion frontend/assets/tailwind.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--team-role-border: 2px solid;
}
Expand All @@ -24,4 +23,18 @@
--color-team-role-event-organizers: lime;
--color-team-role-assistant-organizers: #c35339;
--team-role-shadow-rgb: 255, 255, 255; /* default for dark theme */

--animate-fade-in: fade-in 0.3s ease-in-out;

@keyframes fade-in {
from {
opacity: 0;
scale: 0.8;
}
to {
opacity: 1;
scale: 1;
}

}
}
6 changes: 3 additions & 3 deletions frontend/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,13 @@ title: Play, Learn & Compete in Brisbane
</tbody>
</table>
</div>
<img src="{{ '/assets/images/locations.jpg' | url }}" alt="Chess Meetup" class="mx-auto rounded-lg w-full md:w-3/4" />
<img src="{{ '/assets/images/locations.jpg' | url }}" alt="Chess Meetup" class="mx-auto rounded-lg w-full md:w-3/4 modal-image" />
</section>

<section class="px-4 max-w-3xl text-center">
<h2 class="text-xl md:text-2xl font-semibold mb-4">A Dedicated Team of Organisers</h2>
<p class="mb-6">We are a registered community organisation in Queensland with a committee. Ready to contribute? We would like to hear from you.</p>
<img src="{{ '/assets/images/organisers.jpg' | url }}" alt="Organisers" class="mx-auto rounded-lg w-full md:w-3/4" />
<img src="{{ '/assets/images/organisers.jpg' | url }}" alt="Organisers" class="mx-auto rounded-lg w-full md:w-3/4 modal-image" />
</section>

<section class="px-4 max-w-3xl text-center">
Expand All @@ -151,5 +151,5 @@ title: Play, Learn & Compete in Brisbane
<a href="https://worldchess.com/community/bsc" class="px-6 py-3 bg-indigo-900 hover:bg-indigo-500 rounded-full font-bold shadow-md transition">FIDE Online Arena</a>
<a href="https://lichess.org/team/brisbane-social-chess" class="px-6 py-3 bg-indigo-900 hover:bg-indigo-500 rounded-full font-bold shadow-md transition">Lichess</a>
</div>
<img src="{{ '/assets/images/background-smaller.jpg' | url }}" alt="Play Chess" class="mx-auto rounded-lg w-full md:w-3/4" />
<img src="{{ '/assets/images/background-smaller.jpg' | url }}" alt="Play Chess" class="mx-auto rounded-lg w-full md:w-3/4 modal-image" />
</section>
2 changes: 1 addition & 1 deletion merge-assets.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const mergedJsFile = path.relative(process.cwd(), path.join(jsDir, 'bundle.js'))

// --- Include arrays ---
const includeCss = ['base.css', 'gh-fork-ribbon.css', 'custom.css'];
const includeJs = ['script.js'];
const includeJs = ['script.js','image.js'];

// --- Merge CSS ---
const cssFiles = includeCss
Expand Down