gabriel / musehub public
domain-detail.ts typescript
126 lines 4.6 KB
Raw
sha256:3c58668648c7323bb9f5c6881cfe6a3f14fc93fcb73b537d253732952a5bf8bf chore: bump version to 0.2.0rc12 Sonnet 4.6 patch 8 days ago
1 /**
2 * domain-detail.ts — Domain plugin detail page interactivity.
3 *
4 * Handles:
5 * - Copy-to-clipboard for terminal blocks and hash cells
6 * - Entrance animations for dimension cards (IntersectionObserver)
7 * - Install button feedback
8 */
9
10 export interface DomainDetailData {
11 domainSlug?: string;
12 domainId?: string;
13 [key: string]: unknown;
14 }
15
16 // ── Copy to clipboard ─────────────────────────────────────────────────────────
17
18 function setupCopyButtons(): void {
19 document.querySelectorAll<HTMLButtonElement>('[data-copy-id]').forEach(btn => {
20 btn.addEventListener('click', async () => {
21 const targetId = btn.dataset.copyId;
22 if (!targetId) return;
23 const el = document.getElementById(targetId);
24 if (!el) return;
25 const text = el.textContent?.trim() ?? '';
26 try {
27 await navigator.clipboard.writeText(text);
28 const prev = btn.textContent;
29 btn.textContent = '✓ Copied';
30 btn.classList.add('copied');
31 setTimeout(() => {
32 btn.textContent = prev;
33 btn.classList.remove('copied');
34 }, 2000);
35 } catch {
36 // fallback: select text
37 const range = document.createRange();
38 range.selectNode(el);
39 window.getSelection()?.removeAllRanges();
40 window.getSelection()?.addRange(range);
41 }
42 });
43 });
44 }
45
46 // ── Dimension card entrance animation ─────────────────────────────────────────
47
48 function setupDimAnimations(): void {
49 const grid = document.querySelector<HTMLElement>('.dd-dim-grid');
50 if (!grid) return;
51
52 const cards = Array.from(grid.querySelectorAll<HTMLElement>('.dd-dim-card'));
53 cards.forEach(card => {
54 card.style.opacity = '0';
55 card.style.transform = 'translateY(12px)';
56 });
57
58 const observer = new IntersectionObserver(entries => {
59 if (!entries.some(e => e.isIntersecting)) return;
60 observer.disconnect();
61 cards.forEach((card, i) => {
62 setTimeout(() => {
63 card.style.transition = 'opacity 0.35s ease, transform 0.35s ease';
64 card.style.opacity = '1';
65 card.style.transform = 'none';
66 }, i * 30);
67 });
68 }, { threshold: 0.1 });
69
70 observer.observe(grid);
71 }
72
73 // ── Stat pill count-up animation ──────────────────────────────────────────────
74
75 function setupStatCountUp(): void {
76 const pills = document.querySelectorAll<HTMLElement>('.dd-stat-pill__value');
77 pills.forEach(el => {
78 const raw = el.textContent?.trim() ?? '';
79 const num = parseInt(raw, 10);
80 if (isNaN(num) || num <= 1) return;
81 el.textContent = '0';
82 const duration = 600;
83 const start = performance.now();
84 const tick = (now: number) => {
85 const t = Math.min((now - start) / duration, 1);
86 const ease = 1 - Math.pow(1 - t, 3);
87 el.textContent = String(Math.round(ease * num));
88 if (t < 1) requestAnimationFrame(tick);
89 };
90 requestAnimationFrame(tick);
91 });
92 }
93
94 // ── Install button ─────────────────────────────────────────────────────────────
95
96 function setupInstallButton(): void {
97 const btn = document.getElementById('dd-install-btn');
98 if (!btn) return;
99 btn.addEventListener('click', () => {
100 // Show a "copied" instruction since Muse CLI install is a terminal command
101 const installEl = document.getElementById('install-cmd');
102 if (!installEl) return;
103 const text = installEl.textContent?.trim() ?? '';
104 navigator.clipboard.writeText(text).then(() => {
105 const prev = btn.textContent;
106 btn.textContent = '✓ Command Copied — paste in terminal';
107 btn.setAttribute('disabled', '');
108 setTimeout(() => {
109 btn.textContent = prev ?? '↓ Install Domain';
110 btn.removeAttribute('disabled');
111 }, 3000);
112 }).catch(() => {
113 btn.textContent = 'Open terminal and run: muse domain install';
114 setTimeout(() => { btn.textContent = '↓ Install Domain'; }, 4000);
115 });
116 });
117 }
118
119 // ── Entry point ───────────────────────────────────────────────────────────────
120
121 export function initDomainDetail(_data: DomainDetailData): void {
122 setupCopyButtons();
123 setupDimAnimations();
124 setupStatCountUp();
125 setupInstallButton();
126 }
File History 1 commit
sha256:3c58668648c7323bb9f5c6881cfe6a3f14fc93fcb73b537d253732952a5bf8bf chore: bump version to 0.2.0rc12 Sonnet 4.6 patch 8 days ago