Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -1708,3 +1708,39 @@ mark {
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible {
outline: 2px solid var(--color-accent); outline-offset: 2px; border-radius: 4px;
}

/* ---------- Reading progress bar (resource pages) ---------- */
.reading-progress {
position: fixed; top: 0; left: 0; right: 0; height: 3px;
z-index: 200; background: transparent; opacity: 0;
transition: opacity 0.25s; pointer-events: none;
}
.reading-progress.visible { opacity: 1; }
.reading-progress > span {
display: block; height: 100%; width: 0%;
background: var(--brand-grad); box-shadow: 0 0 8px rgba(42,157,143,0.4);
transition: width 0.1s linear;
}

/* ---------- Insights dashboard ---------- */
.insights-panel {
display: none; background: var(--color-surface);
border: 1px solid var(--color-border); border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm); padding: 24px; margin: 0 0 20px;
}
.insights-panel.open { display: block; }
.insight-loading { padding: 24px; text-align: center; color: var(--color-text-muted); font-size: 0.85rem; }
.insight-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px; margin-bottom: 24px; }
.insight-tile { display: flex; flex-direction: column; gap: 2px; padding: 16px; border-radius: var(--radius-md); background: var(--color-bg); border: 1px solid var(--color-border-light); }
.insight-tile-value { font-family: var(--font-serif); font-size: 1.55rem; font-weight: 700; color: var(--color-accent); line-height: 1; letter-spacing: -0.02em; }
.insight-tile-label { font-size: 0.68rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-muted); }
.insight-charts { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 24px 32px; }
.insight-group-title { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-text-muted); margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid var(--color-border-light); }
.insight-bar { display: grid; grid-template-columns: 1fr 90px 34px; align-items: center; gap: 8px; padding: 3px 4px; border-radius: var(--radius-sm); }
.insight-bar.clickable { cursor: pointer; }
.insight-bar.clickable:hover { background: var(--color-accent-light); }
.insight-bar-label { font-size: 0.76rem; color: var(--color-text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.insight-bar-track { height: 7px; background: var(--color-border-light); border-radius: 4px; overflow: hidden; }
.insight-bar-fill { display: block; height: 100%; border-radius: 4px; background: var(--brand-grad); transition: width 0.6s cubic-bezier(0.2,0.8,0.2,1); }
.insight-bar-value { font-size: 0.74rem; font-weight: 700; color: var(--color-text-secondary); text-align: right; font-variant-numeric: tabular-nums; }
.insight-note { margin-top: 20px; font-size: 0.73rem; color: var(--color-text-muted); font-style: italic; }
6 changes: 6 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ <h1 class="site-title">Development Discourses</h1>
<div class="results-header">
<p class="results-count" id="resultsCount">Loading resources...</p>
<div class="results-tools">
<button class="tool-btn" id="insightsBtn" aria-expanded="false" aria-controls="insightsPanel" title="Show library insights">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="20" x2="12" y2="10"></line><line x1="18" y1="20" x2="18" y2="4"></line><line x1="6" y1="20" x2="6" y2="16"></line></svg>
<span>Insights</span>
</button>
<button class="tool-btn" id="randomBtn" title="Open a random resource">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 3h5v5"></path><path d="M4 20 21 3"></path><path d="M21 16v5h-5"></path><path d="m15 15 6 6"></path><path d="M4 4l5 5"></path></svg>
<span>Surprise me</span>
Expand Down Expand Up @@ -236,6 +240,8 @@ <h1 class="site-title">Development Discourses</h1>
</div>
</div>

<section id="insightsPanel" class="insights-panel" aria-label="Library insights"></section>

<div class="resources-list" id="resourcesList">
<!-- Resources rendered by JS -->
</div>
Expand Down
75 changes: 75 additions & 0 deletions js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,81 @@

applyFilters();
bindEvents();
setupInsights();
}

// ---- Insights dashboard ----
function setupInsights() {
const btn = document.getElementById('insightsBtn');
const panel = document.getElementById('insightsPanel');
if (!btn || !panel) return;
let loaded = false;

function barChart(title, obj, clickTopic) {
const rows = Object.keys(obj || {}).map(k => ({ label: k, count: obj[k] }))
.sort((a, b) => b.count - a.count);
if (!rows.length) return '';
const max = Math.max.apply(null, rows.map(r => r.count));
const bars = rows.map(r => {
const pct = Math.round((r.count / max) * 100);
const attrs = clickTopic ? ` data-topic="${escapeHtml(r.label)}" role="button" tabindex="0"` : '';
return `<div class="insight-bar${clickTopic ? ' clickable' : ''}"${attrs}>
<span class="insight-bar-label">${escapeHtml(prettyLabel(r.label))}</span>
<span class="insight-bar-track"><span class="insight-bar-fill" style="width:${pct}%"></span></span>
<span class="insight-bar-value">${r.count}</span>
</div>`;
}).join('');
return `<div class="insight-group"><h4 class="insight-group-title">${escapeHtml(title)}</h4>${bars}</div>`;
}

function prettyLabel(k) {
return ({ grey_literature: 'Grey literature', paper: 'Papers', book: 'Books',
open_access: 'Open access', free_to_read: 'Free to read', check_access: 'Check access' })[k] || k;
}

function render(s) {
const tiles = [
{ v: s.total, l: 'Resources' },
{ v: s.topic_count, l: 'Topics' },
{ v: (s.by_type && s.by_type.paper) || 0, l: 'Papers' },
{ v: (s.by_access && s.by_access.open_access) || 0, l: 'Open access' },
{ v: (s.year_min && s.year_max) ? (s.year_min + '–' + s.year_max) : '—', l: 'Year span' },
];
panel.innerHTML =
'<div class="insight-tiles">' +
tiles.map(x => `<div class="insight-tile"><span class="insight-tile-value">${x.v}</span><span class="insight-tile-label">${x.l}</span></div>`).join('') +
'</div><div class="insight-charts">' +
barChart('By topic', s.by_topic, true) +
barChart('By type', s.by_type, false) +
barChart('By access', s.by_access, false) +
'</div><p class="insight-note">Generated from the library index. Click a topic bar to filter.</p>';

panel.querySelectorAll('[data-topic]').forEach(el => {
const apply = () => {
currentTopic = el.dataset.topic;
setActivePill(topicFilters, currentTopic);
displayCount = PAGE_SIZE;
applyFilters();
document.querySelector('.results-section').scrollIntoView({ behavior: 'smooth' });
};
el.addEventListener('click', apply);
el.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); apply(); } });
});
}

