1function initHeroShowcase(root) { 2 if (!root || root.dataset.showcaseReady === "1") return; 3 root.dataset.showcaseReady = "1"; 4 5 const ring = root.querySelector("[data-orbit-ring]"); 6 const track = root.querySelector(".home-hero-showcase__orbit-track"); 7 const cards = Array.from(root.querySelectorAll("[data-orbit-card]")); 8 const terminal = root.querySelector("[data-hero-terminal]"); 9 const stage = root.querySelector(".home-hero-showcase__stage"); 10 if (!ring || !cards.length) return; 11 12 const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; 13 const finePointer = window.matchMedia("(hover: hover) and (pointer: fine)").matches; 14 const isCompact = 15 root.classList.contains("home-plugin-strip--beside") || !!root.querySelector(".home-hero-showcase--compact"); 16 17 let activeIndex = 0; 18 let autoplayTimer = null; 19 let paused = false; 20 let sectionVisible = true; 21 let dragPointerId = null; 22 let dragStartX = 0; 23 let dragMoved = false; 24 let dragStartIndex = 0; 25 26 const DRAG_CLICK_THRESHOLD = 10; 27 const DRAG_SLIDE_PX = 56; 28 29 if (isCompact) root.classList.add("is-plugin-coverflow"); 30 31 function ensureCardFaces() { 32 cards.forEach((card, i) => { 33 card.style.setProperty("--orbit-index", String(i)); 34 card.dataset.orbitIndex = String(i); 35 if (card.querySelector(".home-hero-showcase__card-face")) return; 36 const face = document.createElement("div"); 37 face.className = "home-hero-showcase__card-face"; 38 while (card.firstChild) face.appendChild(card.firstChild); 39 card.appendChild(face); 40 }); 41 } 42 43 function wrappedDelta(i, active) { 44 const n = cards.length; 45 let d = i - active; 46 if (d > n / 2) d -= n; 47 if (d < -n / 2) d += n; 48 return d; 49 } 50 51 function layoutCoverflow() { 52 // Flat coverflow â no orbit ring rotation (avoids mirrored/overlapping faces). 53 ring.style.transform = "none"; 54 cards.forEach((card, i) => { 55 const d = wrappedDelta(i, activeIndex); 56 const abs = Math.abs(d); 57 const face = card.querySelector(".home-hero-showcase__card-face"); 58 if (face) face.style.transform = "none"; 59 60 card.classList.toggle("is-front", d === 0); 61 card.classList.toggle("is-prev", d === -1); 62 card.classList.toggle("is-next", d === 1); 63 card.classList.toggle("is-far", abs > 1); 64 65 if (abs > 1) { 66 card.style.opacity = "0"; 67 card.style.visibility = "hidden"; 68 card.style.pointerEvents = "none"; 69 card.style.transform = "translate(-50%, -50%) scale(0.6)"; 70 card.style.zIndex = "0"; 71 return; 72 } 73 74 card.style.visibility = "visible"; 75 card.style.pointerEvents = d === 0 ? "auto" : "auto"; 76 card.style.zIndex = String(5 - abs); 77 78 if (d === 0) { 79 card.style.opacity = "1"; 80 card.style.filter = "none"; 81 card.style.transform = "translate(-50%, -50%) translateX(0) translateZ(40px) rotateY(0deg) scale(1)"; 82 } else if (d === -1) { 83 card.style.opacity = "0.55"; 84 card.style.filter = "grayscale(0.35) brightness(0.72)"; 85 card.style.transform = "translate(-50%, -50%) translateX(-86px) translateZ(0) rotateY(18deg) scale(0.82)"; 86 } else { 87 card.style.opacity = "0.55"; 88 card.style.filter = "grayscale(0.35) brightness(0.72)"; 89 card.style.transform = "translate(-50%, -50%) translateX(86px) translateZ(0) rotateY(-18deg) scale(0.82)"; 90 } 91 }); 92 } 93 94 // Legacy full orbit (only if not compact) 95 let angle = 0; 96 let targetAngle = 0; 97 let raf = null; 98 const stepDeg = 360 / cards.length; 99 100 function applyOrbitAngle(deg) { 101 ring.style.setProperty("--orbit-angle", `${deg.toFixed(2)}deg`); 102 ring.style.transform = `rotateY(${deg.toFixed(2)}deg)`; 103 cards.forEach((card, i) => { 104 const total = deg + i * stepDeg; 105 const face = card.querySelector(".home-hero-showcase__card-face"); 106 if (face) face.style.transform = `rotateY(${(-total).toFixed(2)}deg)`; 107 const d = wrappedDelta(i, activeIndex); 108 card.classList.toggle("is-front", d === 0); 109 card.classList.toggle("is-prev", d === -1); 110 card.classList.toggle("is-next", d === 1); 111 card.classList.toggle("is-far", Math.abs(d) > 1); 112 });
113 } 114 115 function indexFromAngle(deg) { 116 const raw = Math.round(-deg / stepDeg); 117 return ((raw % cards.length) + cards.length) % cards.length; 118 } 119 120 function tickOrbit() { 121 const diff = targetAngle - angle; 122 angle += diff * 0.1; 123 if (Math.abs(diff) < 0.05) angle = targetAngle; 124 applyOrbitAngle(angle); 125 if (Math.abs(diff) > 0.05) raf = requestAnimationFrame(tickOrbit); 126 else raf = null; 127 } 128 129 function setActive(index, { animate = true } = {}) { 130 activeIndex = ((index % cards.length) + cards.length) % cards.length; 131 if (isCompact) { 132 layoutCoverflow(); 133 return; 134 } 135 targetAngle = -activeIndex * stepDeg; 136 if (!animate) { 137 angle = targetAngle; 138 applyOrbitAngle(angle); 139 } else if (raf == null) { 140 raf = requestAnimationFrame(tickOrbit); 141 } 142 } 143 144 ensureCardFaces(); 145 ring.style.setProperty("--orbit-step", `${stepDeg}deg`); 146 setActive(0, { animate: false }); 147 148 function startAutoplay() { 149 stopAutoplay(); 150 if (reduceMotion || paused || dragPointerId != null || !sectionVisible) return; 151 autoplayTimer = setInterval(() => setActive(activeIndex + 1), 4200); 152 } 153 154 function stopAutoplay() { 155 if (autoplayTimer) { 156 clearInterval(autoplayTimer); 157 autoplayTimer = null; 158 } 159 } 160 161 function endDrag() { 162 if (dragPointerId == null) return; 163 if (track?.hasPointerCapture?.(dragPointerId)) { 164 try { 165 track.releasePointerCapture(dragPointerId); 166 } catch { 167 /* ignore */ 168 } 169 } 170 track?.classList.remove("is-orbit-dragging"); 171 const wasDrag = dragMoved; 172 dragPointerId = null; 173 if (wasDrag) { 174 window.setTimeout(() => { 175 dragMoved = false; 176 }, 280); 177 } else { 178 dragMoved = false; 179 } 180 if (!root.matches(":hover")) { 181 paused = false; 182 startAutoplay(); 183 } 184 } 185 186 const dragSurface = track || stage || root; 187 if (!reduceMotion && dragSurface) { 188 dragSurface.addEventListener( 189 "pointerdown", 190 (e) => { 191 if (e.button !== 0 && e.pointerType !== "touch") return; 192 dragPointerId = e.pointerId; 193 dragStartX = e.clientX; 194 dragStartIndex = activeIndex; 195 dragMoved = false; 196 paused = true; 197 stopAutoplay(); 198 dragSurface.setPointerCapture?.(e.pointerId); 199 track?.classList.add("is-orbit-dragging"); 200 }, 201 { passive: true } 202 ); 203 204 dragSurface.addEventListener( 205 "pointermove", 206 (e) => { 207 if (e.pointerId !== dragPointerId) return; 208 const dx = e.clientX - dragStartX; 209 if (Math.abs(dx) > DRAG_CLICK_THRESHOLD) dragMoved = true; 210 if (!dragMoved) return; 211 e.preventDefault(); 212 if (isCompact) { 213 const steps = Math.round(-dx / DRAG_SLIDE_PX);
214 setActive(dragStartIndex + steps); 215 } else { 216 angle = -dragStartIndex * stepDeg + dx * 0.42; 217 targetAngle = angle; 218 activeIndex = indexFromAngle(angle); 219 applyOrbitAngle(angle); 220 if (raf) { 221 cancelAnimationFrame(raf); 222 raf = null; 223 } 224 } 225 }, 226 { passive: false } 227 ); 228 229 dragSurface.addEventListener("pointerup", (e) => { 230 if (!isCompact && dragMoved) setActive(indexFromAngle(angle)); 231 endDrag(); 232 }); 233 dragSurface.addEventListener("pointercancel", endDrag); 234 } 235 236 cards.forEach((card, i) => { 237 card.addEventListener("click", (e) => { 238 if (dragMoved) { 239 e.preventDefault(); 240 e.stopPropagation(); 241 return; 242 } 243 if (i !== activeIndex) { 244 e.preventDefault(); 245 setActive(i); 246 } 247 }); 248 card.addEventListener("mouseenter", () => { 249 if (dragPointerId != null) return; 250 paused = true; 251 stopAutoplay(); 252 setActive(i); 253 }); 254 }); 255 256 root.addEventListener("mouseleave", () => { 257 if (dragPointerId != null) return; 258 paused = false; 259 startAutoplay(); 260 }); 261 262 if (terminal && !reduceMotion) { 263 const terminalLines = [ 264 "> Bootstrapping OnlineSubsystem⦠OK", 265 "> Loading PlayFab backend module⦠OK", 266 "> Spawning lobby session⦠128 slots", 267 "> Steam auth ticket validated", 268 "> Matchmaking queue: READY", 269 "> Shipping build: v5.4.2 â", 270 ]; 271 let terminalIdx = 0; 272 setInterval(() => { 273 terminalIdx = (terminalIdx + 1) % terminalLines.length; 274 terminal.innerHTML = `<span data-terminal-line>${terminalLines[terminalIdx]}</span>`; 275 }, 2800); 276 } 277 278 if ("IntersectionObserver" in window) { 279 const sectionIo = new IntersectionObserver( 280 (entries) => { 281 entries.forEach((entry) => { 282 sectionVisible = entry.isIntersecting && entry.intersectionRatio >= 0.08; 283 if (sectionVisible) startAutoplay(); 284 else stopAutoplay(); 285 }); 286 }, 287 { threshold: [0, 0.08, 0.2] } 288 ); 289 sectionIo.observe(root); 290 } 291 292 startAutoplay(); 293} 294 295window.__initHeroShowcase = initHeroShowcase; 296 297document.addEventListener("DOMContentLoaded", () => { 298 document.querySelectorAll("[data-hero-showcase]").forEach(initHeroShowcase); 299});
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.