layout.js (4178B)
1 import { 2 getWakaTimeData, 3 startWakaTimeRefreshTimer, 4 subscribeWakaTimeRefresh, 5 } from "./wakatime.js"; 6 7 const BOOT_CHARS = "▓▒░█▄▀■□▪▫●○◆◇"; 8 const SCRAMBLE_DURATION = 400; 9 const SCRAMBLE_INTERVAL = 30; 10 const HOLD_DURATION = 450; 11 const FADE_DURATION = 300; 12 13 let bootPlayed = false; 14 let statusTimer = 0; 15 let unsubscribeWakaFooter = null; 16 17 export async function initLayout() { 18 const initialWakaDataPromise = getWakaTimeData(); 19 20 await playBootAnimation(); 21 22 startStatusClock(); 23 24 await loadWakaFooter(await initialWakaDataPromise); 25 if (unsubscribeWakaFooter) { 26 unsubscribeWakaFooter(); 27 } 28 unsubscribeWakaFooter = subscribeWakaTimeRefresh((data) => { 29 void loadWakaFooter(data); 30 }); 31 startWakaTimeRefreshTimer(); 32 } 33 34 function sleep(ms) { 35 return new Promise((resolve) => setTimeout(resolve, ms)); 36 } 37 38 function scrambleText(length) { 39 return Array.from( 40 { length }, 41 () => BOOT_CHARS[Math.floor(Math.random() * BOOT_CHARS.length)], 42 ).join(""); 43 } 44 45 async function scrambleReveal(element, target, duration) { 46 const steps = Math.floor(duration / SCRAMBLE_INTERVAL); 47 const length = target.length; 48 49 for (let i = 0; i <= steps; i++) { 50 const progress = i / steps; 51 const revealedCount = Math.floor(progress * length); 52 const revealed = target.slice(0, revealedCount); 53 const scrambled = scrambleText(length - revealedCount); 54 element.textContent = revealed + scrambled; 55 await sleep(SCRAMBLE_INTERVAL); 56 } 57 58 element.textContent = target; 59 } 60 61 function createBootOverlay(title, subtitle) { 62 const overlay = document.createElement("div"); 63 overlay.id = "boot-overlay"; 64 overlay.innerHTML = 65 '<div class="boot-terminal"><div class="boot-text"><span class="boot-title"></span><span class="boot-subtitle"></span></div></div>'; 66 67 const bootTitle = overlay.querySelector(".boot-title"); 68 const bootSubtitle = overlay.querySelector(".boot-subtitle"); 69 if (bootTitle) bootTitle.textContent = scrambleText(title.length); 70 if (bootSubtitle) bootSubtitle.textContent = scrambleText(subtitle.length); 71 72 document.body.appendChild(overlay); 73 return overlay; 74 } 75 76 async function playBootAnimation() { 77 const app = document.getElementById("app"); 78 if (!app) return; 79 80 if (bootPlayed || app.classList.contains("ready")) { 81 bootPlayed = true; 82 app.classList.add("ready"); 83 return; 84 } 85 86 const title = app.dataset.bootTitle || "prime8.dev"; 87 const subtitle = app.dataset.bootSubtitle || "www.prime8.dev"; 88 89 const overlay = createBootOverlay(title, subtitle); 90 const bootTitle = overlay.querySelector(".boot-title"); 91 const bootSubtitle = overlay.querySelector(".boot-subtitle"); 92 93 await sleep(100); 94 if (bootTitle) await scrambleReveal(bootTitle, title, SCRAMBLE_DURATION); 95 if (bootSubtitle) 96 await scrambleReveal(bootSubtitle, subtitle, SCRAMBLE_DURATION); 97 98 await sleep(HOLD_DURATION); 99 overlay.classList.add("fade-out"); 100 await sleep(FADE_DURATION); 101 102 overlay.remove(); 103 app.classList.add("ready", "boot-reveal"); 104 bootPlayed = true; 105 } 106 107 function updateStatusTime() { 108 const el = document.getElementById("status-time"); 109 if (!el) return; 110 111 const now = new Date(); 112 const time = now.toLocaleTimeString(); 113 const date = formatStatusDate(now); 114 el.textContent = `${date} ${time}`; 115 } 116 117 function formatStatusDate(date) { 118 const year = String(date.getFullYear()); 119 const month = String(date.getMonth() + 1).padStart(2, "0"); 120 const day = String(date.getDate()).padStart(2, "0"); 121 return `${year}-${month}-${day}`; 122 } 123 124 function startStatusClock() { 125 if (statusTimer) { 126 clearTimeout(statusTimer); 127 } 128 129 const tick = () => { 130 updateStatusTime(); 131 const delay = 1000 - (Date.now() % 1000) || 1000; 132 statusTimer = window.setTimeout(tick, delay); 133 }; 134 135 tick(); 136 } 137 138 async function loadWakaFooter(data = null) { 139 const link = document.getElementById("wakatime-link"); 140 if (!link) return; 141 142 try { 143 const stats = data || (await getWakaTimeData()); 144 if (!stats || !stats.AllTime || !stats.AllTime.TotalText) { 145 link.style.display = "none"; 146 return; 147 } 148 149 link.textContent = stats.AllTime.TotalText; 150 } catch { 151 link.style.display = "none"; 152 } 153 }