CopyLink
1 variant. Open one in isolation: use the link on each card.
Default
Open alone →---interface Props { permalink: string}const { permalink } = Astro.props---<button class="copy-link-btn meta inline-flex cursor-pointer items-center gap-1 border-0 bg-transparent p-0 leading-none text-ink-muted transition-colors hover:text-ink select-none" data-permalink={permalink} aria-label="Copy link to clipboard"> <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" > <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/> <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/> </svg> <span class="copy-link-feedback text-xs" style="opacity: 0; transition: opacity 0.2s ease;" >Copied!</span></button>
<script> document.querySelectorAll<HTMLButtonElement>('.copy-link-btn').forEach(btn => { const feedback = btn.querySelector<HTMLElement>('.copy-link-feedback') let timer: ReturnType<typeof setTimeout> | null = null btn.addEventListener('click', async () => { const permalink = btn.dataset.permalink if (!permalink) return try { await navigator.clipboard.writeText(window.location.origin + permalink) if (feedback) { if (timer) clearTimeout(timer) feedback.style.opacity = '1' timer = setTimeout(() => { if (feedback) feedback.style.opacity = '0' }, 1500) } } catch { // clipboard API unavailable (non-secure context or permission denied) — no feedback shown } }) })</script>