Skip to content

Conversation

@tripledoublev
Copy link
Owner

@tripledoublev tripledoublev commented Dec 30, 2025

footnotes component and links found by claude

Summary by CodeRabbit

Release Notes

  • New Features
    • Added interactive footnotes system throughout the site with numbered references and a dedicated references section.
    • Footnotes now available in both English and French language versions.
    • Enhanced user experience with hover and focus interactions for footnote links and references, including responsive styling and smooth animations.

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

- Created Footnotes.svelte component for displaying reference links
- Added footnoteData.js with comprehensive mappings of roles to external references
- Integrated footnotes into English and French homepages
- Added hover interactions to display footnote details
- Included references to artist portfolios, projects, and affiliated organizations
- Supports both languages (EN/FR) with localized content
@coderabbitai
Copy link

coderabbitai bot commented Dec 30, 2025

📝 Walkthrough

Walkthrough

This pull request introduces a footnotes system by creating a new Svelte component for rendering footnotes and references, a data module providing role-based footnote content in English and French, and integrating footnote functionality into both language-specific route pages with interactive role elements.

Changes

Cohort / File(s) Summary
Footnotes Component
src/components/Footnotes.svelte
New component accepting footnotes array and currentFootnote index props. Renders a floating footnote badge when a valid footnote is selected and displays a references list below. Includes slide-up animation and responsive styling.
Footnotes Data Module
src/lib/footnoteData.js
New module exporting roleFootnotes constant (bilingual role-to-footnote mapping), getFootnoteForRole(role, language) helper, and getAllFootnotes(language) helper for data retrieval.
English & French Route Integration
src/routes/en/+page.svelte, src/routes/fr/+page.svelte
Both pages now import Footnotes component and data helpers. Add state management (currentFootnote, allFootnotes) and helper functions (showFootnote, hideFootnote, getFootnoteNumber). Replace previous single expanded text display with per-role interactive footnote spans. Render Footnotes component at page bottom. Add styling for interactive role links and footnote markers.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Page as Route Page<br/>(en/fr)
    participant Footnotes as Footnotes<br/>Component
    participant Data as footnoteData<br/>Module

    User->>Page: Hover/Focus on role span
    Page->>Page: showFootnote(roleIndex)
    Page->>Data: getFootnoteForRole(role, lang)
    Data-->>Page: footnote object with refs
    Page->>Page: Set currentFootnote state
    Page->>Footnotes: Pass currentFootnote & all footnotes
    Footnotes-->>User: Display footnote badge & references

    User->>Page: Leave role element
    Page->>Page: hideFootnote()
    Page->>Page: Clear currentFootnote
    Footnotes-->>User: Hide footnote display
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

Hopping through references with care,
Each footnote linked with flair,
En français et anglais so bright,
Interactive roles catch the light,
🐰 footnotes everywhere!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add reference links' accurately summarizes the main change—introducing a footnotes/references system with linked references throughout the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ 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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/routes/en/+page.svelte (1)

237-275: Add keyboard event handlers for accessibility.

The interactive role spans have role="button" and tabindex="0" but are missing keyboard event handlers. Keyboard-only users cannot activate these elements with Enter or Space keys.

🔎 Proposed fix to add keyboard support

Add a helper function and wire it to the keyboard event:

 	function getFootnoteNumber(role) {
 		const footnote = getFootnoteForRole(role, 'en');
 		return footnote ? footnote.number : null;
 	}
+
+	function handleKeydown(event, role) {
+		if (event.key === 'Enter' || event.key === ' ') {
+			event.preventDefault();
+			showFootnote(role);
+		}
+	}

Then update the expanded state template:

 					<span
 						class="role-text role-link"
 						on:mouseenter={() => showFootnote(role)}
 						on:mouseleave={hideFootnote}
 						on:focus={() => showFootnote(role)}
 						on:blur={hideFootnote}
+						on:keydown={(e) => handleKeydown(e, role)}
 						role="button"
 						tabindex="0"
 					>

And the collapsed state template:

 				<span
 					class="role-text role-link"
 					on:mouseenter={() => showFootnote(remainingRoles[roleIndex])}
 					on:mouseleave={hideFootnote}
 					on:focus={() => showFootnote(remainingRoles[roleIndex])}
 					on:blur={hideFootnote}
