commit a41387e654f47cb8b00b59bdfa37ba618b33147f
parent dac0e8a77972d881b726d731d411cc2bd6c84708
Author: Damian Myrda <damian@prime8.dev>
Date: Wed, 21 Jan 2026 20:24:38 -0600
refactor: replace neofetch boot with glitch text reveal
- Replace slow neofetch-style boot (~2.5s) with fast scramble effect (~1.1s)
- Remove fake progress bar in favor of text decode animation
- Fix missing slash in git raw URL path for README images
Diffstat:
3 files changed, 71 insertions(+), 292 deletions(-)
diff --git a/web/src/layouts/Layout.astro b/web/src/layouts/Layout.astro
@@ -377,165 +377,38 @@ const ogImage = image
background: var(--bg);
z-index: 9999;
display: flex;
- align-items: flex-start;
- justify-content: flex-start;
- padding: 20px;
+ align-items: center;
+ justify-content: center;
overflow: hidden;
}
#boot-overlay.fade-out {
- animation: bootFadeOut 0.6s ease-out forwards;
- }
-
- .boot-scanline {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: linear-gradient(
- to bottom,
- transparent 50%,
- rgba(0, 0, 0, 0.1) 50%
- );
- background-size: 100% 4px;
- pointer-events: none;
- opacity: 0.3;
- z-index: 1;
+ animation: bootFadeOut 0.3s ease-out forwards;
}
.boot-terminal {
- font-family: "JetBrains Mono", "SF Mono", monospace;
- font-size: 14px;
- color: var(--fg);
- line-height: 1.6;
- position: relative;
- z-index: 2;
- }
-
- .boot-content {
- white-space: pre-wrap;
- }
-
- .boot-line {
- transition:
- opacity 0.15s ease-out,
- transform 0.15s ease-out;
- }
-
- .boot-line-hidden {
- opacity: 0;
- transform: translateX(-10px);
- }
-
- .boot-line-visible {
- opacity: 1;
- transform: translateX(0);
- }
-
- .boot-cursor {
- display: inline;
- animation: blink 0.7s step-end infinite;
- color: #ffffff;
- }
-
- .boot-progress {
- margin-top: 16px;
- width: 200px;
- height: 4px;
- background: var(--border);
- border-radius: 2px;
- overflow: hidden;
- }
-
- .boot-progress-bar {
- height: 100%;
- width: 0;
- background: linear-gradient(90deg, var(--cyan), var(--accent));
- animation: progressFill 1s ease-out forwards;
- }
-
- @keyframes progressFill {
- 0% {
- width: 0;
- }
- 100% {
- width: 100%;
- }
- }
-
- /* ANSI colors */
- .ansi-bold-yellow {
- color: #d7af5f;
- font-weight: bold;
- }
-
- .ansi-bold-cyan {
- color: #5fafaf;
- font-weight: bold;
+ text-align: center;
}
- .ansi-accent {
- color: var(--accent);
- font-weight: bold;
- }
-
- .ansi-accent {
- color: var(--accent);
+ .boot-text {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
}
- .ansi-bold-accent {
+ .boot-name {
+ font-family: "JetBrains Mono", "SF Mono", monospace;
+ font-size: 32px;
+ font-weight: 600;
color: var(--accent);
- font-weight: bold;
- }
-
- .ansi-bold-green {
- color: #5faf5f;
- font-weight: bold;
+ letter-spacing: -0.5px;
}
- .ansi-white {
- color: #e0e0e0;
- }
-
- .ansi-dim {
- color: #606060;
- }
-
- /* Color blocks for neofetch */
- .ansi-block-black {
- background: #0d0d0d;
- }
- .ansi-block-red {
- background: #d75f5f;
- }
- .ansi-block-green {
- background: #5faf5f;
- }
- .ansi-block-yellow {
- background: #d7af5f;
- }
- .ansi-block-blue {
- background: #5f87d7;
- }
- .ansi-block-magenta {
- background: #af5faf;
- }
- .ansi-block-cyan {
- background: #5fafaf;
- }
- .ansi-block-white {
- background: #c0c0c0;
- }
-
- @keyframes blink {
- 0%,
- 100% {
- opacity: 1;
- }
- 50% {
- opacity: 0;
- }
+ .boot-title {
+ font-family: "JetBrains Mono", "SF Mono", monospace;
+ font-size: 14px;
+ color: var(--fg-dim);
+ letter-spacing: 0.5px;
}
@keyframes bootFadeOut {
diff --git a/web/src/lib/git.ts b/web/src/lib/git.ts
@@ -13,7 +13,7 @@ export async function fetchGitMetadataForProject(
const data = await response.json();
- const baseUrl = `${data.web_url.replace("http://", "https://")}/-/raw${data.default_branch}`;
+ const baseUrl = `${data.web_url.replace("http://", "https://")}/-/raw/${data.default_branch}`;
return {
readme: await fetchReadme(baseUrl, toRawUrl(data.readme_url)),
diff --git a/web/src/scripts/ui/boot.ts b/web/src/scripts/ui/boot.ts
@@ -1,96 +1,28 @@
import env from "../../constants/env";
-interface SystemInfo {
- platform: string;
- browser: string;
- screen: string;
- locale: string;
- uptime: string;
- timezone: string;
+const GLITCH_CHARS = "▓▒░█▄▀■□▪▫●○◆◇";
+const SCRAMBLE_DURATION = 400;
+const SCRAMBLE_INTERVAL = 30;
+const HOLD_DURATION = 200;
+const FADE_DURATION = 300;
+
+function getRandomChar(): string {
+ return GLITCH_CHARS[Math.floor(Math.random() * GLITCH_CHARS.length)];
}
-function getSystemInfo(): SystemInfo {
- const ua = navigator.userAgent;
-
- let platform = "Unknown";
- if (ua.includes("Mac")) platform = "Darwin";
- else if (ua.includes("Win")) platform = "Windows";
- else if (ua.includes("Linux")) platform = "Linux";
- else if (ua.includes("Android")) platform = "Android";
- else if (ua.includes("iPhone") || ua.includes("iPad")) platform = "iOS";
-
- let browser = "Unknown";
- if (ua.includes("Firefox")) browser = "Firefox";
- else if (ua.includes("Edg")) browser = "Edge";
- else if (ua.includes("Chrome")) browser = "Chrome";
- else if (ua.includes("Safari")) browser = "Safari";
-
- const screen = `${window.screen.width}x${window.screen.height}`;
- const locale = navigator.language || "en-US";
-
- const now = new Date();
- const hours = now.getHours().toString().padStart(2, "0");
- const minutes = now.getMinutes().toString().padStart(2, "0");
- const seconds = now.getSeconds().toString().padStart(2, "0");
- const uptime = `${hours}:${minutes}:${seconds}`;
-
- const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
-
- return { platform, browser, screen, locale, uptime, timezone };
-}
-
-function generateNeofetch(sys: SystemInfo): string[] {
- const logo = [
- " .--. ",
- " |o_o | ",
- " |:_/ | ",
- " // \\ \\ ",
- " (| | ) ",
- " /'\\_ _/`\\ ",
- " \\___)=(___/ ",
- ];
-
- const info = [
- `<span class="ansi-bold-accent">prime8</span>@<span class="ansi-bold-accent">dev</span>`,
- '<span class="ansi-dim">──────────────</span>',
- `<span class="ansi-bold-accent">OS</span>: <span class="ansi-white">${sys.platform}</span>`,
- `<span class="ansi-bold-accent">Browser</span>: <span class="ansi-white">${sys.browser}</span>`,
- `<span class="ansi-bold-accent">Resolution</span>: <span class="ansi-white">${sys.screen}</span>`,
- `<span class="ansi-bold-accent">Time</span>: <span class="ansi-white">${sys.uptime}</span>`,
- `<span class="ansi-bold-accent">Timezone</span>: <span class="ansi-white">${sys.timezone}</span>`,
- `<span class="ansi-bold-accent">Locale</span>: <span class="ansi-white">${sys.locale}</span>`,
- ];
-
- const lines: string[] = [];
- const maxLen = Math.max(logo.length, info.length);
-
- for (let i = 0; i < maxLen; i++) {
- const left = logo[i] || " ";
- const right = info[i] || "";
- lines.push(
- `<span class="ansi-bold-white">${left.replace(/ /g, " ")}</span> ${right}`,
- );
- }
-
- return lines;
-}
-
-function generateBootMessages(): string[] {
- const sys = getSystemInfo();
- const neofetchLines = generateNeofetch(sys);
-
- return [...neofetchLines];
+function scrambleText(length: number): string {
+ return Array.from({ length }, () => getRandomChar()).join("");
}
-const CURSOR_CHAR = "█";
-
function createBootOverlay(): HTMLElement {
const overlay = document.createElement("div");
overlay.id = "boot-overlay";
overlay.innerHTML = `
<div class="boot-terminal">
- <div class="boot-content"></div>
- <div class="boot-cursor">${CURSOR_CHAR}</div>
+ <div class="boot-text">
+ <span class="boot-name"></span>
+ <span class="boot-title"></span>
+ </div>
</div>
`;
document.body.appendChild(overlay);
@@ -101,75 +33,49 @@ function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
-async function runBootSequence(overlay: HTMLElement): Promise<void> {
- const content = overlay.querySelector(".boot-content") as HTMLElement;
- const cursor = overlay.querySelector(".boot-cursor") as HTMLElement;
- const messages = generateBootMessages();
-
- await sleep(150);
-
- for (let i = 0; i < messages.length; i++) {
- const line = messages[i];
- const lineEl = document.createElement("div");
- lineEl.className = "boot-line boot-line-hidden";
- lineEl.innerHTML = line || " ";
- content.appendChild(lineEl);
-
- await sleep(40);
- lineEl.classList.remove("boot-line-hidden");
- lineEl.classList.add("boot-line-visible");
- }
+async function scrambleReveal(
+ element: HTMLElement,
+ target: string,
+ duration: number,
+): Promise<void> {
+ const steps = Math.floor(duration / SCRAMBLE_INTERVAL);
+ const length = target.length;
+
+ for (let i = 0; i <= steps; i++) {
+ const progress = i / steps;
+ const revealedCount = Math.floor(progress * length);
- await sleep(300);
-
- const startMsg = document.createElement("div");
- startMsg.className = "boot-line boot-line-hidden";
- startMsg.innerHTML =
- '<br><span class="ansi-bold-white">Starting </span><span class="ansi-bold-accent">prime8dev</span><span class="ansi-bold-white">...</span>';
- content.appendChild(startMsg);
- await sleep(60);
- startMsg.classList.remove("boot-line-hidden");
- startMsg.classList.add("boot-line-visible");
-
- await sleep(400);
-
- cursor.style.display = "none";
-
- const progressBar = document.createElement("div");
- progressBar.className = "boot-line boot-line-visible";
- content.appendChild(progressBar);
-
- const progressWidth = 30;
- const bar = "=".repeat(progressWidth);
-
- progressBar.innerHTML = `<span class="progress-wrapper">[<span class="progress-bar">${bar}</span>]</span> <span class="progress-percent">000</span><span class="progress-percent-sign">%</span>`;
- progressBar.style.fontFamily = "monospace";
-
- const barEl = progressBar.querySelector(".progress-bar") as HTMLElement;
- const percentEl = progressBar.querySelector(".progress-percent") as HTMLElement;
-
- barEl.style.background = `linear-gradient(to right, var(--accent) 0%, var(--accent) 0%, #606060 0%, #606060 100%)`;
- barEl.style.webkitBackgroundClip = "text";
- barEl.style.backgroundClip = "text";
- barEl.style.color = "transparent";
-
- const percentSignEl = progressBar.querySelector(".progress-percent-sign") as HTMLElement;
- percentSignEl.style.color = `var(--accent)`;
-
- for (let i = 0; i <= progressWidth; i++) {
- const pct = (i / progressWidth) * 100;
- barEl.style.background = `linear-gradient(to right, var(--accent) ${pct}%, #606060 ${pct}%)`;
- barEl.style.webkitBackgroundClip = "text";
- barEl.style.backgroundClip = "text";
- percentEl.textContent = Math.round(pct).toString().padStart(3, " ");
- percentEl.style.color = `var(--accent)`;
- await sleep(20);
+ const revealed = target.slice(0, revealedCount);
+ const scrambled = scrambleText(length - revealedCount);
+
+ element.textContent = revealed + scrambled;
+ await sleep(SCRAMBLE_INTERVAL);
}
- await sleep(200);
+ element.textContent = target;
+}
+
+async function runBootSequence(overlay: HTMLElement): Promise<void> {
+ const nameEl = overlay.querySelector(".boot-name") as HTMLElement;
+ const titleEl = overlay.querySelector(".boot-title") as HTMLElement;
+
+ const name = "prime8.dev";
+ const title = "Damian Myrda";
+
+ // Initialize with scrambled text
+ nameEl.textContent = scrambleText(name.length);
+ titleEl.textContent = scrambleText(title.length);
+
+ await sleep(100);
+
+ // Reveal name first, then title
+ await scrambleReveal(nameEl, name, SCRAMBLE_DURATION);
+ await scrambleReveal(titleEl, title, SCRAMBLE_DURATION);
+
+ await sleep(HOLD_DURATION);
overlay.classList.add("fade-out");
- await sleep(400);
+ await sleep(FADE_DURATION);
overlay.remove();
}