Skip to content

Remove jQuery and replace lightbox with vanilla JS (Phase 1: Security & Performance)#158

Open
lordfolken wants to merge 133 commits intomasterfrom
modernize/remove-jquery-replace-lightbox
Open

Remove jQuery and replace lightbox with vanilla JS (Phase 1: Security & Performance)#158
lordfolken wants to merge 133 commits intomasterfrom
modernize/remove-jquery-replace-lightbox

Conversation

@lordfolken
Copy link
Member

@lordfolken lordfolken commented Jan 25, 2026

Summary

This PR removes the outdated jQuery 1.8.2 dependency and replaces the jQuery lightbox with a modern vanilla JavaScript implementation.

Changes

  • ✅ Removed jQuery 1.8.2 (12+ years old, security vulnerabilities)
  • ✅ Removed jQuery lightbox plugin
  • ✅ Added modern vanilla JS lightbox (no dependencies)
  • ✅ Updated all pages to use new lightbox
  • ✅ Removed unnecessary jQuery flags from pages that didn't use it

Benefits

  • Security: Removes outdated library with known vulnerabilities
  • Performance: ~93% file size reduction (90KB → 6KB)
  • Modern: ES6+ JavaScript, no dependencies
  • Accessible: ARIA labels, keyboard navigation
  • Mobile-friendly: Touch support, responsive design

Features

The new lightbox includes:

  • Keyboard navigation (Arrow keys, Escape)
  • Touch-friendly mobile support
  • Accessible (ARIA labels)
  • Image counter and captions
  • Smooth animations

Testing

  • ✅ Jekyll build successful
  • ✅ All pages compile correctly
  • ✅ Lightbox works on homepage and screenshots page

Related Issues

Fixes #42 - Replace jquery lightbox with generic lightbox

Phase 1 Progress

This is part of Phase 1: Critical Security & Performance improvements:

  • Remove jQuery dependency
  • Replace lightbox plugin
  • Add security headers (next)
  • Optimize images (next)

File Size Comparison

  • Before: jQuery (90KB) + Lightbox plugin (~10KB) = ~100KB
  • After: Lightbox JS (4KB) + CSS (2KB) = ~6KB
  • Reduction: ~94KB saved per page load

Summary by CodeRabbit

  • Security

    • Added site security headers and server guidance to strengthen protection.
  • New Features

    • Native lightbox, screenshot carousel, lazy-loading, responsive navbar, and modernized layouts replacing legacy scripts.
  • Bug Fixes

    • Typo fixes and CI/runtime version bump.
  • Documentation

    • Added website structure, modernization roadmap, security headers guide, hosting/local-resources and HTML5-first rules; moved/manualized docs and removed outdated pages.

✏️ Tip: You can customize this high-level summary in your review settings.

Philipp Wollschlegel added 4 commits January 25, 2026 19:48
…testing/

Fixes issue #61 - replaces broken Play Store and nightly_builds links
with the correct testing build URL.
- Update GitHub Actions workflow to use Ruby 4.0.1
- Update README Docker example to use Ruby 4.0.1
- Replace deprecated File.exists? with File.exist? in svgconvert plugin

