feat: add article list below player

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
posimai 2026-03-22 14:10:48 +09:00
parent 58c64ad23b
commit 9adbf23768
1 changed files with 84 additions and 0 deletions

View File

@ -274,6 +274,52 @@
margin-top: 4px; margin-top: 4px;
} }
/* ── 記事リスト ─────────────────────────────────────────────── */
.brief-list {
flex-shrink: 0;
max-height: 38dvh;
overflow-y: auto;
border-top: 1px solid var(--border);
scrollbar-width: none;
}
.brief-list::-webkit-scrollbar { display: none; }
.brief-list-item {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 10px 16px;
border-bottom: 1px solid var(--border);
cursor: pointer;
transition: background 0.12s;
}
.brief-list-item:active { background: var(--surface); }
.brief-list-item.active { background: var(--surface); }
.brief-list-num {
font-size: 11px;
color: var(--text3);
width: 16px;
flex-shrink: 0;
padding-top: 2px;
font-variant-numeric: tabular-nums;
}
.brief-list-num.active { color: var(--accent); }
.brief-list-info { flex: 1; min-width: 0; }
.brief-list-title {
font-size: 13px;
color: var(--text2);
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.brief-list-title.active { color: var(--text); font-weight: 500; }
.brief-list-source {
font-size: 11px;
color: var(--text3);
margin-top: 2px;
}
/* ── 音声エンジン表示 ───────────────────────────────────────── */ /* ── 音声エンジン表示 ───────────────────────────────────────── */
.voice-badge { .voice-badge {
display: inline-flex; display: inline-flex;
@ -391,6 +437,9 @@
</button> </button>
</div> </div>
<!-- 記事リスト -->
<div class="brief-list" id="briefList"></div>
</main> </main>
<div id="toast" role="status" aria-live="polite"></div> <div id="toast" role="status" aria-live="polite"></div>
@ -623,6 +672,7 @@ function updateArticleDisplay(a, idx) {
document.getElementById('briefTitle').textContent = a.title || '---'; document.getElementById('briefTitle').textContent = a.title || '---';
document.getElementById('briefSource').textContent = a.source || ''; document.getElementById('briefSource').textContent = a.source || '';
document.getElementById('briefProgress').textContent = ''; document.getElementById('briefProgress').textContent = '';
updateListHighlight(idx);
// MediaSession // MediaSession
if ('mediaSession' in navigator) { if ('mediaSession' in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({ navigator.mediaSession.metadata = new MediaMetadata({
@ -633,6 +683,39 @@ function updateArticleDisplay(a, idx) {
} }
} }
function renderList() {
const list = document.getElementById('briefList');
list.innerHTML = '';
articles.forEach((a, i) => {
const item = document.createElement('div');
item.className = 'brief-list-item' + (i === currentIdx ? ' active' : '');
item.innerHTML = `
<span class="brief-list-num${i === currentIdx ? ' active' : ''}">${i + 1}</span>
<div class="brief-list-info">
<div class="brief-list-title${i === currentIdx ? ' active' : ''}">${a.title || ''}</div>
${a.source ? `<div class="brief-list-source">${a.source}</div>` : ''}
</div>`;
item.addEventListener('click', () => {
const wasPlaying = isPlaying;
stopAudio();
currentIdx = i;
updateArticleDisplay(articles[i], i);
if (wasPlaying) { isPlaying = true; updatePlayBtn(); playNext(); }
});
list.appendChild(item);
});
}
function updateListHighlight(idx) {
document.querySelectorAll('.brief-list-item').forEach((el, i) => {
const active = i === idx;
el.classList.toggle('active', active);
el.querySelector('.brief-list-num').classList.toggle('active', active);
el.querySelector('.brief-list-title').classList.toggle('active', active);
if (active) el.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
});
}
function updateVoiceBadge(isVoicevox) { function updateVoiceBadge(isVoicevox) {
const badge = document.getElementById('voiceBadge'); const badge = document.getElementById('voiceBadge');
const label = document.getElementById('voiceBadgeLabel'); const label = document.getElementById('voiceBadgeLabel');
@ -738,6 +821,7 @@ async function loadFeed() {
document.getElementById('briefCount').textContent = `${list.length}件`; document.getElementById('briefCount').textContent = `${list.length}件`;
updateArticleDisplay(list[0], 0); updateArticleDisplay(list[0], 0);
renderList();
} catch (e) { } catch (e) {
document.getElementById('briefTitle').textContent = '記事の取得に失敗しました'; document.getElementById('briefTitle').textContent = '記事の取得に失敗しました';
document.getElementById('briefSource').textContent = 'ネットワークを確認してください'; document.getElementById('briefSource').textContent = 'ネットワークを確認してください';