1async function getMediumPosts() { 2 const rssUrl = 'https://medium.com/feed/contango-xyz'; 3 const proxyUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(rssUrl)}`; 4 5 const numberOfPosts = 8; 6 7 try { 8 const response = await fetch(proxyUrl); 9 const data = await response.json(); 10 11 // Parse the XML content 12 const parser = new DOMParser(); 13 const xmlDoc = parser.parseFromString(data.contents, "text/xml"); 14 const items = xmlDoc.querySelectorAll("item"); 15 const blog = document.getElementById('blog'); 16 const posts = blog.querySelector(".posts"); 17 const pagination = document.createElement("div"); 18 pagination.classList.add("pagination"); 19 let postsHtml = ''; 20 let paginationHtml = ''; 21 22 items.forEach((item, index) => { 23 if (index >= numberOfPosts) return; 24 const title = item.querySelector("title").textContent; 25 const link = item.querySelector("link").textContent; 26 const pubDate = new Date(item.querySelector("pubDate").textContent); 27 const content = item.lastElementChild.textContent; 28 const imgMatch = content.match(/<img[^>]+src="([^">]+)"/); // Regex to find the first image 29 const imgSrc = imgMatch ? imgMatch[1] : "assets/videos/landing-bg.png"; // Use placeholder if no image 30 31 postsHtml += ` 32 <a class="post card-style gradient-border" href="${link}" target="_blank"> 33 <div class="post-link"> 34 <h2>${title}</h2> 35 <span>Read post</span> 36 </div> 37 <p>Published on: ${pubDate.toLocaleDateString()}</p> 38 <div class="arrow"></div> 39 <img src="${imgSrc}" alt="${title}"> 40 <input class="radio-items" type="radio" name="posts" id="post-${index}"> 41 </a> 42 `; 43 44 paginationHtml += `<label for="post-${index}"></label>` 45 }); 46 47 blog.insertAdjacentHTML("afterbegin", '<h3>Learn more</h3>'); 48 posts.innerHTML = postsHtml; 49 pagination.innerHTML = paginationHtml; 50 blog.appendChild(pagination); 51 52 const radioItems = posts.querySelectorAll(".radio-items"); 53 radioItems[0].checked = true; 54 blog.querySelector(".posts-container").addEventListener("scroll", (e) => { 55 radioItems.forEach(radio => { 56 if (radio.getBoundingClientRect().left <= blog.getBoundingClientRect().width / 2) { 57 radio.checked = true; 58 } 59 60 }); 61 }); 62 63 } catch (error) { 64 console.error('Error fetching Medium posts:', error); 65 } 66} 67 68async function graphQLQuery(query) { 69 return fetch("https://api.contango.xyz/graphql", { 70 method: 'POST', 71 headers: { 72 'Content-Type': 'application/json', 73 }, 74 body: JSON.stringify({ query }) 75 }); 76} 77 78async function getAPIData() { 79 80 const totalOpenInterest = document.querySelector("#total-open-interest"); 81 const volume = document.querySelector("#volume"); 82 const uniqueUsers = document.querySelector("#unique-users"); 83 const maxAnimationDuration = 20000; 84 85 const openInterestStartValue = Number(localStorage.getItem("openInterest") || 280000000); 86 const volStartValue = Number(localStorage.getItem("volume") || 3100000000); 87 const uniqueIdsStartValue = Number(localStorage.getItem("uniqueIds") || 16000); 88 89 animateValue(totalOpenInterest, openInterestStartValue - 100000, openInterestStartValue, maxAnimationDuration, "$"); 90 animateValue(volume, volStartValue - 100000, volStartValue + 100000, maxAnimationDuration, "$"); 91 animateValue(uniqueUsers, uniqueIdsStartValue - 200, uniqueIdsStartValue + 200, maxAnimationDuration, "+"); 92 93 // Define the GraphQL queries 94 const positionHistoryQuery = ` 95 query { 96 positionHistoryItems{ 97 aggregates{ 98 distinctCount{ 99 owner 100 } 101 } 102 } 103 }`; 104 105 const openInterestQuery = ` 106 query { 107 openInterests{ 108 aggregates{ 109 sum{ 110 oiUsd 111 } 112 } 113 } 114 }`; 115 116 const volumeQuery = ` 117 query { 118 volumes{ 119 aggregates{ 120 sum{ 121 volUsd 122 } 123 } 124 } 125 }`; 126 127 // Send concurrent requests 128 Promise.all([ 129 graphQLQuery(positionHistoryQuery), 130 graphQLQuery(openInterestQuery), 131 graphQLQuery(volumeQuery) 132 ]) 133 .then(responses => Promise.all(responses.map(r => r.ok ? r.json() : null))) 134 .then(([positionData, openInterestData, volumeData]) => { 135 const uniqueIds = positionData?.data.positionHistoryItems.aggregates.distinctCount.owner; 136 if (uniqueIds) { 137 localStorage.setItem("uniqueIds", uniqueIds); 138 if (uniqueUsers.classList.contains("animate")) { 139 uniqueUsers.classList.remove("animate"); 140 uniqueUsers.innerHTML = "+" + (Math.floor(uniqueIds / 100) * 100).toLocaleStr
140ing(); 141 } 142 } 143 144 const openInterest = openInterestData?.data.openInterests.aggregates.sum.oiUsd; 145 if (openInterest) { 146 localStorage.setItem("openInterest", openInterest); 147 148 if (totalOpenInterest.classList.contains("animate")) { 149 totalOpenInterest.classList.remove("animate"); 150 totalOpenInterest.innerHTML = "$" + Math.floor(openInterest).toLocaleString(); 151 } 152 } 153 154 const vol = volumeData?.data.volumes.aggregates.sum.volUsd; 155 if (vol) { 156 localStorage.setItem("volume", vol); 157 if (volume.classList.contains("animate")) { 158 volume.classList.remove("animate"); 159 volume.innerHTML = "$" + Math.floor(vol).toLocaleString(); 160 } 161 } 162 }) 163 .catch(error => { 164 console.error("Error:", error); 165 totalOpenInterest.innerHTML = "$" + totalOpenInterest.textContent.toLocaleString(); 166 volume.innerHTML = "$" + volume.textContent.toLocaleString(); 167 uniqueUsers.innerHTML = "+" + uniqueUsers.textContent.toLocaleString(); 168 }); 169} 170 171function animateValue(obj, start, end, duration, option = "") { 172 let startTimestamp = null; 173 const step = (timestamp) => { 174 if (!obj.classList.contains("animate")) return; 175 if (!startTimestamp) startTimestamp = timestamp; 176 const progress = Math.min((timestamp - startTimestamp) / duration, 1); 177 if (progress < 1) { 178 window.requestAnimationFrame(step); 179 } 180 obj.innerHTML = option + Math.floor(progress * (end - start) + start).toLocaleString(); 181 }; 182 window.requestAnimationFrame(step); 183} 184 185getMediumPosts(); 186getAPIData(); 187
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.