diff --git a/contributors.json b/contributors.json
new file mode 100644
index 0000000..d8c9df6
--- /dev/null
+++ b/contributors.json
@@ -0,0 +1,14 @@
+[
+ {
+ "name": "Kiya Rose",
+ "url": "https://kiya.cat",
+ "avatar": "https://avatars.githubusercontent.com/u/75678535?v=4",
+ "label": "Kiya Rose — Portfolio"
+ },
+ {
+ "name": "Krystal Rose",
+ "url": "https://enby.fun",
+ "avatar": "https://avatars.githubusercontent.com/u/32627918?v=4",
+ "label": "Krystal Rose — Website"
+ }
+]
diff --git a/index.html b/index.html
index 3ffd3d4..0c45d3f 100644
--- a/index.html
+++ b/index.html
@@ -76,6 +76,8 @@
+
+
diff --git a/links.json b/links.json
index 31cde1a..8c95d2d 100644
--- a/links.json
+++ b/links.json
@@ -1,10 +1,7 @@
[
{
- "title": "Kiya Rose — Portfolio",
- "url": "/kiya"
- },
- {
- "title": "Krystal Rose — Website",
- "url": "/krystal"
+ "title": "Projects",
+ "url": "https://projects.sillylittle.tech",
+ "subtitle": "Explore our open-source projects"
}
]
diff --git a/script.js b/script.js
index 2a8bf44..9fcbe44 100644
--- a/script.js
+++ b/script.js
@@ -15,6 +15,10 @@ document.addEventListener('DOMContentLoaded', () => {
debugLog('Calling renderLinks')
renderLinks('#linksContainer', 'links.json')
+ // Render contributor icons from contributors.json
+ debugLog('Calling renderContributors')
+ renderContributors('#contributorsContainer', 'contributors.json')
+
// Render footer from includes/footer.html
debugLog('Calling renderFooter')
renderFooter('#siteFooter', 'includes/footer.html')
@@ -75,6 +79,47 @@ function renderLinks (containerSelector, jsonPath) {
})
}
+/**
+ * Fetches a JSON file containing contributor data and renders round profile icon links.
+ * Each contributor object may have: { name, url, avatar, label }
+ */
+function renderContributors (containerSelector, jsonPath) {
+ const container = document.querySelector(containerSelector)
+ if (!container) return
+ debugLog(`renderContributors: fetching ${jsonPath}`)
+
+ fetch(jsonPath)
+ .then((res) => {
+ if (!res.ok) throw new Error(`Failed to load ${jsonPath}`)
+ return res.json()
+ })
+ .then((contributors) => {
+ container.innerHTML = ''
+ debugLog(`renderContributors: got ${contributors.length} contributors`)
+
+ contributors.forEach((contributor) => {
+ const anchor = document.createElement('a')
+ anchor.className = 'contributor-icon'
+ anchor.href = contributor.url || '#'
+ anchor.target = '_blank'
+ anchor.rel = 'noopener noreferrer'
+ anchor.setAttribute('aria-label', contributor.label || contributor.name || contributor.url || 'Contributor')
+
+ const img = document.createElement('img')
+ img.src = contributor.avatar || ''
+ img.alt = contributor.name || ''
+
+ anchor.appendChild(img)
+ container.appendChild(anchor)
+ })
+ })
+ .catch((err) => {
+ const msg = err?.message ?? 'unknown error'
+ console.error('Error rendering contributors:', err)
+ debugLog(`renderContributors error: ${msg}`)
+ })
+}
+
/**
* Fetches an HTML file and injects its content into the target element.
* Falls back to the original static footer markup already in the page if the fetch fails.
diff --git a/site.css b/site.css
index 510ec3d..03eba6c 100644
--- a/site.css
+++ b/site.css
@@ -158,6 +158,38 @@ footer.site-footer a {
font-size: 14px;
}
+/* Contributor profile icons — displayed above the links list in the right section */
+.contributor-icons {
+ display: flex;
+ flex-direction: row;
+ gap: 10px;
+ margin-bottom: 16px;
+}
+.contributor-icon {
+ width: 44px;
+ height: 44px;
+ border-radius: 50%;
+ overflow: hidden;
+ border: 2px solid var(--border-color);
+ background: var(--card-bg);
+ display: block;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
+ transition:
+ transform 0.18s ease,
+ box-shadow 0.18s ease;
+ text-decoration: none;
+}
+.contributor-icon:hover {
+ transform: scale(1.12);
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.2);
+}
+.contributor-icon img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+}
+
@media (max-width: 768px) {
.container {
grid-template-columns: 1fr;