This addresses Ruby version inconsistencies and prepares for Ruby 4 compatibility.
Ruby 4.0.1 requires updated dependencies (ffi gem doesn't support Ruby 4 yet).
Using Ruby 3.4.8 which is compatible with current Gemfile.lock while still
upgrading from 3.4.7.
@coderabbitai
Copy link

coderabbitai bot commented Jan 25, 2026

📝 Walkthrough

Walkthrough

Replaces the jQuery-based lightbox/carousel with vanilla JS implementations, modernizes layout and CSS classes to Bootstrap 5 patterns, adds security and modernization docs, updates CI/Ruby version references, and removes or consolidates several legacy documentation pages and front-matter flags.

Changes

Cohort / File(s) Summary
Lightbox & Carousel
lib/lightbox.js, lib/screenshot-carousel.js, css/lightbox.css, discover/screenshots/index.html, index.html, discover/screenshots/*
Adds vanilla JS lightbox and carousel; new lightbox CSS; updates screenshots pages and index to use semantic figure/figcaption, lazy loading, and new selectors; removes jQuery-lightbox usage and inline jQuery init.
Header / Navigation / Layout
_includes/header.html, _includes/discover_menu.html, _includes/download_sidebar.html, _includes/footer.html, _includes/section_change.html, _layouts/*.html, discover/news.html
Replaces legacy grid/markup (span*) with semantic HTML and modern col-/float- classes; introduces a Bootstrap-style navbar; updates footer, sidebars, and layout wrappers.
CSS baseline & carousel styles
css/xcsoar.css, css/lightbox.css
Large rewrite to a new baseline (fonts, headings, navbar classes) and extensive carousel/gallery styles; replaces many id-based rules with class-based patterns.
Documentation & Policies
IMPROVEMENTS.md, WEBSITE_STRUCTURE.md, SECURITY_HEADERS.md, HTML5_ANALYSIS.md, .htaccess, .cursor/rules/*
Adds modernization roadmap, site structure docs, security headers guidance and server snippets, HTML5 analysis, and multiple style/policy rule documents (hosting resources locally, HTML5-first, responsive, reuse, relative links).
CI, README & Config
.github/workflows/website-deploy.yml, README.md, _config.yml
Bumps Ruby version references (3.4.7 → 3.4.8) in workflow and Docker example; renames config key trac_server_urlgithub_issues_url; fixes README typo.
Content removals & updates
develop/index.md, contact/index.md, contact/irc.html, develop/*.md (several), discover/manual.md, hardware/index.md, multiple _posts/*.md
Deletes or condenses several documentation pages (contact, develop/donations, infrastructure, new_ticket, manual) and updates historical posts to point bug/report links to site.github_issues_url.
Small fixes & refactors
_plugins/svgconvert.rb, _includes/*, download/maps/index.html, download/maps/ommaps.html, download/waypoints/index.html, _posts/2024-09-02-xcsoar-7-dot-43-released.md
Replaces deprecated Ruby API File.exists?File.exist?; fixes typos; removes jquery: true front-matter where not needed; adjusts script paths.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

"🐇
I hopped from jQuery to nimble plain JS,
Figures and captions now take the breeze,
Docs grew wise, headers keep foes at bay,
Ruby bumped, old markup cleared away,
A carrot-cheer for modern, tidy trees."

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes modernization efforts beyond the linked issue scope, such as adding security headers, upgrading Bootstrap, implementing HTML5 semantics, updating documentation, and various other refactoring tasks. Scope creep detected: Phase 1 should focus on jQuery removal and lightbox replacement; consider deferring security headers, Bootstrap upgrades, HTML5 refactoring, and documentation additions to separate PRs.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title accurately describes the main change: removing jQuery and replacing the lightbox with vanilla JS, which is the primary objective of this PR.
Linked Issues check ✅ Passed The PR successfully implements the core requirement from Issue #42: removing the jQuery lightbox dependency and replacing it with a vanilla JavaScript lightbox for screenshots.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@css/lightbox.css`:
- Around line 54-80: Add a visible keyboard focus indicator for the lightbox
controls (.lightbox-close, .lightbox-prev, .lightbox-next) by defining a
:focus-visible rule that contrasts with the dark background (for example a white
or accent-colored outline or ring and increased box-shadow), ensure the outline
is not removed elsewhere so it appears only for keyboard focus (use
:focus-visible rather than :focus), and keep the existing sizing/positioning so
the focus ring does not shift layout.

In `@lib/lightbox.js`:
- Around line 51-62: The image loader lacks error handling: add an img.onerror
handler alongside img.onload in the lightbox code (the Image instance `img`) to
handle failed loads for `images[index]`; in the handler set a fallback state
(e.g., set `image.src` to a placeholder or clear it, set `image.alt` and
`caption.textContent` to an error message like "Image failed to load", update
`counter.textContent` appropriately, and adjust `prevBtn`/`nextBtn` visibility)
so the lightbox never stays blank when a network/404 error occurs.
- Around line 28-37: initLightbox currently inserts the lightboxHTML
unconditionally (document.body.insertAdjacentHTML) so calling initLightbox twice
creates duplicate overlays; fix by first checking for the existing lightbox
container (e.g., document.getElementById('lightbox-overlay') or another unique
id/class used by lightboxHTML) inside initLightbox and skip insertion if
present, or alternatively remove multiple init calls by combining selectors into
a single initLightbox call; update references in initLightbox and the
DOMContentLoaded/bootstrap area (the places that call initLightbox) to use the
chosen approach.
🧹 Nitpick comments (4)
WEBSITE_STRUCTURE.md (1)

15-15: Add language specifier to fenced code blocks.

Per markdownlint, fenced code blocks should have a language specified for proper syntax highlighting.

Suggested fix
-```
+```text
 website/

The same applies to the navigation structure block at line 248.

lib/lightbox.js (1)

103-118: Keyboard event listener is global and never removed.

The keydown listener is attached to document and persists for the page lifetime. While this works, it processes key events even when the lightbox is closed (returning early after the display check). Consider attaching when opened and removing when closed for cleaner resource management.

_includes/header.html (1)

73-75: Consider renaming page.jquery to reflect its actual purpose.

The front matter variable page.jquery now controls lightbox loading rather than jQuery loading. This naming is misleading after removing jQuery. Consider renaming to something like page.lightbox for clarity.

This would require updating the front matter in pages that use the lightbox (e.g., index.html, discover/screenshots/index.html).

Also applies to: 83-85

IMPROVEMENTS.md (1)

195-202: Note: Implementation differs from recommendation (which is fine).

The document recommends GLightbox CDN, but the PR implements a custom vanilla JS solution instead. This is actually a good choice - it avoids external CDN dependency and keeps the implementation lightweight (~6KB vs external library). Consider updating this section to reflect the chosen approach.

Suggested update
 ### Replace jQuery Lightbox
 **Issue**: Issue `#42` already tracks this
 **Solution**:
-```html
-<!-- Replace with modern solution -->
-<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/glightbox/dist/css/glightbox.min.css">
-<script src="https://cdn.jsdelivr.net/npm/glightbox/dist/js/glightbox.min.js"></script>
-```
+Implemented with custom vanilla JS lightbox (`/lib/lightbox.js` and `/css/lightbox.css`) - no external dependencies, ~6KB total.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @.htaccess:
- Line 6: The commented .htaccess CSP header is out-of-sync and misleading
because the active policy is set in the Content-Security-Policy meta tag inside
_includes/header.html; remove or update the commented header line in .htaccess
to avoid confusion and then make the CSP in _includes/header.html authoritative
(keep it in one place). If you want to tighten CSP, start by externalizing
inline styles/scripts referenced by the site (phpBB templates, download page map
styles, and main site element styling) and then remove 'unsafe-inline' from the
meta tag's script-src and style-src directives in _includes/header.html, testing
each change to ensure functionality remains.
🧹 Nitpick comments (2)
.htaccess (2)

14-15: Remove or replace the commented X-XSS-Protection header. The header is deprecated and ignored by modern browsers; OWASP recommends relying on CSP instead. Either delete the line entirely or replace it with an explicit X-XSS-Protection: 0 header for clarity if you want to document the decision to disable it.


5-24: Prefer Header always set to cover error responses.

Without always, headers are applied only to successful 2xx responses. For security headers (CSP, MIME sniffing prevention, clickjacking protection, XSS protection, Referrer Policy, Permissions Policy, and HSTS), using Header always set ensures they are present even on 4xx/5xx error responses, preventing temporary security gaps on error pages.

♻️ Suggested change (apply to all header lines)
-# Header set Content-Security-Policy "default-src 'self'; ..."
+# Header always set Content-Security-Policy "default-src 'self'; ..."

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@_includes/header.html`:
- Around line 58-64: The meta-based X-* headers in _includes/header.html (the
meta http-equiv tags for X-Content-Type-Options, X-Frame-Options, and
X-XSS-Protection) give a false sense of protection because browsers ignore them
when set via meta; fix by moving these headers to server-level configuration:
uncomment and enable the equivalent directives in .htaccess (set
X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN or proper CSP
frame-ancestors, and remove/replace X-XSS-Protection with a CSP fallback) so
server emits them consistently, and update SECURITY_HEADERS.md to reflect the
enabled server headers; alternatively, if you cannot enable server headers,
remove the meta http-equiv lines from _includes/header.html and document that
only CSP remains via meta in SECURITY_HEADERS.md.

In `@_layouts/trac.html`:
- Around line 14-18: Replace the jQuery DOM manipulation in the ready handler
with vanilla DOM APIs: select the footer element using the selector '#primary
`#footer`', remove it from its parent (preserve the node), then append that node
into the target container selected by '#secondary .row .col-12'; also select the
header via '#primary `#content` `#banner` `#header`' and hide it by setting its style
(e.g., display = "none"). Ensure this runs after DOMContentLoaded (use
DOMContentLoaded event instead of jQuery's ready) and handle missing elements by
checking for null before removing/appending or changing style.

In `@IMPROVEMENTS.md`:
- Line 22: Update the wording in IMPROVEMENTS.md to remove weak/redundant
phrases: replace "Bootstrap 2.x (very old, likely custom build)" with a more
precise phrase such as "Bootstrap 2.x (legacy, likely custom build)" and change
any instances of "SEO Optimization" to "SEO" (remove redundant "Optimization");
apply these edits at the occurrences referenced (the line shown plus the other
occurrences noted around lines 103 and 297) so the language is tighter and
non-redundant.
🧹 Nitpick comments (2)
css/lightbox.css (1)

108-112: Redundant selector — inline display: none already hides the element.

This CSS rule targets elements that already have display: none set via inline styles, making the !important override ineffective and the rule itself unnecessary. The JavaScript should handle visibility directly without needing this CSS fallback.

Consider removing this block or using a class-based approach (e.g., .lightbox-nav-hidden) if you need CSS control over visibility.

index.html (1)

95-134: Migrate span8 to col-md-8 for consistency with the rest of the page.

The .span8 class is still defined in lib/bootstrap/bootstrap-grid.css, but the page already uses col-md-8 elsewhere (line 96). Standardizing on col-md-8 throughout would improve maintainability and consistency, especially given the mixed Bootstrap versions in use.

♻️ Suggested update
-  <div class="span8 news-item">
+  <div class="col-md-8 news-item">-  <div class="span8 news-item">
+  <div class="col-md-8 news-item">

Comment on lines 58 to 56
<!-- Security Headers -->
<!-- CSP: cdn.jsdelivr.net only for OpenLayers maps (download/maps pages) -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://maps.google.com https://www.google.com https://download.xcsoar.org; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://cdn.jsdelivr.net https://download.xcsoar.org https://maps.google.com https://www.google.com https://skylines.aero; frame-src https://maps.google.com https://www.google.com; object-src 'none'; base-uri 'self'; form-action 'self';">
<meta http-equiv="X-Content-Type-Options" content="nosniff">
<meta http-equiv="X-Frame-Options" content="SAMEORIGIN">
<meta http-equiv="X-XSS-Protection" content="1; mode=block">
<meta name="referrer" content="strict-origin-when-cross-origin">
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for server configuration files that might set security headers
echo "=== Looking for server config files ==="
fd -a -H '(_headers|\.htaccess|SECURITY_HEADERS\.md|netlify\.toml|vercel\.json|nginx\.conf|apache.*\.conf|caddy\.conf)$' 2>/dev/null

echo ""
echo "=== Searching for security header patterns ==="
rg -in 'Content-Security-Policy|X-Frame-Options|X-Content-Type-Options|X-XSS-Protection' --type-list | head -20

Repository: XCSoar/website

Length of output: 620


🏁 Script executed:

#!/bin/bash
# More comprehensive search for config and header files
echo "=== Config files in root and common locations ==="
git ls-files | grep -E '(^[^/]*\.(toml|json|htaccess|conf)$|^[^/]*/.*(\.toml|\.json|_headers|\.htaccess|\.conf)$)' 2>/dev/null

echo ""
echo "=== Files with "header" in name or path ==="
git ls-files | grep -i header 2>/dev/null

echo ""
echo "=== Search for HTTP header configuration patterns ==="
rg -in 'X-Frame-Options|X-Content-Type-Options|X-XSS-Protection' 2>/dev/null | head -20

Repository: XCSoar/website

Length of output: 1329


🏁 Script executed:

cat -n /home/jailuser/git/.htaccess

Repository: XCSoar/website

Length of output: 1688


🏁 Script executed:

cat -n /home/jailuser/git/SECURITY_HEADERS.md

Repository: XCSoar/website

Length of output: 4750


Meta-based X- headers provide no browser enforcement; server-level headers are not configured.*

X-Content-Type-Options, X-Frame-Options, and X-XSS-Protection are ignored by modern browsers when set via <meta http-equiv> tags. Only CSP is honored via meta tags. The .htaccess file shows these headers are intentionally not configured at the server level (all directives are commented out), leaving the application without actual protection against MIME sniffing, clickjacking, and XSS attacks.

The SECURITY_HEADERS.md documentation acknowledges that HTTP headers are necessary for maximum security but they remain unimplemented. Either uncomment and activate the headers in .htaccess, or remove the meta tags to avoid a false sense of coverage.

🤖 Prompt for AI Agents
In `@_includes/header.html` around lines 58 - 64, The meta-based X-* headers in
_includes/header.html (the meta http-equiv tags for X-Content-Type-Options,
X-Frame-Options, and X-XSS-Protection) give a false sense of protection because
browsers ignore them when set via meta; fix by moving these headers to
server-level configuration: uncomment and enable the equivalent directives in
.htaccess (set X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN or
proper CSP frame-ancestors, and remove/replace X-XSS-Protection with a CSP
fallback) so server emits them consistently, and update SECURITY_HEADERS.md to
reflect the enabled server headers; alternatively, if you cannot enable server
headers, remove the meta http-equiv lines from _includes/header.html and
document that only CSP remains via meta in SECURITY_HEADERS.md.

