|
21 | 21 | if (navCloseBtn) {
|
22 | 22 | navCloseBtn.addEventListener("click", toggleNav);
|
23 | 23 | }
|
24 |
| - |
| 24 | + |
25 | 25 | // Close on Escape key
|
26 | 26 | document.addEventListener('keydown', (e) => {
|
27 | 27 | if (e.key === 'Escape' && body.classList.contains('is-mobile-nav-open')) {
|
|
44 | 44 |
|
45 | 45 | let bubblePool = [];
|
46 | 46 | for (let i = 0; i < MAX_BUBBLES; i++) {
|
47 |
| - let bubble = document.createElement('div'); |
48 |
| - bubble.className = 'contribution-bubble'; |
49 |
| - bubble.dataset.busy = 'false'; |
50 |
| - bubbleContainer.appendChild(bubble); |
51 |
| - bubblePool.push(bubble); |
| 47 | + let bubble = document.createElement('div'); |
| 48 | + bubble.className = 'contribution-bubble'; |
| 49 | + bubble.dataset.busy = 'false'; |
| 50 | + bubbleContainer.appendChild(bubble); |
| 51 | + bubblePool.push(bubble); |
52 | 52 | }
|
53 |
| - |
| 53 | + |
54 | 54 | let activeBubbles = new Map();
|
55 | 55 |
|
56 | 56 | const updateBubblePositions = () => {
|
57 | 57 | activeBubbles.forEach((bubble, partner) => {
|
58 | 58 | const partnerRect = partner.getBoundingClientRect();
|
59 | 59 | const containerRect = bubbleContainer.getBoundingClientRect();
|
60 |
| - |
| 60 | + |
61 | 61 | const left = partnerRect.left - containerRect.left + (partnerRect.width / 2) - (bubble.offsetWidth / 2);
|
62 | 62 | const top = partnerRect.top - containerRect.top - bubble.offsetHeight - 10;
|
63 | 63 |
|
64 | 64 | bubble.style.transform = `translate(${left}px, ${top}px)`;
|
65 | 65 | });
|
66 | 66 | requestAnimationFrame(updateBubblePositions);
|
67 | 67 | };
|
68 |
| - |
| 68 | + |
69 | 69 | requestAnimationFrame(updateBubblePositions);
|
70 | 70 |
|
71 | 71 | const showBubble = (partner) => {
|
72 |
| - const bubble = bubblePool.find(b => b.dataset.busy === 'false'); |
73 |
| - if (!bubble) return; |
74 |
| - |
75 |
| - bubble.dataset.busy = 'true'; |
76 |
| - activeBubbles.set(partner, bubble); |
77 |
| - |
78 |
| - bubble.innerHTML = partner.dataset.contributions; |
79 |
| - bubble.classList.add('visible'); |
80 |
| - |
81 |
| - setTimeout(() => { |
82 |
| - hideBubble(partner, bubble); |
83 |
| - }, BUBBLE_LIFETIME); |
| 72 | + const bubble = bubblePool.find(b => b.dataset.busy === 'false'); |
| 73 | + if (!bubble) return; |
| 74 | + |
| 75 | + bubble.dataset.busy = 'true'; |
| 76 | + activeBubbles.set(partner, bubble); |
| 77 | + |
| 78 | + bubble.innerHTML = partner.dataset.contributions; |
| 79 | + bubble.classList.add('visible'); |
| 80 | + |
| 81 | + setTimeout(() => { |
| 82 | + hideBubble(partner, bubble); |
| 83 | + }, BUBBLE_LIFETIME); |
84 | 84 | };
|
85 | 85 |
|
86 | 86 | const hideBubble = (partner, bubble) => {
|
87 |
| - bubble.classList.remove('visible'); |
88 |
| - activeBubbles.delete(partner); |
89 |
| - |
90 |
| - setTimeout(() => { |
91 |
| - bubble.dataset.busy = 'false'; |
92 |
| - }, 300); |
| 87 | + bubble.classList.remove('visible'); |
| 88 | + activeBubbles.delete(partner); |
| 89 | + |
| 90 | + setTimeout(() => { |
| 91 | + bubble.dataset.busy = 'false'; |
| 92 | + }, 300); |
93 | 93 | };
|
94 | 94 |
|
95 | 95 | setInterval(() => {
|
96 |
| - if (activeBubbles.size >= MAX_BUBBLES) return; |
97 |
| - |
98 |
| - const rect = section.getBoundingClientRect(); |
99 |
| - const isInView = rect.top < window.innerHeight && rect.bottom >= 0; |
100 |
| - |
101 |
| - if (isInView) { |
102 |
| - const availablePartners = partnersWithContributions.filter(p => { |
103 |
| - if (activeBubbles.has(p)) return false; |
104 |
| - const r = p.getBoundingClientRect(); |
105 |
| - return r.left > 50 && r.right < window.innerWidth - 50; |
106 |
| - }); |
107 |
| - |
108 |
| - if (availablePartners.length > 0) { |
109 |
| - const randomIndex = Math.floor(Math.random() * availablePartners.length); |
110 |
| - showBubble(availablePartners[randomIndex]); |
111 |
| - } |
| 96 | + if (activeBubbles.size >= MAX_BUBBLES) return; |
| 97 | + |
| 98 | + const rect = section.getBoundingClientRect(); |
| 99 | + const isInView = rect.top < window.innerHeight && rect.bottom >= 0; |
| 100 | + |
| 101 | + if (isInView) { |
| 102 | + const availablePartners = partnersWithContributions.filter(p => { |
| 103 | + if (activeBubbles.has(p)) return false; |
| 104 | + const r = p.getBoundingClientRect(); |
| 105 | + return r.left > 50 && r.right < window.innerWidth - 50; |
| 106 | + }); |
| 107 | + |
| 108 | + if (availablePartners.length > 0) { |
| 109 | + const randomIndex = Math.floor(Math.random() * availablePartners.length); |
| 110 | + showBubble(availablePartners[randomIndex]); |
112 | 111 | }
|
113 |
| - }, BUBBLE_INTERVAL); |
| 112 | + } |
| 113 | + }, BUBBLE_INTERVAL); |
114 | 114 | };
|
115 |
| - |
| 115 | + |
116 | 116 | // --- Copy Code Block ---
|
117 | 117 | const initCopyCodeButtons = () => {
|
118 | 118 | const codeBlocks = document.querySelectorAll('.code-block-wrapper');
|
|
131 | 131 | setTimeout(() => {
|
132 | 132 | button.classList.remove('copied');
|
133 | 133 | button.querySelector('.copy-text').classList.remove('hidden');
|
134 |
| - button.querySelector('.copied-text').classList.add('hidden'); |
| 134 | + button.querySelector('.copied-text').add('hidden'); |
135 | 135 | }, 2000);
|
136 | 136 | }).catch(err => {
|
137 | 137 | console.error('Failed to copy text: ', err);
|
|
142 | 142 | });
|
143 | 143 | };
|
144 | 144 |
|
| 145 | + // --- OG Image Assets Modal --- |
| 146 | + const initOgImageModal = () => { |
| 147 | + const modal = document.getElementById('og-image-modal'); |
| 148 | + const modalContent = document.getElementById('og-modal-content'); |
| 149 | + const closeBtn = document.getElementById('og-modal-close'); |
| 150 | + if (!modal || !modalContent || !closeBtn) return; |
| 151 | + |
| 152 | + const showModal = () => { |
| 153 | + modal.classList.remove('hidden'); |
| 154 | + modal.classList.add('flex'); |
| 155 | + }; |
| 156 | + |
| 157 | + const hideModal = () => { |
| 158 | + modal.classList.add('hidden'); |
| 159 | + modal.classList.remove('flex'); |
| 160 | + }; |
| 161 | + |
| 162 | + closeBtn.addEventListener('click', hideModal); |
| 163 | + modal.addEventListener('click', (e) => { |
| 164 | + if (e.target === modal) { |
| 165 | + hideModal(); |
| 166 | + } |
| 167 | + }); |
| 168 | + document.addEventListener('keydown', (e) => { |
| 169 | + if (e.key === 'Escape' && !modal.classList.contains('hidden')) { |
| 170 | + hideModal(); |
| 171 | + } |
| 172 | + }); |
| 173 | + |
| 174 | + window.openOgModal = (imageUrls) => { |
| 175 | + modalContent.innerHTML = ''; // Clear previous content |
| 176 | + |
| 177 | + if (!imageUrls || imageUrls.length === 0) { |
| 178 | + modalContent.innerHTML = '<p class="text-center col-span-full">No social media assets found for this page.</p>'; |
| 179 | + } else { |
| 180 | + imageUrls.forEach(img => { |
| 181 | + const cardHTML = ` |
| 182 | + <div class="og-image-card text-center"> |
| 183 | + <h4 class="font-semibold mb-2 text-sm">${img.label}</h4> |
| 184 | + <a href="${img.url}" download="${img.filename}" title="Download this image"> |
| 185 | + <img src="${img.url}" alt="OG Image Preview for ${img.label}" class="w-full rounded-md border border-border dark:border-darkmode-border shadow-md hover:shadow-lg transition-shadow"> |
| 186 | + </a> |
| 187 | + <div class="mt-3"> |
| 188 | + <a href="${img.url}" download="${img.filename}" class="btn btn-sm btn-new-primary"> |
| 189 | + Download |
| 190 | + </a> |
| 191 | + </div> |
| 192 | + </div> |
| 193 | + `; |
| 194 | + modalContent.insertAdjacentHTML('beforeend', cardHTML); |
| 195 | + }); |
| 196 | + } |
| 197 | + showModal(); |
| 198 | + }; |
| 199 | + |
| 200 | + document.querySelectorAll('[data-og-assets]').forEach(trigger => { |
| 201 | + trigger.addEventListener('click', (e) => { |
| 202 | + e.preventDefault(); |
| 203 | + const assetsJson = trigger.dataset.ogAssets; |
| 204 | + try { |
| 205 | + const assets = JSON.parse(assetsJson); |
| 206 | + window.openOgModal(assets); |
| 207 | + } catch (error) { |
| 208 | + console.error("Could not parse OG assets JSON:", error); |
| 209 | + window.openOgModal([]); |
| 210 | + } |
| 211 | + }); |
| 212 | + }); |
| 213 | + }; |
| 214 | + |
145 | 215 | // Wait for the DOM to be fully loaded to initialize
|
146 | 216 | if (document.readyState === 'loading') {
|
147 | 217 | document.addEventListener('DOMContentLoaded', () => {
|
148 |
| - initCopyCodeButtons(); |
149 |
| - if (window.innerWidth > 768) { // Only run on larger screens |
150 |
| - initContributionBubbles(); |
151 |
| - } |
| 218 | + initCopyCodeButtons(); |
| 219 | + initOgImageModal(); |
| 220 | + if (window.innerWidth > 768) { // Only run on larger screens |
| 221 | + initContributionBubbles(); |
| 222 | + } |
152 | 223 | });
|
153 | 224 | } else {
|
154 | 225 | initCopyCodeButtons();
|
| 226 | + initOgImageModal(); |
155 | 227 | if (window.innerWidth > 768) { // Only run on larger screens
|
156 |
| - initContributionBubbles(); |
| 228 | + initContributionBubbles(); |
157 | 229 | }
|
158 | 230 | }
|
159 | 231 |
|
|
0 commit comments