+					on:keydown={(e) => handleKeydown(e, remainingRoles[roleIndex])}
 					role="button"
 					tabindex="0"
 				>
src/routes/fr/+page.svelte (1)

237-275: Add keyboard event handlers for accessibility.

Similar to the English version, the interactive role spans are missing keyboard event handlers. Keyboard-only users cannot activate these elements with Enter or Space keys.

🔎 Proposed fix to add keyboard support

Add a helper function:

 	function getFootnoteNumber(role) {
 		const footnote = getFootnoteForRole(role, 'fr');
 		return footnote ? footnote.number : null;
 	}
+
+	function handleKeydown(event, role) {
+		if (event.key === 'Enter' || event.key === ' ') {
+			event.preventDefault();
+			showFootnote(role);
+		}
+	}

Then update both the expanded and collapsed state templates with on:keydown handlers (similar to the English version fix).

🧹 Nitpick comments (4)
src/routes/en/+page.svelte (1)

221-224: Consider memoizing getFootnoteNumber results.

The getFootnoteNumber function is called in the template for every render, which means repeated lookups of the same role-to-number mapping. For better performance, consider computing all role numbers once during initialization.

🔎 Proposed optimization

Add a computed map during initialization:

 	let currentFootnote = null;
 	let allFootnotes = {};