Comment on lines 14 to 18
<script>
$(document).ready(function() {
$('#secondary .row .span12').append($('#primary #footer').detach());
$('#secondary .row .col-12').append($('#primary #footer').detach());
$('#primary #content #banner #header').hide();
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's check if jQuery is actually included in the project's dependencies
if [ -f package.json ]; then
  echo "=== Checking package.json ==="
  cat package.json | head -50
fi

if [ -f Gemfile ]; then
  echo "=== Checking Gemfile ==="
  cat Gemfile | grep -i jquery || echo "No jQuery in Gemfile"
fi

echo ""
echo "=== Searching for jQuery includes/script tags ==="
rg -n 'jquery(\.min)?\.js|code\.jquery\.com|<script.*jquery' --type html || true

Repository: XCSoar/website

Length of output: 154


🏁 Script executed:

#!/bin/bash
# Check for jQuery usages across the codebase
echo "=== jQuery variable/function usages ==="
rg -n '\$\(' --type html -A 1 -B 1 || true

Repository: XCSoar/website

Length of output: 379


🏁 Script executed:

#!/bin/bash
# Look at the specific file mentioned
echo "=== Full content of _layouts/trac.html ==="
if [ -f _layouts/trac.html ]; then
  cat -n _layouts/trac.html
else
  echo "File not found. Searching for trac.html..."
  find . -name "trac.html" -type f
fi

Repository: XCSoar/website

Length of output: 636


🏁 Script executed:

#!/bin/bash
# Check the overall project structure to understand if this is a Jekyll site
echo "=== Project structure ==="
ls -la | head -20
echo ""
if [ -f _config.yml ]; then
  echo "=== _config.yml (Jekyll config) ==="
  cat _config.yml | head -30
fi

Repository: XCSoar/website

Length of output: 1753


Convert to vanilla JavaScript; jQuery is not loaded and this script will fail at runtime.

The project has no jQuery includes (not in Gemfile or any layout files), but the code in _layouts/trac.html uses $() and .detach()/.hide() methods. This will throw a ReferenceError when the page loads, breaking the footer repositioning and header hiding logic.

🔧 Proposed vanilla JS replacement
 <script>
-  $(document).ready(function() {
-    $('#secondary .row .col-12').append($('#primary `#footer`').detach());
-    $('#primary `#content` `#banner` `#header`').hide();
-  });
+  document.addEventListener('DOMContentLoaded', () => {
+    const footer = document.querySelector('#primary `#footer`');
+    const target = document.querySelector('#secondary .row .col-12');
+    if (footer && target) target.appendChild(footer);
+
+    const header = document.querySelector('#primary `#content` `#banner` `#header`');
+    if (header) header.style.display = 'none';
+  });
 </script>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<script>
$(document).ready(function() {
$('#secondary .row .span12').append($('#primary #footer').detach());
$('#secondary .row .col-12').append($('#primary #footer').detach());
$('#primary #content #banner #header').hide();
});
<script>
document.addEventListener('DOMContentLoaded', () => {
const footer = document.querySelector('#primary `#footer`');
const target = document.querySelector('#secondary .row .col-12');
if (footer && target) target.appendChild(footer);
const header = document.querySelector('#primary `#content` `#banner` `#header`');
if (header) header.style.display = 'none';
});
</script>
🤖 Prompt for AI Agents
In `@_layouts/trac.html` around lines 14 - 18, Replace the jQuery DOM manipulation
in the ready handler with vanilla DOM APIs: select the footer element using the
selector '#primary `#footer`', remove it from its parent (preserve the node), then
append that node into the target container selected by '#secondary .row
.col-12'; also select the header via '#primary `#content` `#banner` `#header`' and
hide it by setting its style (e.g., display = "none"). Ensure this runs after
DOMContentLoaded (use DOMContentLoaded event instead of jQuery's ready) and
handle missing elements by checking for null before removing/appending or
changing style.

- **Impact**: Reduces page load, improves security, better performance

### 2. **Outdated Bootstrap Version**
**Current**: Bootstrap 2.x (very old, likely custom build)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Tighten a couple of wording nits.

LanguageTool flags “very old” as a weak intensifier and “SEO Optimization” as redundant. Suggested tweaks below.

✏️ Suggested wording tweaks
-**Current**: Bootstrap 2.x (very old, likely custom build)
+**Current**: Bootstrap 2.x (outdated, likely custom build)

-### 8. **SEO Optimization**
+### 8. **SEO**

-| Medium | Medium | Medium | SEO optimization |
+| Medium | Medium | Medium | SEO |

Also applies to: 103-103, 297-297

🧰 Tools
🪛 LanguageTool

[style] ~22-~22: As an alternative to the over-used intensifier ‘very’, consider replacing this phrase.
Context: ...p Version** Current: Bootstrap 2.x (very old, likely custom build) Issues: - No ...

(EN_WEAK_ADJECTIVE)

🤖 Prompt for AI Agents
In `@IMPROVEMENTS.md` at line 22, Update the wording in IMPROVEMENTS.md to remove
weak/redundant phrases: replace "Bootstrap 2.x (very old, likely custom build)"
with a more precise phrase such as "Bootstrap 2.x (legacy, likely custom build)"
and change any instances of "SEO Optimization" to "SEO" (remove redundant
"Optimization"); apply these edits at the occurrences referenced (the line shown
plus the other occurrences noted around lines 103 and 297) so the language is
tighter and non-redundant.

Philipp Wollschlegel and others added 22 commits January 26, 2026 01:44
- Update nanoc from 4.14.3 to 4.14.6 (performance improvements)
- Update related dependencies: addressable, concurrent-ruby, zeitwerk
- Update actions/checkout to latest v6 commit (8e8c483)

Addresses PRs #152 and #153
- Remove jQuery 1.8.2 (12+ years old, security risk)
- Remove jQuery lightbox plugin
- Add modern vanilla JS lightbox (no dependencies)
- Update header.html to load new lightbox CSS/JS
- Remove jQuery scripts from index.html and screenshots page
- Remove unnecessary jquery: true from maps/waypoints pages

Features of new lightbox:
- No external dependencies
- Keyboard navigation (Arrow keys, Escape)
- Touch-friendly mobile support
- Accessible (ARIA labels)
- Smaller file size (~4KB vs ~90KB jQuery + plugin)
- Modern ES6+ JavaScript

Fixes #42 - Replace jquery lightbox with generic lightbox

This is part of Phase 1: Critical Security & Performance improvements.
- Add Content-Security-Policy meta tag to prevent XSS attacks
- Add X-Content-Type-Options to prevent MIME sniffing
- Add X-Frame-Options to prevent clickjacking
- Add X-XSS-Protection for legacy browser support
- Add Referrer-Policy for privacy protection
- Create .htaccess template for server-level headers
- Add SECURITY_HEADERS.md documentation

CSP Configuration:
- Allows scripts from self, inline (for lightbox), and trusted CDNs
- Allows styles from self, inline, and CDNs
- Allows images from self, data URIs, and HTTPS sources
- Allows connections to download.xcsoar.org, Google Maps, etc.
- Blocks plugins (object-src 'none')
- Restricts base-uri and form-action to self

Security Headers:
- Content-Security-Policy: Prevents XSS, data injection attacks
- X-Content-Type-Options: Prevents MIME type confusion
- X-Frame-Options: Prevents clickjacking
- X-XSS-Protection: Legacy browser XSS protection
- Referrer-Policy: Controls referrer information

Note: Meta tags provide basic protection. For maximum security,
HTTP headers should be configured at the server level (see .htaccess
and SECURITY_HEADERS.md for configuration examples).

This is part of Phase 1: Critical Security & Performance improvements.
…ness

Fixes from CodeRabbit review:

1. Accessibility improvements:
   - Add :focus-visible styles for lightbox controls (keyboard navigation)
   - White outline with offset for clear focus indication on dark background

2. Error handling:
   - Add img.onerror handler for failed image loads
   - Display error message instead of blank lightbox
   - Maintain navigation functionality even on errors

3. Bug fixes:
   - Prevent duplicate lightbox HTML insertion
   - Combine selectors into single initLightbox call
   - Use shared state for all images across multiple selectors

4. Code quality:
   - Attach keyboard handler only when lightbox is open
   - Remove keyboard handler when lightbox is closed
   - Better resource management

5. Documentation and naming:
   - Rename page.jquery to page.lightbox for clarity
   - Update IMPROVEMENTS.md to reflect actual implementation
   - Add language specifiers to code blocks in WEBSITE_STRUCTURE.md

All changes tested and build successful.
Major upgrade to modern Bootstrap version:

1. Replaced Bootstrap 2 CSS files with Bootstrap 5.3.8 CDN
   - Removed local bootstrap-normalize.css, bootstrap-button.css, bootstrap-grid.css
   - Added Bootstrap 5.3.8 from jsDelivr CDN with integrity hash

2. Updated grid system classes:
   - .span4 → .col-md-4
   - .span8 → .col-md-8
   - .span12 → .col-12

3. Updated utility classes:
   - .pull-right → .float-end
   - .btn-large → .btn-lg
   - .btn-block → .w-100

4. Updated all layout files:
   - _layouts/default.html, simple.html, post.html, develop.html
   - _layouts/download.html, discover.html, trac.html
   - _includes/header.html, footer.html, section_change.html
   - _includes/discover_menu.html, download_sidebar.html
   - index.html, discover/screenshots/index.html, discover/news.html

5. Updated custom CSS:
   - Changed #screenshots .span4 to #screenshots .col-md-4

Benefits:
- Modern responsive grid system
- Better mobile support
- Improved accessibility
- Latest security patches
- Smaller CSS bundle (CDN cached)
- Better browser compatibility

Build tested and verified successful.
Create rule to enforce hosting all external resources (CSS, JS, fonts, images) locally instead of using CDN links.

Benefits:
- Better privacy and security (no external requests)
- Improved reliability (no CDN dependency)
- Offline support
- Version control of all dependencies
- Better performance (no external network latency)

The rule is set to alwaysApply: true, so it will guide all future development to avoid CDN dependencies.

Note: Bootstrap is currently loaded from CDN and should be migrated to local hosting per this rule.
Create rule to guide using native HTML5 features instead of JavaScript implementations whenever possible.

Benefits:
- Better performance (native browser features)
- Improved accessibility (built-in ARIA support)
- Progressive enhancement (works without JS)
- Less code to maintain
- Better SEO (semantic HTML)
- Better mobile support

The rule provides examples for:
- Forms and input validation
- Navigation and links
- Modals and dialogs
- Details/accordions
- Images and lazy loading
- Video/audio controls
- Semantic HTML5 elements

Rule is set to alwaysApply: true to guide all development.
Rule 1: Host all resources locally
- Downloaded Bootstrap 5.3.8 CSS and source map
- Moved from CDN to local: /lib/bootstrap-5.3.8/bootstrap.min.css
- Updated header.html to use local Bootstrap path
- Note: OpenLayers (maps) still uses CDN - separate task

Rule 2: Prefer HTML5 over JavaScript
- Added semantic <nav> element around navigation
- Added lazy loading (loading="lazy") to all screenshot images
- Converted screenshots to use <figure> and <figcaption> elements
- Improved semantic HTML structure

Benefits:
- Better privacy (no Bootstrap CDN requests)
- Improved performance (lazy loading images)
- Better accessibility (semantic HTML5 elements)
- Better SEO (semantic structure)
- Progressive enhancement (works without JS)

Files changed:
- _includes/header.html (local Bootstrap, <nav>)
- index.html (lazy loading, <figure>)
- discover/screenshots/index.html (lazy loading, <figure>)
- lib/bootstrap-5.3.8/ (new directory with Bootstrap files)

Build tested and verified successful.
…utes

HTML5 syntax improvements:
- Remove self-closing slashes: <img /> → <img>, <br/> → <br>
- Fix closing tag: </div> → </aside> for aside element
- Add rel="noopener noreferrer" to external links with target="_blank"
  (security best practice to prevent window.opener attacks)

Files updated:
- index.html (br tags, img tags, aside closing, security rel)
- discover/screenshots/index.html (br tags, img tags)
- _includes/header.html (img tags)

All changes follow HTML5 standard syntax (no XHTML-style self-closing tags).
Build tested and verified successful.
Issue: After converting #primary from div to header, the main content
was outside the white background container, causing text to flow outside.

Solution:
- Moved white background and width constraints to .shadowed container
  (which wraps both header and main)
- Simplified #primary styling to just padding
- Ensured main element is properly inside .shadowed container
- Added proper closing of .shadowed div in footer

Now both header and main content are contained within the white
background area with proper width constraints (98% width, max 1230px).

Files changed:
- css/xcsoar.css (moved background to .shadowed, simplified #primary)
- _includes/header.html (indentation fix)
- _includes/footer.html (close .shadowed div)

Build tested and verified successful.
Changed margin from 'margin: auto; margin-top: 1%' to explicit
'margin: 1% auto 0 auto' to ensure proper horizontal centering
with 1% margin on each side (from width: 98%).

This ensures the white content area has proper left/right margins
on all screen sizes.
Changed from centered (margin: auto) to explicit margins:
- Left margin: 2% (increased from 1%)
- Right margin: 1%
- Width: 97% (reduced from 98% to accommodate larger left margin)

This gives more visual space on the left side of the content area.
Added padding: 0 20px to .shadowed container to create
internal margins on left and right sides. This prevents
content from touching the edges of the white container.

The container now has:
- External margins: 1% right, 2% left
- Internal padding: 20px left and right
- Content is properly spaced from container edges
Issues fixed:
1. Removed double padding - moved padding from .shadowed to .shadowed .container
   to avoid padding conflicts
2. Added logo image constraints - max-width: 100% to prevent cutoff
3. Reset logo container padding-left to 0 for proper left alignment

The logo should now:
- Be properly aligned on the left
- Not be cut off on the right (responsive sizing)
- Have consistent spacing with the rest of the header
Restored padding: 0 20px to .shadowed container to maintain
internal left/right margins. Removed padding from .shadowed .container
to avoid double padding, keeping only the .shadowed padding.

This ensures:
- External margins: 2% left, 1% right (outside white container)
- Internal padding: 20px left/right (inside white container)
- Content properly spaced from all edges
Issue: Logo was being cut off on the left side because container padding
was removed, causing content to touch the edge.

Fix:
- Restored padding: 0 20px to .shadowed .container
- Added overflow: visible to #logo to prevent clipping
- Added width: auto to logo img to maintain natural size
- Logo now has proper spacing and won't be cut off

The logo (230px wide) should now display fully within the col-md-4
container with proper left/right spacing.
Issue: Logo SVG was missing viewBox attribute, causing content to be
cut off on the left side when scaled or displayed.

Fix:
- Added viewBox="0 0 230 70" to logo-blue.svg
- This ensures the SVG scales properly and all content is visible
- Reverted container padding change (not the issue)

The viewBox tells the browser the coordinate system and ensures
the entire logo (0,0 to 230,70) is visible when rendered.
Added viewBox="0 0 230 70" to logo-green.svg and logo-red.svg
to match the fix applied to logo-blue.svg. This ensures all
logo variants scale properly and display correctly without cutoff.
Issue: The 'R' in XCSoar was being cut off because the SVG viewport
was too narrow. Text starts at x=90px and extends to ~265px, but
the SVG width was only 230px.

Fix:
- Increased width from 230px to 270px
- Updated viewBox from "0 0 230 70" to "0 0 270 70"
- Applied to all three logo variants (blue, green, red)

Now the full 'XCSoar' text is visible when viewing the SVG directly
or when it's rendered on the website.
Navigation changes:
- Removed 'Hardware' from main navigation
- Removed 'Contact' from main navigation
- Removed 'Features' from discover menu
- Simplified navigation to: Discover, Download, Contribute

Homepage restructuring:
- Created clear sections: About, Screenshots, News
- Added semantic <section> elements for better structure
- Reorganized content flow: About -> Screenshots -> News
- Updated About section with content from discover/index.md
- Improved News section layout with better headings
- Removed SkyLines promotion (can be added elsewhere if needed)

Benefits:
- Cleaner, simpler navigation
- Better homepage structure with clear sections
- More focused user experience
- Better semantic HTML5 structure
Reorganized homepage sections to flow correctly:
1. About section (with download sidebar)
2. Screenshots section
3. News section

Removed duplicate news section and ensured proper semantic
HTML5 structure with <section> elements.
Philipp Wollschlegel added 28 commits January 26, 2026 01:45
Increased main padding-top from 0 to 1.25rem to create proper
separation between the tagline header and the carousel content.
Increased carousel container height to accommodate full images and captions:
- Increased min-height from 21.875rem to 28.125rem (350px to 450px)
- Changed overflow from hidden to visible to prevent clipping
- Increased image max-height from 17.5rem to 20rem (280px to 320px)
- Updated carousel-track to use min-height instead of fixed height
- Applied same changes to mobile media query

This ensures screenshots and their captions are fully visible without
being cut off by the container boundaries.
Increased image size to better match container width:
- Increased max-width from 70% to 90% for carousel images
- Updated .screenshot class max-width to 90% and max-height to 20rem
- Changed figure width from 100% to auto with max-width 90%
- Added margin: 0 auto to images for proper centering

This reduces the empty space around images, making the carousel
canvas better match the actual image dimensions.
Changed images to width: auto with max-width: 85% instead of
fixed max-width: 90%. This allows images to size naturally
while still constraining them, making better use of available
space without breaking the absolute positioning layout.
Increased image max-width from 85% to 95% to make images fill
more of the available canvas space. This makes the canvas appear
closer to the actual image size rather than having large empty
spaces around the images.

- Changed max-width from 85% to 95% for carousel images
- Updated figure max-width from 90% to 100%
- Updated .screenshot class to match (95% max-width, 20rem height)
- Applied same changes to mobile media query
Implemented JavaScript solution to measure image width and set
carousel container width to match:

JavaScript changes:
- Added updateCarouselWidth() to measure active image width
- Added setCarouselWidth() to apply measured width to containers
- Call updateCarouselWidth() after each slide change
- Handle window resize events to recalculate width
- Wait for all images to load before initial width calculation

CSS changes:
- Added margin: 0 auto to center the dynamically-sized carousel
- Containers now use JavaScript-set width instead of fixed 100%

The carousel canvas now sizes to match the actual image dimensions
rather than using the full Bootstrap column width, eliminating
empty space around images.
Added functions to measure image width and set carousel container
width to match the actual image dimensions:

- updateCarouselWidth(): Measures the active image width
- setCarouselWidth(): Applies measured width to carousel containers
- Called after each slide change with a small delay
- Handles window resize events
- Waits for all images to load before initial calculation

This makes the carousel canvas fit the image size dynamically.
Enhanced width measurement to account for CSS constraints:
- Temporarily remove max-width constraint when measuring
- Measure actual rendered width after forcing reflow
- Increased image max-width from 95% to 100%
- Changed figure width back to 100% for proper measurement
- Add small buffer (20px) for shadows/padding in width calculation

This should reduce the margin from ~40% to minimal by measuring
the actual image size more accurately.
Changed approach to calculate width from natural image dimensions
instead of measuring rendered size:

- Removed max-width constraint from carousel images (only max-height remains)
- Calculate width based on naturalWidth/naturalHeight and max-height scale
- If image height > max-height, calculate proportional width
- Otherwise use natural width
- Increased buffer to 40px to account for box-shadow extension

This should eliminate the ~40% margin by sizing the container to
match the actual image dimensions rather than the constrained size.
Updated .carousel-slide .screenshot to remove max-width constraint,
matching the img element changes for consistent sizing.
Removed the 40px buffer from width calculation. The carousel
now uses the exact calculated image width with no additional
padding or margin.

Add spacing to carousel caption and indicators

Added margins for proper spacing:
- Added margin-top: 0.625rem to figcaption for spacing from image
- Changed figcaption padding from bottom-only (3.125rem) to balanced
  top/bottom (1.25rem each) with horizontal padding
- Added margin-top: 1.25rem to carousel-indicators for spacing from caption

This ensures the caption has proper spacing from the carousel container
and maintains distance between the caption and the carousel dots.

Add bullet points to ul elements in about section

Added list-style formatting for ul elements in #about:
- Added list-style-type: disc and list-style-position: outside to #about ul
- Added padding-left: 1.25rem for proper indentation
- Override li list-style: none with list-style-type: disc for #about ul li
- Added padding-left: 0.625rem for spacing between bullet and text

This ensures the bullet points are visible and properly formatted
in the about section list items.

Add spacing between paragraph elements

Added padding-top and padding-bottom of 0.625rem (10px) to p elements
to create visual separation between paragraphs. This improves readability
by providing clear spacing between blocks of text.

Add spacing between paragraph and list in about section

Added padding-top: 0.625rem to #about ul to create separation
between the first paragraph and the unordered list. This improves
visual hierarchy and readability in the about section.

Make spacing between p and ul/ol elements universal

Added padding-top and padding-bottom to all ul and ol elements:
- Added padding-top: 0.625rem and padding-bottom: 0.625rem to ul, ol
- Removed redundant padding-top from #about ul (now inherited)
- This creates consistent spacing between paragraphs and lists
  across all pages, not just the about section

All pages now have proper separation between paragraphs and
list elements for better readability.
Simplified and condensed text across key pages:

Homepage (index.html):
- Shortened about section intro from 2 lines to 1
- Condensed list items (removed redundant words)
- Simplified closing paragraph
- Shortened all carousel captions (removed verbose descriptions)

Discover pages:
- discover/index.md: Removed historical details, condensed to essentials
- discover/manual.md: Simplified error reporting instructions

Develop page:
- develop/index.md: Condensed contribution descriptions, removed verbose explanations

All text is now more concise while retaining essential information.
Removed the data repository link from the Data management section
in develop/index.md, keeping only map generation and data content links.

Replace Contribute page with simple GitHub link

Replaced the detailed contribution page with a minimal page that
simply links to the GitHub repository. The page now contains:
- Brief one-line description
- Direct link to GitHub repository

This follows the 'less is more' principle and directs users to
GitHub for all contribution information.

Link Contribute navigation directly to GitHub

Changed the Contribute navigation link to point directly to GitHub
instead of the /develop/ page. The link now:
- Points to https://github.com/XCSoar/XCSoar
- Opens in new tab (target="_blank")
- Includes rel="noopener noreferrer" for security
- Removed menu state tracking (no longer needed)

Users clicking Contribute will go directly to GitHub.

Replace Contribute text with GitHub logo icon

Replaced the 'Contribute' text link with a GitHub logo icon:
- Added icon-github.svg (simple GitHub octocat icon)
- Updated navigation link to display GitHub icon instead of text
- Icon sized to 1.25rem (20px) to match navigation text height
- Maintains accessibility with title and alt text

The navigation now shows a GitHub icon that links directly to
the XCSoar GitHub repository.

Add GitHub text alongside logo icon

Added 'GitHub' text before the icon in the navigation link.
The link now displays as 'GitHub' followed by the GitHub logo icon,
making it clearer what the link is while maintaining the visual
icon for quick recognition.

Add Discussion link to navbar

Added Discussion navigation link pointing to the XCSoar forum:
- Links to https://forum.xcsoar.org/
- Opens in new tab with proper security attributes
- Styled with blue hover color to match other navigation items
- Positioned between Download and GitHub links

This provides easy access to the community discussion forum
from the main navigation.

Change Discussion link to GitHub Discussions

Updated Discussion navigation link to point to GitHub Discussions
instead of the forum. The link now goes to:
https://github.com/XCSoar/XCSoar/discussions

This provides direct access to GitHub Discussions from the navbar.

Remove contact page and all links to it

Removed contact section:
- Deleted contact/index.md and contact/irc.html
- Removed contact directory

Updated links:
- discover/manual.md: Removed 'contact us' link, kept GitHub issue link
- _posts/2012-03-14: Removed 'contact us' link
- _posts/2010-12-20: Replaced 'contact us' with GitHub issues link
- _posts/2021-05-20: Removed IRC webchat link, kept IRC info as text

All contact functionality is now handled through GitHub (Issues/Discussions)
or external services (forum, IRC) without needing a dedicated contact page.

Remove develop/contribute pages and all links to them

Removed develop section:
- Deleted develop/index.md, develop/new_ticket.md, develop/donations.md, develop/infrastructure.md
- Removed develop/ directory

Updated all links:
- discover/manual.md: Changed /develop/ to GitHub link
- discover/index.md: Changed /develop/new_ticket.html to GitHub issues
- _posts/2012-03-14: Changed to GitHub issues
- _posts/2011-11-04: Changed to GitHub issues
- _posts/2012-01-27: Changed to GitHub issues

All contribution and bug reporting now goes directly to GitHub
(Issues/Discussions) without intermediate pages.
Deleted the remaining new_ticket.md file that was missed in the
previous commit. The develop directory is now completely removed.

Move discover/manual.md to docs/manual.md

Moved manual page to docs directory:
- Moved discover/manual.md to docs/manual.md
- Updated menu from 'discover' to 'docs'

Updated all links:
- _includes/header.html: Changed /discover/manual.html to /docs/manual.html
- _includes/download_sidebar.html: Updated manual link
- _includes/discover_menu.html: Updated manual link
- hardware/index.md: Updated manual link

The manual is now accessible at /docs/manual.html instead of
/discover/manual.html, better matching the 'Docs' navigation label.
Created docs/index.md as the main docs landing page:
- Added Manual section linking to /docs/manual.html
- Added Developer Docs section linking to developer manual

Updated navigation:
- Changed Docs link from /docs/manual.html to /docs/ to show
  the index page with both sections

The docs page now serves as a hub for both user and developer
documentation.

Remove developer manual link from manual page

Removed the developer manual link from docs/manual.md since it's
now in the docs index page under Developer Docs section.

Integrate manual content into docs index page

Merged docs/manual.md content directly into docs/index.md:
- Manual section now shows all PDF manuals directly on /docs/
- Removed separate /docs/manual.html page

Updated all references:
- hardware/index.md: changed link from /docs/manual.html to /docs/
- _includes/discover_menu.html: changed 'Manual' link to 'Docs' pointing to /docs/
- _includes/download_sidebar.html: changed button link from /docs/manual.html to /docs/

Deleted docs/manual.md as it's no longer needed.

The docs page now serves as a unified hub with Manual and Developer Docs
sections, with all manual PDFs visible directly on the main docs page.

Remove outdated sentence from docs page

Removed the sentence about manual topics potentially being missing or
outdated from the docs index page.
Updated FLARM protocol link from /support/manuals/ to /support/downloads/
as per user correction.
Added protocol documentation links for:
- Loefgren vario
- Vaulter

Both link to their respective driver implementations in the XCSoar
GitHub repository.
Changed LXNAV protocol link from GitHub repository to the official
LX navigation Communication Protocols download page at
downloads.lxnavigation.com.
Added LX navigation as a separate entry pointing to the official
download page, and kept LXNAV as its own entry pointing to the
GitHub implementation. These are two different manufacturers.
Added key features identified from XCSoar NEWS.txt:
- Contest scoring with automatic OLC and WeGlide integration
- Weather integration with RASP forecasts and satellite imagery
- Airspace warnings with real-time alerts and filtering
- Device integration supporting many manufacturers

These features highlight XCSoar's comprehensive capabilities for
competition pilots, weather awareness, safety, and hardware compatibility.

Add iOS to platform list

Added iOS to the multi-platform feature list to reflect XCSoar's
support for iOS devices.

Replace Unix with Linux and list it first

Changed 'Unix' to 'Linux' and reordered platforms to list Linux first,
followed by Android, iOS, and Windows.
Removed OLC from the contest scoring feature and added links to:
- WeGlide (weglide.org) for automatic integration
- SkyLines (skylines.aero) for tracking

Both links open in new tabs with proper security attributes.
Added mention of automatic thermals from Thermal Info Map (TIM) to the
weather integration feature, with a link to thermalmap.info.
Updated moving map feature to mention worldwide databases for
airspace and waypoints data.
Updated FLARM integration to mention both collision avoidance and
traffic information capabilities.
Added a prominent button in the download sidebar linking to
downloads.xcsoar.org/testing/ for the latest development build.
The button uses warning style to indicate it's a testing version
and includes a brief description.
Changed relative path ./ol.js to root-relative path /download/maps/ol.js
in both index.html and ommaps.html to fix import failures. This ensures
the OpenLayers JavaScript library loads correctly regardless of how the
page is accessed.
Updated Content-Security-Policy to:
- Add 'unsafe-eval' to script-src to allow eval() for maps.config.js parsing
- Add worker-src 'self' blob: to allow blob workers used by OpenLayers

This fixes CSP violations on the maps download page where:
1. eval() is used to parse the fetched maps.config.js file
2. OpenLayers creates blob workers for map rendering

Also updated SECURITY_HEADERS.md documentation to reflect these changes.
@lordfolken lordfolken force-pushed the modernize/remove-jquery-replace-lightbox branch from 9572488 to 198c66b Compare January 26, 2026 00:49
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In `@_includes/download_sidebar.html`:
- Line 33: Remove the unintended double space before the English link in the
HTML snippet: find the line containing the anchor tag '<a href="{{
site.download_server_url }}/{{ site.xcsoar_stable_version
}}/XCSoar-manual.pdf">English</a>' in _includes/download_sidebar.html and change
"In  <a ..." to "In <a ..." so there is only a single space between "In" and the
link.

In `@docs/index.md`:
- Line 18: The link text contains a duplicated word: replace the link label
"XCSoar XCSoar em um flash" with "XCSoar em um flash" (the link target is
XCSoar-in-a-flash-pt_BR.pdf), so update the markdown entry to remove the extra
"XCSoar" while keeping the existing flag and URL unchanged.

In `@IMPROVEMENTS.md`:
- Around line 5-20: Update the "Current: jQuery 1.8.2" label in IMPROVEMENTS.md
to indicate it's the pre-Phase-1 / legacy baseline (e.g., "Pre-Phase-1: jQuery
1.8.2") and add a short clarifying sentence that the live/deployed
implementation now uses the custom vanilla lightbox at /lib/lightbox.js (refer
to the "Implemented with custom vanilla JS" note) so readers understand the
document contrasts the legacy baseline with the current implemented state.

In `@index.html`:
- Around line 114-122: The carousel indicators list in the element with class
"carousel-indicators" is missing the button for the 8th slide; add a new
<button> with class "carousel-indicator", aria-label "Go to screenshot 8" (or
"Analysis: OLC" if you prefer more descriptive text) and data-slide="7" so the
indicator count matches the eight slides and navigation works correctly (follow
the pattern used by the existing "carousel-indicator" buttons).

In `@lib/screenshot-carousel.js`:
- Around line 28-33: The load handler for images can update the carousel width
even after the user has navigated away; modify the listener on activeImage so it
verifies the image is still the current active slide before calling
setCarouselWidth. Concretely, inside the 'load' callback registered on
activeImage, compare the event.target (or the captured activeImage) against the
current active slide image (e.g., query the carousel for the currently active
image or compare to a stored current reference) and only call
setCarouselWidth(activeImage) if they match; keep the { once: true } behavior to
avoid lingering listeners.
- Around line 127-139: The carousel's keydown handler won't run because the
carousel element isn't focusable; make the element referenced as carousel
focusable (e.g., set carousel.tabIndex = 0 or
carousel.setAttribute('tabindex','0')) before relying on the
addEventListener('keydown') so ArrowLeft/ArrowRight events fire and
prevSlide()/nextSlide() work; also consider adding an accessible label
(aria-label or role) for clarity and keep showSlide(0) initialization as-is.

In `@WEBSITE_STRUCTURE.md`:
- Around line 7-12: The doc still lists jQuery 1.8.2 and jquery-lightbox even
though the PR added a vanilla ES6+ lightbox; update WEBSITE_STRUCTURE.md to
remove references to jQuery and jquery-lightbox and document the new setup:
replace the "JavaScript: jQuery 1.8.2, custom scripts" line with "JavaScript:
Vanilla ES6+ lightbox, custom scripts" (and note no jQuery dependency) and
similarly change the other sections that mention jquery-lightbox (lines
referenced in the review) to describe the new ES6 module/lightbox implementation
and any initialization details or build/import steps so contributors aren’t
misled.
♻️ Duplicate comments (1)
_includes/header.html (1)

50-56: X- headers via <meta http-equiv> are not enforced by browsers.*

Only Content-Security-Policy is effective via <meta> tags. The X-Content-Type-Options, X-Frame-Options, and X-XSS-Protection meta tags are ignored by modern browsers—these headers must be set server-side to have any effect.

🧹 Nitpick comments (10)
.cursor/rules/use-relative-links.mdc (1)

10-10: Minor inaccuracy in rationale.

Root-relative paths don't actually avoid DNS lookups—DNS resolution happens at the domain level, regardless of path format. The actual performance benefit (if any) is avoiding potential redirects when the domain name changes. Consider removing or rewording this point.

Suggested fix
-- **Performance**: Slightly faster (no DNS lookup needed)
+- **Performance**: Avoids potential redirects if domain configuration changes

Alternatively, simply remove this bullet point since the performance difference is negligible.

lib/lightbox.js (1)

89-114: Image loading handles success and error cases correctly.

The onerror handler (lines 104-112) properly addresses the previous review concern about failed image loads. Navigation buttons remain functional even when an image fails.

One minor enhancement to consider: clearing the image src before loading a new image could prevent a brief flash of the previous image during slow loads.

Optional enhancement
   function showImage(index) {
     if (index < 0 || index >= allImages.length) return;
     currentIndex = index;
+    
+    // Clear previous image to avoid flash during load
+    image.src = '';

     const img = new Image();
_includes/header.html (1)

52-52: CSP uses unsafe-inline and unsafe-eval which significantly weaken protection.

The 'unsafe-inline' and 'unsafe-eval' directives in script-src allow inline scripts and eval(), which defeats much of CSP's XSS protection. This appears necessary for the OpenLayers maps page that uses eval() (ommaps.html line 121).

Consider using nonce-based CSP or refactoring the maps page to avoid eval() for stronger security. This could be tracked as a future improvement.

css/xcsoar.css (7)

1-5: Consider the implications of overflow-x: hidden on both html and body.

Applying overflow-x: hidden to both elements prevents horizontal scrolling entirely, which can hide content from users who zoom in or use assistive technologies. If the intent is to prevent horizontal scroll caused by layout issues, consider fixing the root cause instead. If needed for specific components, apply it only to those containers.


98-103: Consider removing !important if possible.

The !important declarations suggest a specificity battle, likely with Bootstrap defaults. If feasible, increase selector specificity instead (e.g., .navbar .navbar-nav .nav-link) to avoid cascading issues in future maintenance.


190-203: Mixed layout approaches: float-based and flexbox.

This section uses float for layout (lines 191-203), while the preceding section (lines 153-188) uses flexbox. The float-based styling with fixed rem widths may conflict with the responsive flexbox grid above. If this is legacy code for backward compatibility, consider adding a comment; otherwise, consolidate to one approach to avoid maintenance confusion.


314-325: Redundant padding declarations.

Lines 315-317 set padding: 0 1.25rem and then immediately override with padding-top and padding-bottom. This can be simplified to a single declaration.

♻️ Suggested fix
 .carousel-slide figcaption {
-  padding: 0 1.25rem;
-  padding-top: 1.25rem;
-  padding-bottom: 1.25rem;
+  padding: 1.25rem;
   opacity: 0;
   transition: opacity 0.3s ease-in-out;

329-347: Missing cursor: pointer on interactive carousel buttons.

The .carousel-btn elements are clickable controls but lack cursor: pointer, which is a usability expectation for interactive elements. Same applies to .carousel-indicator (line 369).

♻️ Suggested fix
 .carousel-btn {
   position: absolute;
   top: 50%;
   transform: translateY(-50%);
   background: rgba(0, 0, 0, 0.6);
   color: `#fff`;
   border: 0.125rem solid `#fff`;
   border-radius: 50%;
   width: 3.125rem;
   height: 3.125rem;
   font-size: 2rem;
   line-height: 1;
   z-index: 10;
   display: flex;
   align-items: center;
   justify-content: center;
   padding: 0;
   transition: background-color 0.3s;
+  cursor: pointer;
 }

And for the indicator:

 .carousel-indicator {
   width: 0.75rem;
   height: 0.75rem;
   border-radius: 50%;
   border: 0.125rem solid `#878380`;
   padding: 0;
   transition: all 0.3s;
+  cursor: pointer;
+  background: transparent;
 }

439-455: Heavy use of !important in footer styles.

Multiple !important declarations suggest style override conflicts, likely with Bootstrap. While this works, it makes future maintenance harder. Consider scoping footer styles more specifically or using CSS custom properties for theming.


469-505: Potential list styling conflict.

Line 471 sets list-style-type: disc on ul, but line 504 sets list-style: none on all li elements. This means the disc style from ul will be overridden. If the intent is for most lists to have no markers except specific ones (lines 507-516), this works but is confusing. Consider adding a comment clarifying the cascade.

<hr>

<p><a href="/docs/" class="btn btn-lg w-100 btn-primary">Download Manual</a></p>
<p>In <a href="{{ site.download_server_url }}/{{ site.xcsoar_stable_version }}/XCSoar-manual.pdf">English</a>,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor: Extra space in text.

There's a double space after "In" which appears unintentional.

Suggested fix
-  <p>In  <a href="{{ site.download_server_url }}/{{ site.xcsoar_stable_version }}/XCSoar-manual.pdf">English</a>,
+  <p>In <a href="{{ site.download_server_url }}/{{ site.xcsoar_stable_version }}/XCSoar-manual.pdf">English</a>,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<p>In <a href="{{ site.download_server_url }}/{{ site.xcsoar_stable_version }}/XCSoar-manual.pdf">English</a>,
<p>In <a href="{{ site.download_server_url }}/{{ site.xcsoar_stable_version }}/XCSoar-manual.pdf">English</a>,
🤖 Prompt for AI Agents
In `@_includes/download_sidebar.html` at line 33, Remove the unintended double
space before the English link in the HTML snippet: find the line containing the
anchor tag '<a href="{{ site.download_server_url }}/{{
site.xcsoar_stable_version }}/XCSoar-manual.pdf">English</a>' in
_includes/download_sidebar.html and change "In  <a ..." to "In <a ..." so there
is only a single space between "In" and the link.

- {% country_flag de %} [XCSoar Handbuch]({{ site.download_server_url }}/{{ site.xcsoar_stable_version }}/XCSoar-manual-de.pdf) Complete German manual
- {% country_flag fr %} [XCSoar Prise en main]({{ site.download_server_url }}/{{ site.xcsoar_stable_version }}/XCSoar-Prise-en-main.pdf) Quick guide in French
- {% country_flag fr %} [XCSoar Manuel de l'utilisateur]({{ site.download_server_url }}/{{ site.xcsoar_stable_version }}/XCSoar-manual-fr.pdf) Mostly complete French manual
- {% country_flag br %} [XCSoar XCSoar em um flash]({{ site.download_server_url }}/{{ site.xcsoar_stable_version }}/XCSoar-in-a-flash-pt_BR.pdf) Quick guide in Portuguese
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo: Duplicate "XCSoar" in link text.

The link text reads "XCSoar XCSoar em um flash" with "XCSoar" appearing twice.

Proposed fix
-- {% country_flag br %} [XCSoar XCSoar em um flash]({{ site.download_server_url }}/{{ site.xcsoar_stable_version }}/XCSoar-in-a-flash-pt_BR.pdf) Quick guide in Portuguese
+- {% country_flag br %} [XCSoar em um flash]({{ site.download_server_url }}/{{ site.xcsoar_stable_version }}/XCSoar-in-a-flash-pt_BR.pdf) Quick guide in Portuguese
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- {% country_flag br %} [XCSoar XCSoar em um flash]({{ site.download_server_url }}/{{ site.xcsoar_stable_version }}/XCSoar-in-a-flash-pt_BR.pdf) Quick guide in Portuguese
- {% country_flag br %} [XCSoar em um flash]({{ site.download_server_url }}/{{ site.xcsoar_stable_version }}/XCSoar-in-a-flash-pt_BR.pdf) Quick guide in Portuguese
🤖 Prompt for AI Agents
In `@docs/index.md` at line 18, The link text contains a duplicated word: replace
the link label "XCSoar XCSoar em um flash" with "XCSoar em um flash" (the link
target is XCSoar-in-a-flash-pt_BR.pdf), so update the markdown entry to remove
the extra "XCSoar" while keeping the existing flag and URL unchanged.

Comment on lines +5 to +20
### 1. **Outdated JavaScript Libraries**
**Current**: jQuery 1.8.2 (released 2012, 12+ years old)
**Issues**:
- Security vulnerabilities
- No longer maintained
- Large file size (unnecessary for modern needs)
- Incompatible with modern browsers

**Recommendation**:
- Remove jQuery dependency entirely (most features can be done with vanilla JS)
- Replace jQuery lightbox with modern alternatives:
- [GLightbox](https://biati-digital.github.io/glightbox/) (lightweight, modern)
- [PhotoSwipe](https://photoswipe.com/) (feature-rich)
- Native CSS/JS solution (smallest footprint)
- **Impact**: Reduces page load, improves security, better performance

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

git ls-files | head -20

Repository: XCSoar/website

Length of output: 521


🏁 Script executed:

cat -n IMPROVEMENTS.md | head -50

Repository: XCSoar/website

Length of output: 2116


🏁 Script executed:

wc -l IMPROVEMENTS.md

Repository: XCSoar/website

Length of output: 77


🏁 Script executed:

cat -n IMPROVEMENTS.md | sed -n '180,210p'

Repository: XCSoar/website

Length of output: 1464


🏁 Script executed:

cat -n IMPROVEMENTS.md | grep -A 5 -B 5 -i "lightbox\|phase"

Repository: XCSoar/website

Length of output: 2127


🏁 Script executed:

# Check if jQuery is actually present in the codebase
rg -l "jquery" --type-list | head -10
fd . -type f | xargs rg -l "jquery-1\.8|jQuery" 2>/dev/null | head -20

Repository: XCSoar/website

Length of output: 864


🏁 Script executed:

# Check if the implemented lightbox files exist
ls -la lib/lightbox.* css/lightbox.* 2>/dev/null || echo "Files not found"
fd "lightbox\.(js|css)"

Repository: XCSoar/website

Length of output: 221


🏁 Script executed:

# Search for actual jQuery references in HTML/JS files
rg "jquery|jQuery" --type html --type js -c

Repository: XCSoar/website

Length of output: 197


🏁 Script executed:

# Check which HTML files reference jQuery or the lightbox
rg -l "jquery|lightbox" --type html

Repository: XCSoar/website

Length of output: 122


🏁 Script executed:

# Check the actual implementation in the main HTML/layout files
fd . -name "*.html" -o -name "*.liquid" -o -name "*.md" | head -10
find . -name "_layouts" -o -name "_includes" 2>/dev/null

Repository: XCSoar/website

Length of output: 328


🏁 Script executed:

# Check if lightbox.js or the old jquery-lightbox is actually referenced
rg "lightbox\.js|jquery-lightbox" --type html

Repository: XCSoar/website

Length of output: 126


🏁 Script executed:

# Check if jQuery is actually referenced in any HTML/includes
rg "jquery-1\.8\.2" --type html

Repository: XCSoar/website

Length of output: 40


🏁 Script executed:

# Check the header.html file to see all scripts
cat -n _includes/header.html

Repository: XCSoar/website

Length of output: 12677


🏁 Script executed:

# Check main layout files
cat -n _layouts/default.html 2>/dev/null | head -40

Repository: XCSoar/website

Length of output: 294


🏁 Script executed:

# Final check: verify jQuery is not actually loaded anywhere
rg "jquery-1\.8"

Repository: XCSoar/website

Length of output: 115


🏁 Script executed:

# Also verify the vanilla lightbox implementation
cat -n lib/lightbox.js | head -30

Repository: XCSoar/website

Length of output: 1392


Clarify whether section 1 describes current state or pre-modernization baseline. The section titled "Current: jQuery 1.8.2" conflicts with the later statement (lines 195-197) that the lightbox replacement is already "Implemented with custom vanilla JS." jQuery 1.8.2 is not referenced in active code; the deployed implementation uses the vanilla /lib/lightbox.js instead. Update the "Current" label to clarify this is the pre-Phase 1 state:

Suggested update
-**Current**: jQuery 1.8.2 (released 2012, 12+ years old)
+**Previous (pre-Phase 1)**: jQuery 1.8.2 (removed in Phase 1)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### 1. **Outdated JavaScript Libraries**
**Current**: jQuery 1.8.2 (released 2012, 12+ years old)
**Issues**:
- Security vulnerabilities
- No longer maintained
- Large file size (unnecessary for modern needs)
- Incompatible with modern browsers
**Recommendation**:
- Remove jQuery dependency entirely (most features can be done with vanilla JS)
- Replace jQuery lightbox with modern alternatives:
- [GLightbox](https://biati-digital.github.io/glightbox/) (lightweight, modern)
- [PhotoSwipe](https://photoswipe.com/) (feature-rich)
- Native CSS/JS solution (smallest footprint)
- **Impact**: Reduces page load, improves security, better performance
### 1. **Outdated JavaScript Libraries**
**Previous (pre-Phase 1)**: jQuery 1.8.2 (removed in Phase 1)
**Issues**:
- Security vulnerabilities
- No longer maintained
- Large file size (unnecessary for modern needs)
- Incompatible with modern browsers
**Recommendation**:
- Remove jQuery dependency entirely (most features can be done with vanilla JS)
- Replace jQuery lightbox with modern alternatives:
- [GLightbox](https://biati-digital.github.io/glightbox/) (lightweight, modern)
- [PhotoSwipe](https://photoswipe.com/) (feature-rich)
- Native CSS/JS solution (smallest footprint)
- **Impact**: Reduces page load, improves security, better performance
🤖 Prompt for AI Agents
In `@IMPROVEMENTS.md` around lines 5 - 20, Update the "Current: jQuery 1.8.2"
label in IMPROVEMENTS.md to indicate it's the pre-Phase-1 / legacy baseline
(e.g., "Pre-Phase-1: jQuery 1.8.2") and add a short clarifying sentence that the
live/deployed implementation now uses the custom vanilla lightbox at
/lib/lightbox.js (refer to the "Implemented with custom vanilla JS" note) so
readers understand the document contrasts the legacy baseline with the current
implemented state.

Comment on lines +114 to 122
<div class="carousel-indicators">
<button class="carousel-indicator active" aria-label="Go to screenshot 1" data-slide="0"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 2" data-slide="1"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 3" data-slide="2"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 4" data-slide="3"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 5" data-slide="4"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 6" data-slide="5"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 7" data-slide="6"></button>
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Bug: Indicator count doesn't match slide count.

There are 8 carousel slides but only 7 indicator buttons. The 8th slide ("Analysis: OLC") has no corresponding indicator, breaking navigation.

Suggested fix: Add missing indicator
           <button class="carousel-indicator" aria-label="Go to screenshot 6" data-slide="5"></button>
           <button class="carousel-indicator" aria-label="Go to screenshot 7" data-slide="6"></button>
+          <button class="carousel-indicator" aria-label="Go to screenshot 8" data-slide="7"></button>
         </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div class="carousel-indicators">
<button class="carousel-indicator active" aria-label="Go to screenshot 1" data-slide="0"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 2" data-slide="1"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 3" data-slide="2"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 4" data-slide="3"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 5" data-slide="4"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 6" data-slide="5"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 7" data-slide="6"></button>
</div>
<div class="carousel-indicators">
<button class="carousel-indicator active" aria-label="Go to screenshot 1" data-slide="0"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 2" data-slide="1"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 3" data-slide="2"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 4" data-slide="3"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 5" data-slide="4"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 6" data-slide="5"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 7" data-slide="6"></button>
<button class="carousel-indicator" aria-label="Go to screenshot 8" data-slide="7"></button>
</div>
🤖 Prompt for AI Agents
In `@index.html` around lines 114 - 122, The carousel indicators list in the
element with class "carousel-indicators" is missing the button for the 8th
slide; add a new <button> with class "carousel-indicator", aria-label "Go to
screenshot 8" (or "Analysis: OLC" if you prefer more descriptive text) and
data-slide="7" so the indicator count matches the eight slides and navigation
works correctly (follow the pattern used by the existing "carousel-indicator"
buttons).

Comment on lines +28 to +33
// Wait for image to load if not already loaded
if (activeImage.complete) {
setCarouselWidth(activeImage);
} else {
activeImage.addEventListener('load', () => setCarouselWidth(activeImage), { once: true });
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid width updates from non‑active slide image loads. If a previous slide finishes loading after the user navigates away, its load handler can override the width for the current slide. Guard the handler to only update when that slide is still active.

🐛 Proposed fix
-      if (activeImage.complete) {
-        setCarouselWidth(activeImage);
-      } else {
-        activeImage.addEventListener('load', () => setCarouselWidth(activeImage), { once: true });
-      }
+      if (activeImage.complete) {
+        setCarouselWidth(activeImage);
+      } else {
+        const expectedIndex = currentSlide;
+        activeImage.addEventListener('load', () => {
+          if (expectedIndex === currentSlide) {
+            setCarouselWidth(activeImage);
+          }
+        }, { once: true });
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Wait for image to load if not already loaded
if (activeImage.complete) {
setCarouselWidth(activeImage);
} else {
activeImage.addEventListener('load', () => setCarouselWidth(activeImage), { once: true });
}
// Wait for image to load if not already loaded
if (activeImage.complete) {
setCarouselWidth(activeImage);
} else {
const expectedIndex = currentSlide;
activeImage.addEventListener('load', () => {
if (expectedIndex === currentSlide) {
setCarouselWidth(activeImage);
}
}, { once: true });
}
🤖 Prompt for AI Agents
In `@lib/screenshot-carousel.js` around lines 28 - 33, The load handler for images
can update the carousel width even after the user has navigated away; modify the
listener on activeImage so it verifies the image is still the current active
slide before calling setCarouselWidth. Concretely, inside the 'load' callback
registered on activeImage, compare the event.target (or the captured
activeImage) against the current active slide image (e.g., query the carousel
for the currently active image or compare to a stored current reference) and
only call setCarouselWidth(activeImage) if they match; keep the { once: true }
behavior to avoid lingering listeners.

Comment on lines +127 to +139
// Keyboard navigation
carousel.addEventListener('keydown', function(e) {
if (e.key === 'ArrowLeft') {
prevSlide();
} else if (e.key === 'ArrowRight') {
nextSlide();
}
});

// Make carousel focusable for keyboard navigation

// Initialize the first slide state on load
showSlide(0);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find HTML files that might contain the carousel
git ls-files | grep -E "\.(html|htm)$"

Repository: XCSoar/website

Length of output: 576


🏁 Script executed:

# Search for the carousel element in the codebase
rg "screenshot-carousel" -S --max-count=20

Repository: XCSoar/website

Length of output: 458


🏁 Script executed:

# Check the full context of the screenshot-carousel.js file
wc -l lib/screenshot-carousel.js

Repository: XCSoar/website

Length of output: 88


🏁 Script executed:

# Check the HTML markup for screenshot-carousel in index.html
rg -n "screenshot-carousel" index.html -B5 -A10

Repository: XCSoar/website

Length of output: 885


🏁 Script executed:

# Read the full JavaScript file to understand the focusability setup
cat -n lib/screenshot-carousel.js

Repository: XCSoar/website

Length of output: 6564


The carousel lacks keyboard focusability — arrow-key navigation will not work. The keydown handler is attached (lines 127–134) but the carousel element itself is not focusable. Without tabindex="0" or equivalent, the div will never receive focus, so keyboard events won't fire. Add focusability in JavaScript to ensure accessibility:

Suggested fix
     // Keyboard navigation
     carousel.addEventListener('keydown', function(e) {
       if (e.key === 'ArrowLeft') {
         prevSlide();
       } else if (e.key === 'ArrowRight') {
         nextSlide();
       }
     });
 
     // Make carousel focusable for keyboard navigation
+    if (!carousel.hasAttribute('tabindex')) {
+      carousel.setAttribute('tabindex', '0');
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Keyboard navigation
carousel.addEventListener('keydown', function(e) {
if (e.key === 'ArrowLeft') {
prevSlide();
} else if (e.key === 'ArrowRight') {
nextSlide();
}
});
// Make carousel focusable for keyboard navigation
// Initialize the first slide state on load
showSlide(0);
// Keyboard navigation
carousel.addEventListener('keydown', function(e) {
if (e.key === 'ArrowLeft') {
prevSlide();
} else if (e.key === 'ArrowRight') {
nextSlide();
}
});
// Make carousel focusable for keyboard navigation
if (!carousel.hasAttribute('tabindex')) {
carousel.setAttribute('tabindex', '0');
}
// Initialize the first slide state on load
showSlide(0);
🤖 Prompt for AI Agents
In `@lib/screenshot-carousel.js` around lines 127 - 139, The carousel's keydown
handler won't run because the carousel element isn't focusable; make the element
referenced as carousel focusable (e.g., set carousel.tabIndex = 0 or
carousel.setAttribute('tabindex','0')) before relying on the
addEventListener('keydown') so ArrowLeft/ArrowRight events fire and
prevSlide()/nextSlide() work; also consider adding an accessible label
(aria-label or role) for clarity and keep showSlide(0) initialization as-is.

Comment on lines +7 to +12
- **Static Site Generator**: Jekyll 4.4.1
- **Ruby Version**: 3.4.8
- **Theme**: Custom (not using a standard Jekyll theme)
- **CSS Framework**: Bootstrap (customized)
- **JavaScript**: jQuery 1.8.2, custom scripts

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's check if WEBSITE_STRUCTURE.md exists and read the relevant sections
find . -name "WEBSITE_STRUCTURE.md" -type f

Repository: XCSoar/website

Length of output: 80


🏁 Script executed:

# Search for jQuery and jquery-lightbox references across the repo
rg "jquery-1\.8\.2|jquery-lightbox|jQuery.*lightbox" -i --no-heading

Repository: XCSoar/website

Length of output: 4159


🏁 Script executed:

# Also check for any jQuery references more broadly to understand scope
rg "jQuery|jquery" -i --no-heading -c | head -20

Repository: XCSoar/website

Length of output: 303


🏁 Script executed:

# Let's read the WEBSITE_STRUCTURE.md file to see current state at the mentioned lines
head -15 ./WEBSITE_STRUCTURE.md

Repository: XCSoar/website

Length of output: 509


🏁 Script executed:

# Check lines 87-92
sed -n '87,92p' ./WEBSITE_STRUCTURE.md

Repository: XCSoar/website

Length of output: 335


🏁 Script executed:

# Check line 106
sed -n '106p' ./WEBSITE_STRUCTURE.md

Repository: XCSoar/website

Length of output: 113


🏁 Script executed:

# Check lines 271-274
sed -n '271,274p' ./WEBSITE_STRUCTURE.md

Repository: XCSoar/website

Length of output: 250


🏁 Script executed:

# Verify if jQuery and jquery-lightbox are actually still in the codebase
ls -la lib/ | grep -E "jquery|lightbox"

Repository: XCSoar/website

Length of output: 258


Update WEBSITE_STRUCTURE.md to reflect vanilla JS lightbox instead of jQuery. The documentation still lists jQuery 1.8.2 and jquery-lightbox across multiple sections (lines 7–12, 87–92, 106, 271–274), but the PR introduces a new vanilla ES6+ lightbox implementation. Update these sections to document the new setup and avoid misleading contributors.

🛠️ Example edits
- - **JavaScript**: jQuery 1.8.2, custom scripts
+ - **JavaScript**: Vanilla ES6+ (lightbox/carousel), custom scripts

- │   ├── jquery-1.8.2.min.js # jQuery
- │   └── jquery-lightbox/    # Lightbox plugin
+ │   ├── lightbox.js         # Vanilla lightbox
+ │   └── screenshot-carousel.js # Screenshot carousel

- - **Features**: Screenshot gallery with jQuery lightbox
+ - **Features**: Screenshot gallery with vanilla JS lightbox

- - **JavaScript**: jQuery 1.8.2, custom scripts
- - **Libraries**: Bootstrap, jQuery Lightbox, country flags
+ - **JavaScript**: Vanilla ES6+, custom scripts
+ - **Libraries**: Bootstrap, lightbox, country flags
🤖 Prompt for AI Agents
In `@WEBSITE_STRUCTURE.md` around lines 7 - 12, The doc still lists jQuery 1.8.2
and jquery-lightbox even though the PR added a vanilla ES6+ lightbox; update
WEBSITE_STRUCTURE.md to remove references to jQuery and jquery-lightbox and
document the new setup: replace the "JavaScript: jQuery 1.8.2, custom scripts"
line with "JavaScript: Vanilla ES6+ lightbox, custom scripts" (and note no
jQuery dependency) and similarly change the other sections that mention
jquery-lightbox (lines referenced in the review) to describe the new ES6
module/lightbox implementation and any initialization details or build/import
steps so contributors aren’t misled.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TASK: replace jquery lightbox with generic lightbox example

1 participant