From 351db5b2f38223fba10bbe4e0869b34ce4bbea43 Mon Sep 17 00:00:00 2001 From: Achref Soua Date: Sun, 21 Jun 2026 17:04:38 +0200 Subject: [PATCH 1/2] release: promote develop to main (v4) (#7) * feat: Medium articles, timeline animation, full FR i18n, project links (#4) - Add Medium RSS feed (via rss2json proxy) displayed as article cards below the GitHub repos grid; fetched in parallel with GitHub API - Replace broken CSS stagger (--stagger custom property) with a proper container-level IntersectionObserver + 220ms setTimeout stagger; timeline items now slide in from the left, one by one, on scroll - Add data/i18n.fr.json with complete French translations for every content section: experience highlights, education degrees, skill groups, focus areas, metrics, publications, and all 10 project summaries/highlights/impact lines - Extend applyLang() to merge all FR content (not just nav/hero/chrome) and initLangToggle() to re-render every section on language switch - Wire project links for AI Resume Tailor, FinOps Multi-Agent, Cardiac MRI Diagnosis, Adaptive Nutrition Planner (GitHub) and Satellite Algal Bloom Detection (DrivenData competition) - Bump cache version to v=20260621-3 * fix: Open Source filter shows all projects, Pulse in LLM Systems, Medium always visible, Edge AI publication link (#6) - Open Source filter now shows all 10 projects (acts as a broader philosophy tab rather than a category sub-filter) - Pulse moved to LLM Systems category so it appears in that filter tab - Medium section always renders: heading row + 'View all' profile link regardless of RSS fetch result; shows placeholder text if no articles load rather than silently disappearing - Edge AI Gait Analyzer: add publication link to GEEE 2024 paper - Cache bump to v=20260621-4 --- assets/css/style.css | 29 ++++++++++++++++++++++++++++- assets/js/main.js | 34 ++++++++++++++++++++++++---------- data/projects.json | 5 +++-- index.html | 14 +++++++------- 4 files changed, 62 insertions(+), 20 deletions(-) diff --git a/assets/css/style.css b/assets/css/style.css index 8271ae4..323deae 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -1208,13 +1208,40 @@ h3 { } /* ── Medium articles ──────────────────────────────────── */ +.gh-medium-heading { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; + margin: 32px 0 14px; +} + .gh-sub-heading { font-size: 1rem; font-weight: 650; - margin: 32px 0 14px; + margin: 0; letter-spacing: 0; } +.gh-medium-all { + font-size: 0.82rem; + color: var(--muted); + text-decoration: none; + white-space: nowrap; +} + +.gh-medium-all:hover { + color: var(--text); + text-decoration: underline; + text-underline-offset: 3px; +} + +.gh-medium-placeholder { + font-size: 0.88rem; + color: var(--muted); + margin: 0; +} + .medium-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); diff --git a/assets/js/main.js b/assets/js/main.js index a3a5190..47f7781 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,8 +1,8 @@ const paths = { - site: 'data/site.json?v=20260621-3', - resume: 'data/resume.json?v=20260621-3', - projects: 'data/projects.json?v=20260621-3', - i18nFr: 'data/i18n.fr.json?v=20260621-3' + site: 'data/site.json?v=20260621-4', + resume: 'data/resume.json?v=20260621-4', + projects: 'data/projects.json?v=20260621-4', + i18nFr: 'data/i18n.fr.json?v=20260621-4' }; const state = { @@ -246,7 +246,7 @@ function renderProjects() { container.replaceChildren(); const source = state.displayProjects || state.projects; - const projects = state.activeProjectFilter === 'All' + const projects = (state.activeProjectFilter === 'All' || state.activeProjectFilter === 'Open Source') ? source : source.filter((project) => project.category === state.activeProjectFilter); @@ -532,12 +532,25 @@ async function fetchMediumArticles() { return data.items || []; } -function renderMediumArticles(articles, container) { - if (!articles.length) return; +const MEDIUM_URL = 'https://achref-soua.medium.com/'; +function renderMediumArticles(articles, container) { + const headingRow = createElement('div', 'gh-medium-heading'); const heading = createElement('h3', 'gh-sub-heading', 'Latest on Medium'); - const grid = createElement('div', 'medium-grid'); + const allLink = createElement('a', 'gh-medium-all', 'View all →'); + allLink.href = MEDIUM_URL; + allLink.target = '_blank'; + allLink.rel = 'noopener noreferrer'; + headingRow.append(heading, allLink); + container.append(headingRow); + + if (!articles.length) { + const placeholder = createElement('p', 'gh-medium-placeholder', 'Articles loading — or visit Medium directly.'); + container.append(placeholder); + return; + } + const grid = createElement('div', 'medium-grid'); articles.slice(0, 4).forEach(article => { const card = addReveal(createElement('article', 'medium-card')); @@ -556,7 +569,7 @@ function renderMediumArticles(articles, container) { grid.append(card); }); - container.append(heading, grid); + container.append(grid); } async function renderGitHubDashboard() { @@ -628,11 +641,12 @@ async function renderGitHubDashboard() { }); container.replaceChildren(statsStrip, grid); - renderMediumArticles(articles, container); + renderMediumArticles(articles || [], container); observeRevealItems(); } catch { const msg = createElement('p', 'gh-error', 'GitHub stats unavailable — try again later.'); container.replaceChildren(msg); + renderMediumArticles([], container); } } diff --git a/data/projects.json b/data/projects.json index b6bce04..a508796 100644 --- a/data/projects.json +++ b/data/projects.json @@ -55,7 +55,7 @@ { "title": "Pulse", "featured": true, - "category": "Medical AI", + "category": "LLM Systems", "range": "2025", "role": "Author", "summary": "Clinical decision support for endovascular surgery. Full-stack app (Next.js 15 + FastAPI) with hybrid RAG over Quiver and a streaming multi-agent copilot via Groq.", @@ -146,7 +146,8 @@ ], "stack": ["Arduino", "IMU", "LSTM", "GRU", "Transformers", "Autoencoders", "Python"], "impact": "Published at GEEE 2024. Edge-ready AI for bionic rehabilitation.", - "link": "#" + "link": "https://cnpsi.com/PBS/Vol12/GEEE-12.pdf", + "linkLabel": "View Publication" }, { "title": "Adaptive Nutrition Planner", diff --git a/index.html b/index.html index ab68855..a844f89 100644 --- a/index.html +++ b/index.html @@ -38,13 +38,13 @@ /> - - - - - + + + + + - + @@ -241,6 +241,6 @@

Let us build something useful.

- + From 1197baa215bf6b79423ca3a7fe5d373be6674ae1 Mon Sep 17 00:00:00 2001 From: achref-soua Date: Sun, 21 Jun 2026 17:13:33 +0200 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20remove=20count=20param=20from=20rss2?= =?UTF-8?q?json=20=E2=80=94=20requires=20paid=20API=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/js/main.js | 10 +++++----- index.html | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/assets/js/main.js b/assets/js/main.js index 47f7781..b568967 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -1,8 +1,8 @@ const paths = { - site: 'data/site.json?v=20260621-4', - resume: 'data/resume.json?v=20260621-4', - projects: 'data/projects.json?v=20260621-4', - i18nFr: 'data/i18n.fr.json?v=20260621-4' + site: 'data/site.json?v=20260621-5', + resume: 'data/resume.json?v=20260621-5', + projects: 'data/projects.json?v=20260621-5', + i18nFr: 'data/i18n.fr.json?v=20260621-5' }; const state = { @@ -526,7 +526,7 @@ const LANG_COLORS = { async function fetchMediumArticles() { const rssUrl = 'https://medium.com/feed/@achref-soua'; - const apiUrl = `https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(rssUrl)}&count=4`; + const apiUrl = `https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(rssUrl)}`; const data = await fetch(apiUrl).then(r => r.json()); if (data.status !== 'ok') return []; return data.items || []; diff --git a/index.html b/index.html index a844f89..5626844 100644 --- a/index.html +++ b/index.html @@ -38,13 +38,13 @@ /> - - - - - + + + + + - + @@ -241,6 +241,6 @@

Let us build something useful.

- +