+	let roleNumbers = {};
 
 	const roles = [

Initialize in onMount:

 		// Initialize footnotes
 		allFootnotes = getAllFootnotes('en');
+		roleNumbers = roles.reduce((acc, role) => {
+			const footnote = getFootnoteForRole(role, 'en');
+			if (footnote) acc[role] = footnote.number;
+			return acc;
+		}, {});

Update the helper:

 	function getFootnoteNumber(role) {
-		const footnote = getFootnoteForRole(role, 'en');
-		return footnote ? footnote.number : null;
+		return roleNumbers[role] || null;
 	}
src/components/Footnotes.svelte (1)

24-36: Consider sorting footnotes by number for consistent ordering.

The footnotes list uses Object.entries(footnotes) which relies on JavaScript object key ordering. While modern JavaScript maintains insertion order, explicitly sorting by the footnote number would make the ordering intention clearer and more resilient.

🔎 Proposed refinement
 		<ol class="footnotes-list">
-			{#each Object.entries(footnotes) as [number, links]}
+			{#each Object.entries(footnotes).sort(([a], [b]) => Number(a) - Number(b)) as [number, links]}
 				<li id="footnote-{number}" class="footnote-item">
src/lib/footnoteData.js (1)

2-321: Consider extracting data to separate JSON files.

The roleFootnotes object contains a large amount of data (320 lines) embedded in the JavaScript module. For better maintainability and potential reusability, consider moving this data to separate JSON files (e.g., footnotes.en.json and footnotes.fr.json).

This would:

  • Separate data from logic
  • Make it easier to update URLs and references without touching code
  • Enable potential runtime loading or caching strategies
  • Allow non-developers to update content more easily
🔎 Example structure

Create src/lib/data/footnotes.en.json:

{
  "an intermedia artist": {
    "number": 1,
    "links": [
      {
        "title": "Vincent Charlebois: Intermedia Artist Portfolio",
        "url": "https://www.vincentcharlebois.net/"
      }
    ]
  }
}

Then import in the module:

import footnotesEn from './data/footnotes.en.json';
import footnotesFr from './data/footnotes.fr.json';

export const roleFootnotes = {
  en: footnotesEn,
  fr: footnotesFr
};
src/routes/fr/+page.svelte (1)

248-251: Consider memoizing getFootnoteNumber results.

Same optimization opportunity as the English version - the getFootnoteNumber function is called repeatedly in the template. Consider computing all role numbers once during initialization.

See the English version comment for the detailed implementation approach.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee86937 and 321187a.

📒 Files selected for processing (4)
  • src/components/Footnotes.svelte
  • src/lib/footnoteData.js
  • src/routes/en/+page.svelte
  • src/routes/fr/+page.svelte
🔇 Additional comments (4)
src/components/Footnotes.svelte (2)

6-19: Verify currentFootnote exists in footnotes object.

The condition checks currentFootnote is truthy and footnotes[currentFootnote] exists, which is correct. However, if currentFootnote is set to a number that doesn't exist as a key in the footnotes object, the second check prevents errors.

The defensive check footnotes[currentFootnote] properly guards against undefined access.


41-55: The footnote container's fixed positioning is appropriately isolated. The only other fixed-position element in the codebase is the .skip-to-main accessibility link in src/routes/+layout.svelte, which is positioned off-screen (left: -9999px) and has a higher z-index (9999), so there are no visual conflicts or layering issues.

src/lib/footnoteData.js (1)

324-328: Helper function implementation is clean and defensive.

The getFootnoteForRole function properly handles missing language data and missing role entries by returning null. The default parameter value for language is a good practice.

src/routes/fr/+page.svelte (1)

1-399: Consistent implementation across language variants.

The French page implementation correctly mirrors the English version with appropriate language-specific data ('fr' parameter). This consistency makes the codebase easier to maintain.

Comment on lines +1 to +321
// Mapping of roles to their reference links
export const roleFootnotes = {
en: {
'an intermedia artist': {
number: 1,
links: [
{
title: 'Vincent Charlebois: Intermedia Artist Portfolio',
url: 'https://www.vincentcharlebois.net/'
},
{
title: 'Art Souterrain Festival 2024',
url: 'https://festival2024.artsouterrain.com/en/artistes/charlebois-vincent/'
},
{
title: 'ISEA Symposium Archives',
url: 'https://isea-archives.siggraph.org/person/vincent-charlebois/'
},
{
title: 'Dare-Dare Gallery Event',
url: 'https://dare-dare.org/en/events/vincent-charlebois'
},
{
title: 'Cargo Collective Portfolio',
url: 'https://cargocollective.com/tripledoublev'
}
]
},
'a software developer': {
number: 2,
links: [
{
title: 'Hypha Worker Co-operative - People',
url: 'https://hypha.coop/people/'
},
{
title: 'Charlebois Solutions',
url: 'https://www.charlebois.solutions/'
},
{
title: 'Personal Website',
url: 'https://vincent.charlebois.info/en/'
}
]
},
'a tree planter': {
number: 3,
links: [
{
title: 'ffforests - Fabricating Future Forests',
url: 'https://www.ffforests.xyz/v/'
},
{
title: 'Tree Planting Instagram Highlights',
url: 'https://www.instagram.com/stories/highlights/18089075134342983/'
},
{
title: 'Tree Planting Documentary',
url: 'https://www.youtube.com/watch?v=CO6aJd9X1Ms'
},
{
title: 'La Fabrication de Forêts Futures - Quartier des Spectacles',
url: 'https://www.quartierdesspectacles.com/en/blog/886/la-fabrication-de-forets-futures-a-meditative-journey-into-synthetic-forests'
}
]
},
'an internet explorateur': {
number: 4,
links: [
{
title: 'dailywiki on Rhizome ArtBase',
url: 'https://rhizome.org/editorial/2023/feb/02/announcing-some-tumblrs/'
},
{
title: 'Rhizome ArtBase Collection',
url: 'https://artbase.rhizome.org/'
}
]
},
'a full-stack developer': {
number: 5,
links: [
{
title: 'Hypha Worker Co-operative',
url: 'https://hypha.coop/'
},
{
title: 'Charlebois Solutions',
url: 'https://www.charlebois.solutions/'
}
]
},
'a performance artist': {
number: 6,
links: [
{
title: 'Vincent Charlebois: Intermedia Artist',
url: 'https://www.vincentcharlebois.net/'
},
{
title: 'Dare-Dare Performance',
url: 'https://dare-dare.org/en/events/vincent-charlebois'
}
]
},
'a member of hypha worker co-op': {
number: 7,
links: [
{
title: 'Hypha - People',
url: 'https://hypha.coop/people/'
},
{
title: 'Hypha Worker Co-operative',
url: 'https://hypha.coop/'
},
{
title: 'Hypha Governance Experiments',
url: 'https://datacommunities.ca/toolkit/hypha-worker-cooperative-governance-experiments/'
}
]
},
'a cooperative technologist': {
number: 8,
links: [
{
title: 'Hypha Worker Co-operative',
url: 'https://hypha.coop/'
},
{
title: 'Personal Website',
url: 'https://vincent.charlebois.info/en/'
}
]
},
'an artist-researcher': {
number: 9,
links: [
{
title: 'Main Portfolio - Ecologies, Technologies',
url: 'https://www.vincentcharlebois.com/'
},
{
title: 'Projects',
url: 'https://www.vincentcharlebois.com/projects/'
},
{
title: 'About',
url: 'https://www.vincentcharlebois.com/about/'
},
{
title: 'La Couleur de Montréal',
url: 'https://vincent.charlebois.info/en/couleur/'
},
{
title: 'La Couleur de Montréal - FIFA',
url: 'https://lefifa.com/catalogue/la-couleur-de-montreal'
}
]
}
},
fr: {
'un artiste intermédiatique': {
number: 1,
links: [
{
title: 'Vincent Charlebois : Artiste intermédiatique',
url: 'https://www.vincentcharlebois.net/'
},
{
title: 'Festival Art Souterrain 2024',
url: 'https://festival2024.artsouterrain.com/en/artistes/charlebois-vincent/'
},
{
title: 'Archives du Symposium ISEA',
url: 'https://isea-archives.siggraph.org/person/vincent-charlebois/'
},
{
title: 'Événement Dare-Dare',
url: 'https://dare-dare.org/en/events/vincent-charlebois'
},
{
title: 'Portfolio Cargo Collective',
url: 'https://cargocollective.com/tripledoublev'
}
]
},
'un développeur logiciel': {
number: 2,
links: [
{
title: 'Hypha Worker Co-operative - Équipe',
url: 'https://hypha.coop/people/'
},
{
title: 'Charlebois Solutions',
url: 'https://www.charlebois.solutions/'
},
{
title: 'Site personnel',
url: 'https://vincent.charlebois.info/fr/'
}
]
},
"un planteur d'arbres": {
number: 3,
links: [
{
title: 'ffforests - Fabrication de forêts futures',
url: 'https://www.ffforests.xyz/v/'
},
{
title: 'Moments Instagram - Plantation',
url: 'https://www.instagram.com/stories/highlights/18089075134342983/'
},
{
title: 'Documentaire - Plantation',
url: 'https://www.youtube.com/watch?v=CO6aJd9X1Ms'
},
{
title: 'La Fabrication de Forêts Futures - Quartier des Spectacles',
url: 'https://www.quartierdesspectacles.com/en/blog/886/la-fabrication-de-forets-futures-a-meditative-journey-into-synthetic-forests'
}
]
},
"un explorateur de l'internet": {
number: 4,
links: [
{
title: 'dailywiki sur Rhizome ArtBase',
url: 'https://rhizome.org/editorial/2023/feb/02/announcing-some-tumblrs/'
},
{
title: 'Collection Rhizome ArtBase',
url: 'https://artbase.rhizome.org/'
}
]
},
'un développeur full-stack': {
number: 5,
links: [
{
title: 'Hypha Worker Co-operative',
url: 'https://hypha.coop/'
},
{
title: 'Charlebois Solutions',
url: 'https://www.charlebois.solutions/'
}
]
},
'un artiste performatif': {
number: 6,
links: [
{
title: 'Vincent Charlebois : Artiste intermédiatique',
url: 'https://www.vincentcharlebois.net/'
},
{
title: 'Performance Dare-Dare',
url: 'https://dare-dare.org/en/events/vincent-charlebois'
}
]
},
'un membre de hypha worker co-op': {
number: 7,
links: [
{
title: 'Hypha - Équipe',
url: 'https://hypha.coop/people/'
},
{
title: 'Hypha Worker Co-operative',
url: 'https://hypha.coop/'
},
{
title: 'Expériences de gouvernance Hypha',
url: 'https://datacommunities.ca/toolkit/hypha-worker-cooperative-governance-experiments/'
}
]
},
'un technologue coopératif': {
number: 8,
links: [
{
title: 'Hypha Worker Co-operative',
url: 'https://hypha.coop/'
},
{
title: 'Site personnel',
url: 'https://vincent.charlebois.info/fr/'
}
]
},
'un artiste-chercheur': {
number: 9,
links: [
{
title: 'Portfolio principal - Écologies, technologies',
url: 'https://www.vincentcharlebois.com/'
},
{
title: 'Projets',
url: 'https://www.vincentcharlebois.com/projects/'
},
{
title: 'À propos',
url: 'https://www.vincentcharlebois.com/about/'
},
{
title: 'La Couleur de Montréal',
url: 'https://vincent.charlebois.info/fr/couleur/'
},
{
title: 'La Couleur de Montréal - FIFA',
url: 'https://lefifa.com/catalogue/la-couleur-de-montreal'
}
]
}
}
};
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
# Extract and check all URLs from the footnoteData.js file

echo "=== Extracting all URLs from footnoteData.js ==="
rg -oN "https?://[^\"]+" src/lib/footnoteData.js | sort -u > /tmp/urls.txt

echo -e "\n=== Checking for HTTP (non-HTTPS) URLs ==="
grep "^http://" /tmp/urls.txt || echo "All URLs use HTTPS ✓"

echo -e "\n=== List of all unique URLs ==="
cat /tmp/urls.txt

echo -e "\n=== Total unique URLs ==="
wc -l < /tmp/urls.txt

echo -e "\n=== Testing URL accessibility ==="
while read url; do
  echo -n "Testing $url ... "
  http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null)
  if [ "$http_code" = "200" ] || [ "$http_code" = "301" ] || [ "$http_code" = "302" ]; then
    echo "✓ HTTP $http_code"
  else
    echo "✗ HTTP $http_code"
  fi
done < /tmp/urls.txt

Repository: tripledoublev/vincentcharlebois.com

Length of output: 3101


🏁 Script executed:

#!/bin/bash
# Verify actual URL format in the source file
echo "=== Checking actual URL format in source file ==="
head -100 src/lib/footnoteData.js | grep -A 2 "url:"

echo -e "\n=== Re-extract URLs with corrected pattern ==="
# Match URLs within quotes more precisely
rg -oP "(?<=url.*?')[https://[^']+(?=')" src/lib/footnoteData.js | sort -u > /tmp/urls_corrected.txt

echo "=== Corrected unique URLs ==="
cat /tmp/urls_corrected.txt

echo -e "\n=== Testing corrected URLs for accessibility ==="
while read url; do
  echo -n "Testing $url ... "
  http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null)
  if [ "$http_code" = "200" ] || [ "$http_code" = "301" ] || [ "$http_code" = "302" ]; then
    echo "✓ HTTP $http_code"
  elif [ "$http_code" = "403" ]; then
    echo "⚠ HTTP $http_code (forbidden/blocked)"
  else
    echo "✗ HTTP $http_code"
  fi
done < /tmp/urls_corrected.txt

Repository: tripledoublev/vincentcharlebois.com

Length of output: 1628


🏁 Script executed:

#!/bin/bash
echo "=== Extracting URLs correctly using grep and sed ==="
grep -oP "url: '\K[^']+" src/lib/footnoteData.js | sort -u > /tmp/urls_clean.txt

echo "=== Cleaned unique URLs ==="
cat /tmp/urls_clean.txt

echo -e "\n=== Total unique URLs ==="
wc -l < /tmp/urls_clean.txt

echo -e "\n=== Testing URL accessibility (with proper URLs) ==="
while read url; do
  echo -n "Testing $url ... "
  http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null)
  if [ "$http_code" = "200" ] || [ "$http_code" = "301" ] || [ "$http_code" = "302" ]; then
    echo "✓ HTTP $http_code"
  elif [ "$http_code" = "403" ]; then
    echo "⚠ HTTP $http_code (forbidden)"
  else
    echo "✗ HTTP $http_code"
  fi
done < /tmp/urls_clean.txt

echo -e "\n=== Summary of broken URLs ==="
broken_count=0
while read url; do
  http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" 2>/dev/null)
  if ! [[ "$http_code" =~ ^(200|301|302)$ ]]; then
    echo "  - $url (HTTP $http_code)"
    ((broken_count++))
  fi
done < /tmp/urls_clean.txt
echo "Total broken URLs: $broken_count"

Repository: tripledoublev/vincentcharlebois.com

Length of output: 3232


Fix two inaccessible URLs in footnoteData.js.

All 23 URLs use HTTPS correctly, but 2 are not currently accessible and should be verified or removed:

  • https://isea-archives.siggraph.org/person/vincent-charlebois/ returns HTTP 404 (appears in "an intermedia artist" role)
  • https://rhizome.org/editorial/2023/feb/02/announcing-some-tumblrs/ returns HTTP 403 (appears in "an internet explorateur" role)

The remaining 21 URLs are accessible and valid (HTTP 200, 301, or 302).

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.

3 participants