gabriel / musehub public
domain-detail.ts typescript
126 lines 4.6 KB
Raw
sha256:0032d6cfa33bc3c8367436ad768e7dd0e339b4332153160247da8266cb5fa352 Merge branch 'task/version-tags-phase3-server' into dev Human 17 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 10 commits
sha256:0032d6cfa33bc3c8367436ad768e7dd0e339b4332153160247da8266cb5fa352 Merge branch 'task/version-tags-phase3-server' into dev Human 17 days ago
sha256:4669620efda9ff41c55bdefd1f7bfe1c239d468428744c84ead9957e5a003a53 merge: rescue snapshot-recovery hardening (c00aa21d) into d… Opus 4.8 minor 30 days ago
sha256:a59da49c4611b970fc4b6ae48678ce4943261c213a07ddbd73ce9201df869b4a fix: remove false-positive proposal_comments index drop fro… Sonnet 4.6 patch 34 days ago
sha256:0a240d6dbff234f07d98a28a4a9a68db702f3f9ff9260196f24219bdb1c0b6f3 feat: render markdown mists as HTML with heading anchor links Sonnet 4.6 patch 34 days ago
sha256:24a7d47486ebc4ebd1832830580e177ec6f877b48dced8c000e198cdec4ce9d6 Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 36 days ago
sha256:b9ff931d147e0114a1f17060f415b89ed551c170a91ff226c70437aa5c85f9ee Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 36 days ago
sha256:d1122d21e73471879b460037b22c0b50fded7c423444a176f248428f75dac39c Merge 'task/fix-issue-pagination-cursor' into 'dev' — propo… Human 36 days ago
sha256:01e18975e73d2b3cd5b6db7929c895bef9aa6e0d4391dc5b2adfc548b41318dd Merge 'feat/adding-debug-logs-to-staging' into 'dev' — prop… Human 36 days ago
sha256:6b1949fc2797ca4c1936a637a4cbfec828ef56cf52398a2e74ca3c4f494e728f fix: use wire_bytes not mpack_bytes_raw in compute_object_b… Sonnet 4.6 patch 48 days ago
sha256:b99f2455dc346966d040133f5203297e6e3ef5803a93728a2c30568d0a0f7583 rename: delta_add → delta_upsert across wire format, models… Sonnet 4.6 patch 50 days ago