diff --git a/internal/httpapi/static/app.css b/internal/httpapi/static/app.css
index 21b1084..de8729a 100644
--- a/internal/httpapi/static/app.css
+++ b/internal/httpapi/static/app.css
@@ -19,3 +19,9 @@ footer { position: fixed; left: 0; right: 0; bottom: 0; z-index: 1000; text-alig
@media (max-width: 900px) { .dash { grid-template-columns: 1fr; } .dash aside.sidebar { display:none; } }
/* Summarizer output wrapping */
#out { white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere; }
+
+/* Cards */
+.card { margin:.35rem 0; padding:.5rem; border:1px solid var(--muted-border-color); border-radius:.5rem; background: var(--pico-card-background-color, rgba(0,0,0,0.02)); }
+.card .card-head { display:flex; align-items:center; justify-content:space-between; gap:.5rem; cursor:pointer; }
+.card .card-actions { display:flex; align-items:center; gap:.4rem; }
+.card .summary-title { font-weight:600; margin-top:.25rem; opacity:.85; }
diff --git a/internal/httpapi/static/app.js b/internal/httpapi/static/app.js
index 64b12b2..b03d77d 100644
--- a/internal/httpapi/static/app.js
+++ b/internal/httpapi/static/app.js
@@ -37,6 +37,21 @@ function processLinks(scope){ const links = scope.querySelectorAll('a[href]:not(
if(!card) return;
if(card.title||card.description||card.image||card.html){
const c=document.createElement('div'); c.className='card';
+ // Header with favicon/domain + title (clickable), actions on the right
+ const head=document.createElement('div'); head.className='card-head';
+ const left=document.createElement('div'); left.style.display='flex'; left.style.alignItems='center'; left.style.gap='.5rem';
+ const u=new URL(a.href);
+ const fav=document.createElement('img'); fav.src='https://www.google.com/s2/favicons?domain='+u.hostname+'&sz=32'; fav.width=16; fav.height=16; fav.alt=''; fav.loading='lazy'; fav.referrerPolicy='no-referrer';
+ const title=document.createElement('a'); title.href=a.href; title.target='_blank'; title.rel='noopener'; title.textContent=card.title||u.hostname; title.style.fontWeight='600'; title.style.textDecoration='none';
+ left.appendChild(fav); left.appendChild(title);
+ const actions=document.createElement('div'); actions.className='card-actions';
+ const chevron=document.createElement('button'); chevron.type='button'; chevron.title='Expand/collapse'; chevron.textContent='▾'; chevron.style.padding='0 .4rem'; chevron.style.fontSize='.9rem';
+ const btn=document.createElement('button'); btn.type='button'; btn.title='Summarize this link'; btn.textContent='Summarize ✨'; btn.style.padding='0 .4rem'; btn.style.fontSize='.9rem';
+ const spinner=document.createElement('span'); spinner.textContent=''; spinner.style.marginLeft='.5rem';
+ actions.appendChild(btn); actions.appendChild(chevron); actions.appendChild(spinner);
+ head.appendChild(left); head.appendChild(actions);
+ c.appendChild(head);
+ // Body (details)
const details=document.createElement('div'); details.className='card-details';
var html='';
if(card.image){ html += '

'; }
@@ -44,29 +59,29 @@ function processLinks(scope){ const links = scope.querySelectorAll('a[href]:not(
if(card.title){ html += ''+escapeHtml(card.title)+'
'; }
if(card.description){ html += ''+escapeHtml(card.description)+'
'; }
html += '';
- const row = document.createElement('div'); row.style.display='flex'; row.style.alignItems='flex-start'; row.style.gap='.5rem'; row.innerHTML = html;
+ const row=document.createElement('div'); row.style.display='flex'; row.style.alignItems='flex-start'; row.style.gap='.5rem'; row.innerHTML=html;
details.appendChild(row);
details.querySelectorAll('img').forEach(img=> img.addEventListener('load', ()=> pinBottomMulti()));
if(card.html){ const wrap=document.createElement('div'); wrap.innerHTML=card.html; details.appendChild(wrap); ensureTwitterWidgets(); }
c.appendChild(details);
- // Controls
- const ctrl = document.createElement('div'); ctrl.style.marginTop='.25rem';
- const btn = document.createElement('button'); btn.type='button'; btn.title='Summarize this link'; btn.textContent='\u25B6'; btn.style.padding='0 .4rem'; btn.style.fontSize='.9rem';
- const spinner = document.createElement('span'); spinner.textContent=''; spinner.style.marginLeft='.5rem';
- const sum = document.createElement('div'); sum.className='link-summary'; sum.style.whiteSpace='pre-wrap'; sum.style.marginTop='.25rem';
- const toggleCard = document.createElement('button'); toggleCard.type='button'; toggleCard.title='Collapse/expand card'; toggleCard.textContent='−'; toggleCard.style.padding='0 .4rem'; toggleCard.style.fontSize='.9rem'; toggleCard.style.marginLeft='.5rem';
- const toggleSum = document.createElement('button'); toggleSum.type='button'; toggleSum.title='Collapse/expand summary'; toggleSum.textContent='−'; toggleSum.style.padding='0 .4rem'; toggleSum.style.fontSize='.9rem'; toggleSum.style.marginLeft='.25rem';
- toggleCard.onclick = ()=>{ const hidden = details.style.display==='none'; details.style.display = hidden? '' : 'none'; toggleCard.textContent = hidden? '−' : '+'; pinBottomMulti(); };
- toggleSum.onclick = ()=>{ const hidden = sum.style.display==='none'; sum.style.display = hidden? '' : 'none'; toggleSum.textContent = hidden? '−' : '+'; pinBottomMulti(); };
+ // Summary block (hidden until requested)
+ const sum=document.createElement('div'); sum.className='link-summary'; sum.style.whiteSpace='pre-wrap'; sum.style.marginTop='.25rem'; sum.style.display='none';
+ c.appendChild(sum);
+
+ const toggleDetails = ()=>{ const collapsed = details.style.display==='none'; details.style.display = collapsed? '' : 'none'; chevron.textContent = collapsed? '▾':'▸'; pinBottomMulti(); };
+ head.addEventListener('click', (ev)=>{ if(ev.target===btn || actions.contains(ev.target)) return; toggleDetails(); });
+ chevron.onclick=(ev)=>{ ev.stopPropagation(); toggleDetails(); };
+
btn.onclick = async ()=>{
- btn.disabled=true; spinner.textContent='…'; sum.textContent=''; sum.style.display=''; toggleSum.textContent='−';
- try{ const data = await api('/api/linksummary',{query:{url:a.href}}); sum.textContent = (data && data.summary) ? data.summary : '(no summary)'; }
+ if(sum.style.display!== 'none' && sum.textContent){ // hide existing
+ sum.style.display='none'; btn.textContent='Summarize ✨'; pinBottomMulti(); return;
+ }
+ btn.disabled=true; spinner.textContent='…'; sum.textContent=''; sum.style.display='';
+ try{ const data = await api('/api/linksummary',{query:{url:a.href}}); sum.textContent = (data && data.summary) ? data.summary : '(no summary)'; btn.textContent='Hide summary'; }
catch(e){ sum.textContent = 'error: '+e; }
spinner.textContent=''; btn.disabled=false; pinBottomMulti();
};
- ctrl.appendChild(btn); ctrl.appendChild(spinner); ctrl.appendChild(toggleCard); ctrl.appendChild(toggleSum);
- c.appendChild(ctrl);
- c.appendChild(sum);
+
a.parentNode.insertBefore(c, a.nextSibling);
}
}).catch(()=>{});