btn.addEventListener('click', function () {
const open = panel.classList.toggle('open');
btn.classList.toggle('active', open);
btn.setAttribute('aria-expanded', String(open));
if (open && !loaded) {
loaded = true;
panel.innerHTML = '<div class="insight-loading">Loading insights…</div>';
fetch('data/stats.json')
.then(r => r.ok ? r.json() : Promise.reject())
.then(render)
.catch(() => { panel.innerHTML = '<div class="insight-loading">Insights unavailable.</div>'; });
}
});
}

function showLoadingSkeleton() {
Expand Down
18 changes: 18 additions & 0 deletions js/resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@
window.renderLitmap(currentResource, allResources);
}
bindEvents();
initReadingProgress();
}

// ---- Reading progress bar ----
function initReadingProgress() {
const bar = document.getElementById('reading-progress');
if (!bar) return;
const fill = bar.firstElementChild;
function update() {
const doc = document.documentElement;
const scrollable = doc.scrollHeight - doc.clientHeight;
const pct = scrollable > 0 ? Math.min(100, (window.scrollY / scrollable) * 100) : 0;
if (fill) fill.style.width = pct + '%';
bar.classList.toggle('visible', window.scrollY > 60);
}
window.addEventListener('scroll', update, { passive: true });
window.addEventListener('resize', update, { passive: true });
update();
}

// ---- Render Main ----
Expand Down
2 changes: 2 additions & 0 deletions resource.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Source+Serif+4:ital,wght@0,400;0,600;0,700;1,400&display=swap" rel="stylesheet">
</head>
<body>
<div id="reading-progress" class="reading-progress" aria-hidden="true"><span></span></div>

<header class="site-header">
<div class="container">
<div class="header-content">
Expand Down
Loading