prime8dev

prime8dev
git clone https://git.prime8.dev/prime8dev.git
Log | Files | Refs | README | LICENSE

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 }