The site returned index.html for both /robots.txt and /sitemap.xml because the Workers Assets SPA fallback served the app shell with text/html, so crawlers got a page instead of either file. The site is a single document: About/Projects/Skills/Links are windows, not URLs, and there is no hash or pushState routing. The sitemap therefore lists the one real URL rather than inventing paths that 404. Also adds the description, canonical, and og: tags the head was missing.
2628 lines
95 KiB
HTML
2628 lines
95 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>katOS — oiupoyt</title>
|
|
<meta name="description" content="Terminal desktop portfolio of oiupoyt: draggable windows, a working shell, fastfetch, and links to projects and websites.">
|
|
<link rel="canonical" href="https://oiupoyt.space/">
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:url" content="https://oiupoyt.space/">
|
|
<meta property="og:title" content="katOS — oiupoyt">
|
|
<meta property="og:description" content="Terminal desktop portfolio of oiupoyt: draggable windows, a working shell, fastfetch, and links to projects and websites.">
|
|
|
|
<!-- Google AdSense -->
|
|
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3876936176422477" crossorigin="anonymous"></script>
|
|
|
|
<!-- Fonts & Icons: Space Mono & Share Tech Mono nerdy aesthetic -->
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Space+Mono:ital,wght@0,400;0,700;1,400&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css" rel="stylesheet">
|
|
|
|
<style>
|
|
/* ── Reset & Brutalist Geometry ─────────────────────────────────────────── */
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
border-radius: 0 !important; /* Strict squared-out terminals & windows */
|
|
scrollbar-width: none !important;
|
|
-ms-overflow-style: none !important;
|
|
}
|
|
|
|
body, button, input, textarea, select, pre, code, p, a, span, div, h1, h2, h3, h4, h5, h6 {
|
|
font-family: 'Space Mono', 'Share Tech Mono', monospace;
|
|
}
|
|
|
|
/* Preserve Font Awesome Icons strictly */
|
|
.fa, .fas, .far, .fal, .fad, i[class*="fa-"] {
|
|
font-family: "Font Awesome 6 Free" !important;
|
|
font-weight: 900 !important;
|
|
}
|
|
.fab, i.fab {
|
|
font-family: "Font Awesome 6 Brands" !important;
|
|
font-weight: 400 !important;
|
|
}
|
|
|
|
:root {
|
|
--bg: #000000;
|
|
--panel-bg: rgba(6, 6, 10, 0.22); /* High glass transparency */
|
|
--panel-active: rgba(10, 10, 16, 0.35); /* Focused transparency */
|
|
--border: rgba(255, 255, 255, 0.12);
|
|
--border-hover: rgba(255, 255, 255, 0.35);
|
|
--border-active: rgba(255, 255, 255, 0.7);
|
|
--text: #f0f0f0;
|
|
--text-dim: #666666;
|
|
--text-sub: #999999;
|
|
--accent: #ffffff;
|
|
--glow: 0 0 10px rgba(255, 255, 255, 0.8), 0 0 22px rgba(255, 255, 255, 0.4);
|
|
--glow-subtle: 0 0 8px rgba(255, 255, 255, 0.4);
|
|
--taskbar-h: 36px;
|
|
}
|
|
|
|
body {
|
|
background: var(--bg);
|
|
color: var(--text);
|
|
font-family: 'Space Mono', 'Share Tech Mono', monospace;
|
|
height: 100vh;
|
|
width: 100vw;
|
|
overflow: hidden;
|
|
user-select: none;
|
|
-webkit-font-smoothing: antialiased;
|
|
}
|
|
|
|
/* ── Background Engine & Tactical CRT Layer ────────────────────────────── */
|
|
.video-bg {
|
|
position: fixed;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
z-index: 0;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
display: none;
|
|
transition: opacity 0.5s ease;
|
|
filter: contrast(1.15) brightness(0.88);
|
|
}
|
|
|
|
#bgCanvas {
|
|
position: fixed;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
z-index: 1;
|
|
}
|
|
|
|
.crt-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
pointer-events: none;
|
|
z-index: 2;
|
|
background:
|
|
radial-gradient(ellipse at center, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0.72) 100%),
|
|
repeating-linear-gradient(0deg, rgba(255, 255, 255, 0.012) 0px, rgba(255, 255, 255, 0.012) 1px, transparent 1px, transparent 3px);
|
|
}
|
|
|
|
/* Minimalist On-Screen HUD Notification Toast */
|
|
.kat-toast {
|
|
position: fixed;
|
|
bottom: 48px;
|
|
left: 50%;
|
|
transform: translateX(-50%) translateY(8px);
|
|
background: rgba(10, 10, 14, 0.92);
|
|
border: 1px solid rgba(255, 255, 255, 0.35);
|
|
box-shadow: 0 0 20px rgba(255, 255, 255, 0.25);
|
|
color: #ffffff;
|
|
padding: 6px 16px;
|
|
font-size: 11px;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
z-index: 99999;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: all 0.22s ease;
|
|
}
|
|
|
|
.kat-toast.show {
|
|
opacity: 1;
|
|
transform: translateX(-50%) translateY(0);
|
|
}
|
|
|
|
/* ── Desktop Workspace ───────────────────────────────────────────────────── */
|
|
.desktop {
|
|
position: relative;
|
|
width: 100%;
|
|
height: calc(100vh - var(--taskbar-h));
|
|
z-index: 2;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* ── Transparent Squared Windows ─────────────────────────────────────────── */
|
|
.window {
|
|
position: absolute;
|
|
background: var(--panel-bg);
|
|
border: 1px solid var(--border);
|
|
backdrop-filter: blur(14px) saturate(160%);
|
|
-webkit-backdrop-filter: blur(14px) saturate(160%);
|
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6), 0 0 1px rgba(255, 255, 255, 0.15) inset;
|
|
z-index: 10;
|
|
display: flex;
|
|
flex-direction: column;
|
|
transform: translateZ(0);
|
|
backface-visibility: hidden;
|
|
contain: layout style;
|
|
transition: border-color 0.18s ease, box-shadow 0.18s ease, background 0.18s ease;
|
|
}
|
|
|
|
.window.active {
|
|
border-color: var(--border-active);
|
|
background: var(--panel-active);
|
|
box-shadow: 0 14px 50px rgba(0, 0, 0, 0.75), 0 0 14px rgba(255, 255, 255, 0.18), 0 0 1px rgba(255, 255, 255, 0.4) inset;
|
|
z-index: 30;
|
|
}
|
|
|
|
.window.minimized {
|
|
display: none !important;
|
|
}
|
|
|
|
.window.maximized {
|
|
top: 0 !important;
|
|
left: 0 !important;
|
|
width: 100% !important;
|
|
height: 100% !important;
|
|
}
|
|
|
|
/* Titlebar */
|
|
.titlebar {
|
|
height: 30px;
|
|
min-height: 30px;
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border-bottom: 1px solid var(--border);
|
|
padding: 0 10px;
|
|
font-size: 11px;
|
|
letter-spacing: 0.04em;
|
|
cursor: grab;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
user-select: none;
|
|
}
|
|
|
|
.window.active .titlebar {
|
|
background: rgba(255, 255, 255, 0.06);
|
|
border-bottom-color: var(--border-active);
|
|
}
|
|
|
|
.titlebar:active {
|
|
cursor: grabbing;
|
|
}
|
|
|
|
.title-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
color: var(--text-sub);
|
|
font-weight: 500;
|
|
font-size: 11px;
|
|
}
|
|
|
|
.window.active .title-left {
|
|
color: #ffffff;
|
|
text-shadow: var(--glow-subtle);
|
|
}
|
|
|
|
.title-left i {
|
|
font-size: 10px;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
/* Remade Minimalist Terminal Navigation Dot */
|
|
.window-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.win-dot-close {
|
|
width: 14px;
|
|
height: 14px;
|
|
border: 1px solid rgba(255, 255, 255, 0.22);
|
|
background: rgba(255, 255, 255, 0.03);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all 0.16s ease;
|
|
position: relative;
|
|
}
|
|
|
|
.win-dot-close::before {
|
|
content: '';
|
|
width: 4px;
|
|
height: 4px;
|
|
background: rgba(255, 255, 255, 0.55);
|
|
box-shadow: 0 0 4px rgba(255, 255, 255, 0.35);
|
|
transition: all 0.16s ease;
|
|
}
|
|
|
|
.win-dot-close:hover {
|
|
border-color: #ffffff;
|
|
background: rgba(255, 255, 255, 0.16);
|
|
box-shadow: 0 0 10px rgba(255, 255, 255, 0.45);
|
|
}
|
|
|
|
.win-dot-close:hover::before {
|
|
background: #ffffff;
|
|
width: 6px;
|
|
height: 6px;
|
|
box-shadow: 0 0 8px #ffffff;
|
|
}
|
|
|
|
/* Window Body */
|
|
.window-body {
|
|
flex: 1;
|
|
padding: 14px 16px;
|
|
font-size: 12px;
|
|
line-height: 1.6;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
color: #d0d0d0;
|
|
background: transparent !important;
|
|
}
|
|
|
|
/* Complete scrollbar removal (clean invisible scrolling) */
|
|
* {
|
|
scrollbar-width: none !important;
|
|
-ms-overflow-style: none !important;
|
|
}
|
|
*::-webkit-scrollbar,
|
|
.window-body::-webkit-scrollbar,
|
|
.terminal-body::-webkit-scrollbar,
|
|
body::-webkit-scrollbar,
|
|
.desktop::-webkit-scrollbar {
|
|
display: none !important;
|
|
width: 0 !important;
|
|
height: 0 !important;
|
|
}
|
|
|
|
/* ── Terminal Styling ────────────────────────────────────────────────────── */
|
|
.terminal-body {
|
|
font-family: 'Space Mono', 'Share Tech Mono', monospace !important;
|
|
font-size: 11px;
|
|
line-height: 1.48;
|
|
padding: 12px 14px;
|
|
color: #e5e5e5;
|
|
background: transparent !important;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
height: 100%;
|
|
user-select: text;
|
|
}
|
|
|
|
.fastfetch-pre {
|
|
white-space: pre;
|
|
line-height: 1.35;
|
|
letter-spacing: 0.2px;
|
|
margin-bottom: 12px;
|
|
display: block;
|
|
}
|
|
|
|
.term-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.term-prompt-line {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.term-prompt {
|
|
color: #ffffff;
|
|
text-shadow: var(--glow-subtle);
|
|
font-weight: 600;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.term-input {
|
|
flex: 1;
|
|
background: transparent;
|
|
border: none;
|
|
color: #ffffff;
|
|
font-family: inherit;
|
|
font-size: 11px;
|
|
outline: none;
|
|
caret-color: #fff;
|
|
}
|
|
|
|
.term-output {
|
|
margin-top: 4px;
|
|
margin-bottom: 8px;
|
|
color: #aaa;
|
|
line-height: 1.5;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.term-cmd {
|
|
color: #fff;
|
|
font-weight: 600;
|
|
}
|
|
|
|
/* Links & Lists in Windows */
|
|
.link-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 8px 10px;
|
|
margin-bottom: 6px;
|
|
background: rgba(255, 255, 255, 0.02);
|
|
border: 1px solid rgba(255, 255, 255, 0.09);
|
|
cursor: pointer;
|
|
font-size: 11px;
|
|
color: #ddd;
|
|
transition: all 0.15s ease;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.link-item:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.link-item:hover {
|
|
background: rgba(255, 255, 255, 0.09);
|
|
border-color: #ffffff;
|
|
color: #fff;
|
|
box-shadow: 0 0 10px rgba(255, 255, 255, 0.15);
|
|
transform: translateX(3px);
|
|
}
|
|
|
|
.link-item i {
|
|
width: 16px;
|
|
text-align: center;
|
|
color: #ffffff;
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.link-item .badge {
|
|
margin-left: auto;
|
|
font-size: 10px;
|
|
color: var(--text-dim);
|
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
padding: 1px 6px;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
}
|
|
|
|
.skill-tag {
|
|
display: inline-block;
|
|
padding: 3px 8px;
|
|
margin: 3px;
|
|
background: rgba(255, 255, 255, 0.03);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
font-size: 11px;
|
|
color: #ccc;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
.skill-tag:hover {
|
|
border-color: #ffffff;
|
|
color: #ffffff;
|
|
box-shadow: 0 0 8px rgba(255, 255, 255, 0.3);
|
|
}
|
|
|
|
/* ── Visualizer Container ────────────────────────────────────────────────── */
|
|
.viz-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
position: relative;
|
|
background: transparent !important;
|
|
overflow: hidden;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
#vizCanvas {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: block;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.viz-overlay {
|
|
position: absolute;
|
|
top: 10px;
|
|
left: 12px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
pointer-events: none;
|
|
font-size: 10px;
|
|
letter-spacing: 0.08em;
|
|
color: rgba(255, 255, 255, 0.4);
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.viz-mode-badge {
|
|
padding: 2px 6px;
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
background: rgba(0, 0, 0, 0.6);
|
|
color: #ffffff;
|
|
text-shadow: var(--glow-subtle);
|
|
}
|
|
|
|
.viz-hint {
|
|
position: absolute;
|
|
bottom: 10px;
|
|
right: 12px;
|
|
font-size: 9px;
|
|
color: rgba(255, 255, 255, 0.25);
|
|
letter-spacing: 0.06em;
|
|
text-transform: uppercase;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.viz-idle-msg {
|
|
position: absolute;
|
|
color: rgba(255, 255, 255, 0.25);
|
|
font-size: 11px;
|
|
letter-spacing: 0.15em;
|
|
text-transform: uppercase;
|
|
pointer-events: none;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
/* ── Taskbar ─────────────────────────────────────────────────────────────── */
|
|
.taskbar {
|
|
position: fixed;
|
|
bottom: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: var(--taskbar-h);
|
|
background: rgba(4, 4, 6, 0.94);
|
|
border-top: 1px solid var(--border);
|
|
backdrop-filter: blur(20px);
|
|
z-index: 100;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0 12px;
|
|
font-size: 11px;
|
|
}
|
|
|
|
.taskbar-left {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
height: 100%;
|
|
}
|
|
|
|
/* Clean KatOS button without icon */
|
|
.os-logo-btn {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-weight: 700;
|
|
font-size: 12px;
|
|
letter-spacing: 0.05em;
|
|
color: #ffffff;
|
|
text-shadow: var(--glow-subtle);
|
|
padding: 4px 10px;
|
|
border: 1px solid rgba(255, 255, 255, 0.25);
|
|
background: rgba(255, 255, 255, 0.05);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
.os-logo-btn:hover {
|
|
background: rgba(255, 255, 255, 0.15);
|
|
border-color: #ffffff;
|
|
box-shadow: var(--glow-subtle);
|
|
}
|
|
|
|
.taskbar-divider {
|
|
width: 1px;
|
|
height: 16px;
|
|
background: rgba(255, 255, 255, 0.12);
|
|
margin: 0 2px;
|
|
}
|
|
|
|
.taskbar-windows {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
height: 100%;
|
|
}
|
|
|
|
.taskbar-win-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 4px 10px;
|
|
background: rgba(255, 255, 255, 0.02);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
font-size: 11px;
|
|
color: var(--text-sub);
|
|
cursor: pointer;
|
|
max-width: 140px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
.taskbar-win-btn:hover {
|
|
background: rgba(255, 255, 255, 0.08);
|
|
color: #fff;
|
|
border-color: var(--border-hover);
|
|
}
|
|
|
|
.taskbar-win-btn.active {
|
|
background: rgba(255, 255, 255, 0.12);
|
|
border-color: #ffffff;
|
|
color: #fff;
|
|
box-shadow: var(--glow-subtle);
|
|
}
|
|
|
|
.taskbar-right {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
}
|
|
|
|
/* Music Player Widget in Taskbar */
|
|
.music-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 2px 8px;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
background: rgba(255, 255, 255, 0.02);
|
|
}
|
|
|
|
.music-icon {
|
|
font-size: 11px;
|
|
color: #ffffff;
|
|
animation: spinDisc 3s linear infinite;
|
|
}
|
|
|
|
.music-icon.paused {
|
|
animation-play-state: paused;
|
|
color: var(--text-dim);
|
|
}
|
|
|
|
@keyframes spinDisc {
|
|
from { transform: rotate(0deg); }
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
.music-track {
|
|
max-width: 160px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
font-size: 10px;
|
|
color: var(--text-sub);
|
|
}
|
|
|
|
.player-btn {
|
|
background: transparent;
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
color: #fff;
|
|
width: 20px;
|
|
height: 20px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
font-size: 9px;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
.player-btn:hover {
|
|
background: #fff;
|
|
color: #000;
|
|
box-shadow: var(--glow-subtle);
|
|
}
|
|
|
|
.volume-slider {
|
|
width: 50px;
|
|
height: 3px;
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
background: rgba(255, 255, 255, 0.25);
|
|
outline: none;
|
|
}
|
|
|
|
.volume-slider::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 8px;
|
|
height: 12px;
|
|
background: #ffffff;
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* System Tray Clock */
|
|
.tray-time {
|
|
color: #ffffff;
|
|
font-weight: 600;
|
|
letter-spacing: 0.05em;
|
|
text-shadow: var(--glow-subtle);
|
|
}
|
|
|
|
/* ── Minimal Boot Screen ─────────────────────────────────────────────────── */
|
|
#loadingScreen {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: #000000;
|
|
z-index: 10000;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
transition: opacity 0.5s ease;
|
|
}
|
|
|
|
#loadingScreen.hidden {
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.boot-minimal {
|
|
text-align: center;
|
|
}
|
|
|
|
.boot-logo {
|
|
font-family: 'Space Mono', 'Share Tech Mono', monospace;
|
|
font-size: 54px;
|
|
font-weight: 700;
|
|
letter-spacing: -0.03em;
|
|
color: #ffffff;
|
|
text-shadow: 0 0 24px rgba(255, 255, 255, 0.85), 0 0 45px rgba(255, 255, 255, 0.35);
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.boot-sub {
|
|
font-family: 'Space Mono', 'Share Tech Mono', monospace;
|
|
font-size: 11px;
|
|
color: #777777;
|
|
letter-spacing: 0.12em;
|
|
text-transform: lowercase;
|
|
animation: pulseSub 1.8s infinite alternate;
|
|
}
|
|
|
|
@keyframes pulseSub {
|
|
from { opacity: 0.4; color: #666; }
|
|
to { opacity: 1; color: #ffffff; text-shadow: var(--glow-subtle); }
|
|
}
|
|
|
|
/* ── Desktop Right-Click Context Menu ────────────────────────────────────── */
|
|
.context-menu {
|
|
position: fixed;
|
|
z-index: 9999;
|
|
background: rgba(10, 10, 12, 0.96);
|
|
border: 1px solid var(--border-active);
|
|
box-shadow: 0 10px 30px rgba(0,0,0,0.8);
|
|
backdrop-filter: blur(20px);
|
|
padding: 4px 0;
|
|
min-width: 180px;
|
|
display: none;
|
|
}
|
|
|
|
.ctx-item {
|
|
padding: 7px 14px;
|
|
font-size: 11px;
|
|
color: #ddd;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
transition: background 0.1s ease;
|
|
}
|
|
|
|
.ctx-item:hover {
|
|
background: rgba(255, 255, 255, 0.12);
|
|
color: #ffffff;
|
|
text-shadow: var(--glow-subtle);
|
|
}
|
|
|
|
.ctx-item i {
|
|
width: 14px;
|
|
font-size: 11px;
|
|
}
|
|
|
|
.ctx-sep {
|
|
height: 1px;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
margin: 4px 0;
|
|
}
|
|
|
|
|
|
/* ── Mobile Navigation Bar ───────────────────────────────────────────────── */
|
|
.mobile-nav {
|
|
display: none;
|
|
}
|
|
|
|
/* ── Responsive Mobile & Tablet Experience ──────────────────────────────── */
|
|
@media (max-width: 900px) {
|
|
:root {
|
|
--taskbar-h: 44px;
|
|
}
|
|
|
|
.desktop {
|
|
position: relative !important;
|
|
overflow-y: auto !important;
|
|
overflow-x: hidden !important;
|
|
padding: 6px 12px 76px 12px !important;
|
|
height: calc(100vh - var(--taskbar-h)) !important;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
/* Sticky Mobile Quick App Dock */
|
|
.mobile-nav {
|
|
display: flex !important;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 80;
|
|
gap: 8px;
|
|
padding: 10px 0;
|
|
margin-bottom: 12px;
|
|
overflow-x: auto;
|
|
white-space: nowrap;
|
|
background: rgba(0, 0, 0, 0.8);
|
|
backdrop-filter: blur(16px);
|
|
-webkit-backdrop-filter: blur(16px);
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.12);
|
|
scrollbar-width: none;
|
|
}
|
|
.mobile-nav::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.mob-nav-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 6px 12px;
|
|
background: rgba(255, 255, 255, 0.04);
|
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
|
color: #dcdcdc;
|
|
font-family: 'Space Mono', monospace;
|
|
font-size: 11px;
|
|
cursor: pointer;
|
|
flex-shrink: 0;
|
|
transition: all 0.15s ease;
|
|
}
|
|
.mob-nav-btn i {
|
|
font-size: 10px;
|
|
opacity: 0.75;
|
|
}
|
|
.mob-nav-btn:active, .mob-nav-btn:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
border-color: #ffffff;
|
|
color: #ffffff;
|
|
box-shadow: 0 0 10px rgba(255, 255, 255, 0.4);
|
|
}
|
|
|
|
.window {
|
|
position: static !important;
|
|
width: 100% !important;
|
|
max-width: 100% !important;
|
|
height: auto !important;
|
|
margin-bottom: 16px !important;
|
|
transform: none !important;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.7) !important;
|
|
}
|
|
|
|
.titlebar {
|
|
cursor: default !important;
|
|
height: 32px;
|
|
padding: 0 12px;
|
|
}
|
|
|
|
.window-body {
|
|
padding: 14px 12px !important;
|
|
}
|
|
|
|
.link-item {
|
|
padding: 10px 12px !important;
|
|
min-height: 46px !important;
|
|
}
|
|
.link-item:active {
|
|
background: rgba(255, 255, 255, 0.18) !important;
|
|
transform: scale(0.99);
|
|
}
|
|
|
|
/* Mobile visualizer height */
|
|
.viz-container {
|
|
height: 250px !important;
|
|
min-height: 250px !important;
|
|
}
|
|
|
|
/* Mobile terminal optimization */
|
|
.terminal-body {
|
|
max-height: 440px !important;
|
|
overflow-x: auto !important;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
/* Mobile Taskbar */
|
|
.taskbar {
|
|
height: 44px;
|
|
padding: 0 12px;
|
|
}
|
|
.taskbar-windows, .taskbar-divider {
|
|
display: none !important;
|
|
}
|
|
.os-logo-btn {
|
|
padding: 5px 12px;
|
|
font-size: 12px;
|
|
}
|
|
.music-bar {
|
|
gap: 10px;
|
|
padding: 3px 8px;
|
|
}
|
|
.player-btn {
|
|
width: 28px !important;
|
|
height: 28px !important;
|
|
font-size: 11px !important;
|
|
}
|
|
.volume-slider {
|
|
display: none !important;
|
|
}
|
|
.music-track {
|
|
display: inline-block !important;
|
|
max-width: 110px;
|
|
font-size: 9.5px;
|
|
}
|
|
.tray-time {
|
|
font-size: 11px;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Minimal Boot Screen (Click to Start Audio & Video) -->
|
|
<div id="loadingScreen" onclick="enterKatOS()">
|
|
<div class="boot-minimal">
|
|
<div class="boot-logo">katOS</div>
|
|
<div class="boot-sub">[ click to enter ]</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Dynamic Ambient & Cybernetic Background Layers -->
|
|
<video class="video-bg" id="bgVideo" autoplay muted loop playsinline></video>
|
|
<canvas id="bgCanvas"></canvas>
|
|
<div class="crt-overlay"></div>
|
|
<div id="katToast" class="kat-toast"></div>
|
|
|
|
<!-- Desktop Workspace -->
|
|
<div class="desktop" id="desktop">
|
|
|
|
<!-- Mobile Quick Navigation Bar (Sticky App Dock on Phones) -->
|
|
<nav class="mobile-nav" id="mobileNav" aria-label="Mobile Navigation">
|
|
<button class="mob-nav-btn" onclick="scrollToWin('win-about')"><i class="fas fa-file-lines"></i> about</button>
|
|
<button class="mob-nav-btn" onclick="scrollToWin('win-projects')"><i class="fab fa-git-alt"></i> projects</button>
|
|
<button class="mob-nav-btn" onclick="scrollToWin('win-websites')"><i class="fas fa-globe"></i> websites</button>
|
|
<button class="mob-nav-btn" onclick="scrollToWin('win-links')"><i class="fas fa-folder-closed"></i> links</button>
|
|
<button class="mob-nav-btn" onclick="scrollToWin('win-skills')"><i class="fas fa-terminal"></i> skillz</button>
|
|
<button class="mob-nav-btn" onclick="scrollToWin('win-term')"><i class="fas fa-laptop-code"></i> terminal</button>
|
|
<button class="mob-nav-btn" onclick="scrollToWin('win-viz')"><i class="fas fa-wave-square"></i> visualizer</button>
|
|
</nav>
|
|
|
|
<!-- About Window -->
|
|
<div class="window active" id="win-about">
|
|
<div class="titlebar">
|
|
<div class="title-left">
|
|
<i class="fas fa-file-lines"></i>
|
|
<span>about.md</span>
|
|
</div>
|
|
<div class="window-controls">
|
|
<div class="win-dot-close" onclick="closeWindow('win-about')" title="Close"></div>
|
|
</div>
|
|
</div>
|
|
<div class="window-body">
|
|
<p style="color:#ffffff; font-weight:500; margin-bottom: 12px;">helo, im katR</p>
|
|
<p style="color:#aaa; margin-bottom: 12px;">minecraft stuff, networking experiments, and linux tinkering.</p>
|
|
<p style="color:#555; margin-bottom: 12px; font-family:monospace;">i was on the ███████████████</p>
|
|
<p style="color:#aaa; margin-bottom: 12px; font-family: monospace; font-size: 11px; word-break: break-all; line-height: 1.6;">yo slide me some rq eth: <span style="color:#fff; user-select: all;">0xB77826c34B8FdfD7F8bD600F0F09Fe20658FA01f</span><br>btc: <span style="color:#fff; user-select: all;">bc1qnu8s3awtjxpcewc72y7j6cc38pew0c07ngjfwk</span></p>
|
|
<p style="color:#bbb; margin-bottom: 12px;">i use arch yea</p>
|
|
<p style="color:#ffffff; font-weight:600; text-shadow:var(--glow-subtle);">you da real kat 🤍.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Projects Window -->
|
|
<div class="window" id="win-projects">
|
|
<div class="titlebar">
|
|
<div class="title-left">
|
|
<i class="fab fa-git-alt"></i>
|
|
<span>project.git</span>
|
|
</div>
|
|
<div class="window-controls">
|
|
<div class="win-dot-close" onclick="closeWindow('win-projects')" title="Close"></div>
|
|
</div>
|
|
</div>
|
|
<div class="window-body">
|
|
<a class="link-item" href="https://git.oiupoyt.space/oiupoyt/scythe" target="_blank" rel="noopener">
|
|
<i class="fab fa-rust" style="color:#ffffff;"></i>
|
|
<div>
|
|
<div style="color:#fff; font-weight:500;">scythe</div>
|
|
<div style="font-size:10px; color:#777;">gpu screen recorder • pipewire / dma-buf zero-copy</div>
|
|
</div>
|
|
<span class="badge">rust</span>
|
|
</a>
|
|
<a class="link-item" href="https://git.oiupoyt.space/oiupoyt/brokecrew" target="_blank" rel="noopener">
|
|
<i class="fab fa-rust" style="color:#ffffff;"></i>
|
|
<div>
|
|
<div style="color:#fff; font-weight:500;">brokecrew</div>
|
|
<div style="font-size:10px; color:#777;">llm persona router • token budgeting & pipelines</div>
|
|
</div>
|
|
<span class="badge">rust</span>
|
|
</a>
|
|
<a class="link-item" href="https://git.oiupoyt.space/oiupoyt/model-radar" target="_blank" rel="noopener">
|
|
<i class="fab fa-discord" style="color:#ffffff;"></i>
|
|
<div>
|
|
<div style="color:#fff; font-weight:500;">model-radar</div>
|
|
<div style="font-size:10px; color:#777;">openrouter $0 model monitor bot • live dashboards</div>
|
|
</div>
|
|
<span class="badge">rust</span>
|
|
</a>
|
|
<a class="link-item" href="https://git.oiupoyt.space/oiupoyt/kattoolbox" target="_blank" rel="noopener">
|
|
<i class="fab fa-js" style="color:#ffffff;"></i>
|
|
<div>
|
|
<div style="color:#fff; font-weight:500;">kattoolbox</div>
|
|
<div style="font-size:10px; color:#777;">39 in-browser dev utils • zero telemetry / offline</div>
|
|
</div>
|
|
<span class="badge">ts</span>
|
|
</a>
|
|
<a class="link-item" href="https://git.oiupoyt.space/oiupoyt/just-download" target="_blank" rel="noopener">
|
|
<i class="fas fa-download" style="color:#ffffff;"></i>
|
|
<div>
|
|
<div style="color:#fff; font-weight:500;">just-download</div>
|
|
<div style="font-size:10px; color:#777;">media fetch engine • rust backend + py fallback</div>
|
|
</div>
|
|
<span class="badge">rust</span>
|
|
</a>
|
|
<a class="link-item" href="https://git.oiupoyt.space/oiupoyt/katproxy" target="_blank" rel="noopener">
|
|
<i class="fas fa-network-wired" style="color:#ffffff;"></i>
|
|
<div>
|
|
<div style="color:#fff; font-weight:500;">katproxy</div>
|
|
<div style="font-size:10px; color:#777;">proxy list aggregator & live tcp latency prober</div>
|
|
</div>
|
|
<span class="badge">rust</span>
|
|
</a>
|
|
<a class="link-item" href="https://git.oiupoyt.space/oiupoyt/katdrop" target="_blank" rel="noopener">
|
|
<i class="fas fa-file-arrow-up" style="color:#ffffff;"></i>
|
|
<div>
|
|
<div style="color:#fff; font-weight:500;">katdrop</div>
|
|
<div style="font-size:10px; color:#777;">chunked upload engine with 10-min auto-fuse</div>
|
|
</div>
|
|
<span class="badge">node</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Skills Window -->
|
|
<div class="window" id="win-skills">
|
|
<div class="titlebar">
|
|
<div class="title-left">
|
|
<i class="fas fa-terminal"></i>
|
|
<span>skillz.bash</span>
|
|
</div>
|
|
<div class="window-controls">
|
|
<div class="win-dot-close" onclick="closeWindow('win-skills')" title="Close"></div>
|
|
</div>
|
|
</div>
|
|
<div class="window-body">
|
|
<div style="margin-bottom: 8px; color: var(--text-dim);"># system competencies:</div>
|
|
<span class="skill-tag"><i class="fab fa-linux"></i> linux / arch</span>
|
|
<span class="skill-tag"><i class="fas fa-cube"></i> minecraft servers</span>
|
|
<span class="skill-tag"><i class="fas fa-server"></i> self hosting</span>
|
|
<span class="skill-tag"><i class="fas fa-network-wired"></i> server networking</span>
|
|
<span class="skill-tag"><i class="fab fa-java"></i> java / plugins</span>
|
|
<span class="skill-tag"><i class="fab fa-rust"></i> learning rust</span>
|
|
<div style="margin: 14px 0 6px; color: var(--text-dim);"># personality traits:</div>
|
|
<span class="skill-tag">yappin</span>
|
|
<span class="skill-tag">im crine</span>
|
|
<span class="skill-tag">tweaking</span>
|
|
<span class="skill-tag">hahahahahha</span>
|
|
<span class="skill-tag">cool content</span>
|
|
<span class="skill-tag">very funny</span>
|
|
<span class="skill-tag"><i class="fas fa-cat"></i> veri fond of cats</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Websites & Projects Window -->
|
|
<div class="window" id="win-websites">
|
|
<div class="titlebar">
|
|
<div class="title-left">
|
|
<i class="fas fa-globe"></i>
|
|
<span>websites.html</span>
|
|
</div>
|
|
<div class="window-controls">
|
|
<div class="win-dot-close" onclick="closeWindow('win-websites')" title="Close"></div>
|
|
</div>
|
|
</div>
|
|
<div class="window-body">
|
|
<a class="link-item" href="https://git.oiupoyt.space" target="_blank" rel="noopener">
|
|
<i class="fab fa-git-alt"></i>
|
|
<div>
|
|
<div style="color:#fff; font-weight:500;">git.oiupoyt.space</div>
|
|
<div style="font-size:10px; color:#777;">self-hosted personal git platform</div>
|
|
</div>
|
|
<span class="badge">live</span>
|
|
</a>
|
|
<a class="link-item" href="https://katdrop.oiupoyt.space" target="_blank" rel="noopener">
|
|
<i class="fas fa-file-arrow-up"></i>
|
|
<div>
|
|
<div style="color:#fff; font-weight:500;">katdrop.oiupoyt.space</div>
|
|
<div style="font-size:10px; color:#777;">minimal 10-min file transfer</div>
|
|
</div>
|
|
<span class="badge">live</span>
|
|
</a>
|
|
<a class="link-item" href="https://kats.oiupoyt.space" target="_blank" rel="noopener">
|
|
<i class="fas fa-cat"></i>
|
|
<div>
|
|
<div style="color:#fff; font-weight:500;">kats.oiupoyt.space</div>
|
|
<div style="font-size:10px; color:#777;">look at cat. feel good.</div>
|
|
</div>
|
|
<span class="badge">live</span>
|
|
</a>
|
|
<a class="link-item" href="https://jd.oiupoyt.space" target="_blank" rel="noopener">
|
|
<i class="fas fa-download"></i>
|
|
<div>
|
|
<div style="color:#fff; font-weight:500;">jd.oiupoyt.space</div>
|
|
<div style="font-size:10px; color:#777;">just download yt & ig media</div>
|
|
</div>
|
|
<span class="badge">live</span>
|
|
</a>
|
|
<a class="link-item" href="https://toolbox.oiupoyt.space" target="_blank" rel="noopener">
|
|
<i class="fas fa-wrench"></i>
|
|
<div>
|
|
<div style="color:#fff; font-weight:500;">toolbox.oiupoyt.space</div>
|
|
<div style="font-size:10px; color:#777;">client-side dev utility suite</div>
|
|
</div>
|
|
<span class="badge">live</span>
|
|
</a>
|
|
<a class="link-item" href="https://katproxy.oiupoyt.space" target="_blank" rel="noopener">
|
|
<i class="fas fa-server"></i>
|
|
<div>
|
|
<div style="color:#fff; font-weight:500;">katproxy.oiupoyt.space</div>
|
|
<div style="font-size:10px; color:#777;">minimal proxy list & latency prober</div>
|
|
</div>
|
|
<span class="badge">live</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Social Links Window -->
|
|
<div class="window" id="win-links">
|
|
<div class="titlebar">
|
|
<div class="title-left">
|
|
<i class="fas fa-folder-closed"></i>
|
|
<span>links.zip</span>
|
|
</div>
|
|
<div class="window-controls">
|
|
<div class="win-dot-close" onclick="closeWindow('win-links')" title="Close"></div>
|
|
</div>
|
|
</div>
|
|
<div class="window-body">
|
|
<a class="link-item" href="https://git.oiupoyt.space/oiupoyt" target="_blank" rel="noopener">
|
|
<i class="fab fa-git-alt"></i>
|
|
<span>Forgejo</span>
|
|
<span class="badge">@oiupoyt</span>
|
|
</a>
|
|
<a class="link-item" href="https://github.com/oiupoyt" target="_blank" rel="noopener">
|
|
<i class="fab fa-github"></i>
|
|
<span>GitHub</span>
|
|
<span class="badge">@oiupoyt</span>
|
|
</a>
|
|
<a class="link-item" href="https://discordapp.com/users/1082968457559146506" target="_blank" rel="noopener">
|
|
<i class="fab fa-discord"></i>
|
|
<span>Discord</span>
|
|
<span class="badge">@oiupoyt</span>
|
|
</a>
|
|
<a class="link-item" href="https://www.instagram.com/oiupoyt/" target="_blank" rel="noopener">
|
|
<i class="fab fa-instagram"></i>
|
|
<span>Instagram</span>
|
|
<span class="badge">@oiupoyt</span>
|
|
</a>
|
|
<a class="link-item" href="https://www.youtube.com/@OIUPOYT" target="_blank" rel="noopener">
|
|
<i class="fab fa-youtube"></i>
|
|
<span>YouTube</span>
|
|
<span class="badge">@OIUPOYT</span>
|
|
</a>
|
|
<a class="link-item" href="https://www.roblox.com/users/1529210346/profile" target="_blank" rel="noopener">
|
|
<i class="fas fa-gamepad"></i>
|
|
<span>Roblox</span>
|
|
<span class="badge">oiupoyt</span>
|
|
</a>
|
|
<a class="link-item" href="https://modrinth.com/user/oiupoyt" target="_blank" rel="noopener">
|
|
<i class="fas fa-cube"></i>
|
|
<span>Modrinth</span>
|
|
<span class="badge">mods</span>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Interactive FastFetch Terminal Window -->
|
|
<div class="window active" id="win-term">
|
|
<div class="titlebar">
|
|
<div class="title-left">
|
|
<i class="fas fa-terminal"></i>
|
|
<span>~fastfetch</span>
|
|
</div>
|
|
<div class="window-controls">
|
|
<div class="win-dot-close" onclick="closeWindow('win-term')" title="Close"></div>
|
|
</div>
|
|
</div>
|
|
<div class="terminal-body" id="termBody" onclick="focusTermInput()">
|
|
<div class="fastfetch-pre" id="fastfetchArt"></div>
|
|
<div id="termHistory"></div>
|
|
<div class="term-prompt-line">
|
|
<span class="term-prompt">oiupoyt@archlinux:~$</span>
|
|
<input type="text" class="term-input" id="termInput" autocomplete="off" spellcheck="false" autofocus>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Audio Visualizer Window -->
|
|
<div class="window" id="win-viz">
|
|
<div class="titlebar">
|
|
<div class="title-left">
|
|
<i class="fas fa-wave-square"></i>
|
|
<span>visualizer</span>
|
|
</div>
|
|
<div class="window-controls">
|
|
<div class="win-dot-close" onclick="closeWindow('win-viz')" title="Close"></div>
|
|
</div>
|
|
</div>
|
|
<div class="viz-container" id="vizBox">
|
|
<canvas id="vizCanvas" title="Click to cycle visualizer mode"></canvas>
|
|
<div class="viz-overlay">
|
|
<span>mode:</span>
|
|
<span class="viz-mode-badge" id="vizModeName">mirror pulse</span>
|
|
</div>
|
|
<div class="viz-hint">[click visualizer to switch mode]</div>
|
|
<span class="viz-idle-msg" id="vizIdleMsg">play music to visualize</span>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<!-- Taskbar -->
|
|
<footer class="taskbar">
|
|
<div class="taskbar-left">
|
|
<div class="os-logo-btn" onclick="handleOsLogoClick()" title="katOS Launcher / Scroll to Top">
|
|
<span>katOS</span>
|
|
</div>
|
|
<div class="taskbar-divider"></div>
|
|
<div class="taskbar-windows" id="taskbarWinContainer"></div>
|
|
</div>
|
|
|
|
<div class="taskbar-right">
|
|
<!-- Music Player Controls -->
|
|
<div class="music-bar">
|
|
<i class="fas fa-compact-disc music-icon paused" id="musicDisc"></i>
|
|
<span class="music-track" id="musicTrack">Beat 44</span>
|
|
<button class="player-btn" id="playBtn" onclick="togglePlay()" title="Play/Pause">
|
|
<i class="fas fa-play" id="playIcon"></i>
|
|
</button>
|
|
<input type="range" class="volume-slider" id="volSlider" min="0" max="100" value="45" title="Volume">
|
|
<button class="player-btn" onclick="cycleVizMode()" title="Switch Visualizer Mode">
|
|
<i class="fas fa-sliders"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- System Tray Clock (RAM removed) -->
|
|
<span class="tray-time" id="clock">00:00:00</span>
|
|
</div>
|
|
</footer>
|
|
|
|
<!-- Desktop Right-Click Context Menu -->
|
|
<div class="context-menu" id="ctxMenu">
|
|
<div class="ctx-item" onclick="spawnFastfetch()"><i class="fas fa-terminal"></i> Open Terminal</div>
|
|
<div class="ctx-item" onclick="cycleBgMode()"><i class="fas fa-layer-group"></i> Switch Background Mode</div>
|
|
<div class="ctx-sep"></div>
|
|
<div class="ctx-item" onclick="togglePlay()"><i class="fas fa-music"></i> Play / Pause Music</div>
|
|
<div class="ctx-item" onclick="restoreAllWindows()"><i class="fas fa-window-restore"></i> Show All Windows</div>
|
|
<div class="ctx-item" onclick="autoSpaceWindows()"><i class="fas fa-rotate-left"></i> Reset & Space Windows</div>
|
|
</div>
|
|
|
|
<!-- Audio Element -->
|
|
<audio id="bgMusic" loop preload="auto">
|
|
<source src="music.mp3" type="audio/mpeg">
|
|
</audio>
|
|
|
|
<script>
|
|
// ── Global System State ──────────────────────────────────────────────────
|
|
let topZIndex = 40;
|
|
const windowRegistry = ['win-about', 'win-projects', 'win-skills', 'win-websites', 'win-links', 'win-term', 'win-viz'];
|
|
|
|
// ── Auto-Space Terminals on Start (Organic Staggered Layout) ───────────
|
|
function autoSpaceWindows() {
|
|
const W = window.innerWidth;
|
|
const H = window.innerHeight - 36; // leave room for taskbar
|
|
|
|
if (W >= 1500) {
|
|
// Hand-placed static layout
|
|
setWinPos('win-about', 45, 60, 350, null);
|
|
setWinPos('win-projects', 381, 74, 365, null);
|
|
setWinPos('win-skills', 521, 471, 360, null);
|
|
setWinPos('win-websites', 52, 470, 355, null);
|
|
setWinPos('win-links', 446, 856, 340, null);
|
|
setWinPos('win-term', 45, 915, 620, 378);
|
|
setWinPos('win-viz', 534, 1218, 680, 305);
|
|
} else if (W >= 1150) {
|
|
// Standard laptop / desktop
|
|
setWinPos('win-about', 28, 32, 335, null);
|
|
setWinPos('win-projects', 355, 40, 345, null);
|
|
setWinPos('win-skills', 505, 415, 340, null);
|
|
setWinPos('win-websites', 48, 418, 340, null);
|
|
setWinPos('win-links', 450, 795, 330, null);
|
|
setWinPos('win-term', 22, 785, Math.min(560, W - 805), 378);
|
|
setWinPos('win-viz', 450, 1130, Math.min(620, W - 1140), 285);
|
|
} else if (W >= 800) {
|
|
// Tablet / compact
|
|
setWinPos('win-about', 20, 24, 335, null);
|
|
setWinPos('win-skills', 325, 45, 345, null);
|
|
setWinPos('win-websites', 36, 395, 335, null);
|
|
setWinPos('win-links', 480, 420, 325, null);
|
|
setWinPos('win-projects', 320, 415, 340, null);
|
|
setWinPos('win-term', 685, 20, W - 44, 378);
|
|
setWinPos('win-viz', 1085, 35, W - 70, 280);
|
|
} else {
|
|
// Mobile (<800px): clear inline pixel coordinates so responsive CSS applies cleanly
|
|
windowRegistry.forEach(id => {
|
|
const win = document.getElementById(id);
|
|
if (win) {
|
|
win.style.top = '';
|
|
win.style.left = '';
|
|
win.style.width = '';
|
|
win.style.height = '';
|
|
}
|
|
});
|
|
}
|
|
|
|
restoreAllWindows();
|
|
bringToFront(document.getElementById('win-term'));
|
|
}
|
|
|
|
// ── Mobile App Navigation Quick Jump ─────────────────────────────────────
|
|
function scrollToWin(id) {
|
|
const win = document.getElementById(id);
|
|
if (!win) return;
|
|
if (win.classList.contains('minimized')) {
|
|
win.classList.remove('minimized');
|
|
}
|
|
bringToFront(win);
|
|
|
|
if (window.innerWidth <= 900) {
|
|
const desktop = document.getElementById('desktop');
|
|
if (desktop) {
|
|
const winTop = win.offsetTop - 54;
|
|
desktop.scrollTo({ top: Math.max(0, winTop), behavior: 'smooth' });
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── Taskbar OS Logo Action (Mobile: Scroll to Top, Desktop: Reset Windows) ─
|
|
function handleOsLogoClick() {
|
|
if (window.innerWidth <= 900) {
|
|
const desktop = document.getElementById('desktop');
|
|
if (desktop) {
|
|
desktop.scrollTo({ top: 0, behavior: 'smooth' });
|
|
}
|
|
} else {
|
|
autoSpaceWindows();
|
|
showToast('[ katOS: Windows Reset & Centered ]');
|
|
}
|
|
}
|
|
|
|
function setWinPos(id, top, left, width, height) {
|
|
const win = document.getElementById(id);
|
|
if (!win) return;
|
|
win.style.top = top + 'px';
|
|
win.style.left = left + 'px';
|
|
if (width) win.style.width = width + 'px';
|
|
if (height) {
|
|
win.style.height = height + 'px';
|
|
} else {
|
|
win.style.height = 'auto';
|
|
}
|
|
}
|
|
|
|
// ── Window Dragging & State ──────────────────────────────────────────────
|
|
function bringToFront(win) {
|
|
if (!win) return;
|
|
topZIndex++;
|
|
win.style.zIndex = topZIndex;
|
|
document.querySelectorAll('.window').forEach(w => w.classList.remove('active'));
|
|
win.classList.add('active');
|
|
updateTaskbar();
|
|
}
|
|
|
|
function initWindows() {
|
|
document.querySelectorAll('.window').forEach(win => {
|
|
const bar = win.querySelector('.titlebar');
|
|
if (!bar) return;
|
|
|
|
// Focus on click
|
|
win.addEventListener('mousedown', () => bringToFront(win));
|
|
|
|
// Dragging
|
|
bar.addEventListener('mousedown', e => {
|
|
if (e.target.closest('.win-dot-close')) return;
|
|
bringToFront(win);
|
|
if (win.classList.contains('maximized')) return;
|
|
|
|
const rect = win.getBoundingClientRect();
|
|
const shiftX = e.clientX - rect.left;
|
|
const shiftY = e.clientY - rect.top;
|
|
|
|
win.style.willChange = 'left, top';
|
|
let dragRaf = null;
|
|
|
|
function onMouseMove(moveEvent) {
|
|
const newX = Math.max(0, Math.min(window.innerWidth - 60, moveEvent.clientX - shiftX));
|
|
const newY = Math.max(0, Math.min(window.innerHeight - 70, moveEvent.clientY - shiftY));
|
|
|
|
if (!dragRaf) {
|
|
dragRaf = requestAnimationFrame(() => {
|
|
win.style.left = newX + 'px';
|
|
win.style.top = newY + 'px';
|
|
dragRaf = null;
|
|
});
|
|
}
|
|
}
|
|
|
|
function onMouseUp() {
|
|
if (dragRaf) {
|
|
cancelAnimationFrame(dragRaf);
|
|
dragRaf = null;
|
|
}
|
|
win.style.willChange = 'auto';
|
|
document.removeEventListener('mousemove', onMouseMove);
|
|
document.removeEventListener('mouseup', onMouseUp);
|
|
if (win.id === 'win-viz' && typeof resizeVizCanvas === 'function') resizeVizCanvas();
|
|
}
|
|
|
|
document.addEventListener('mousemove', onMouseMove, { passive: true });
|
|
document.addEventListener('mouseup', onMouseUp);
|
|
});
|
|
});
|
|
|
|
autoSpaceWindows();
|
|
updateTaskbar();
|
|
}
|
|
|
|
function closeWindow(id) {
|
|
const win = document.getElementById(id);
|
|
if (!win) return;
|
|
win.classList.add('minimized');
|
|
updateTaskbar();
|
|
}
|
|
|
|
function restoreWindow(id) {
|
|
const win = document.getElementById(id);
|
|
if (!win) return;
|
|
if (win.classList.contains('minimized')) {
|
|
win.classList.remove('minimized');
|
|
bringToFront(win);
|
|
} else if (win.classList.contains('active')) {
|
|
win.classList.add('minimized');
|
|
} else {
|
|
bringToFront(win);
|
|
}
|
|
updateTaskbar();
|
|
}
|
|
|
|
function restoreAllWindows() {
|
|
windowRegistry.forEach(id => {
|
|
const win = document.getElementById(id);
|
|
if (win) {
|
|
win.classList.remove('minimized');
|
|
win.classList.remove('maximized');
|
|
}
|
|
});
|
|
updateTaskbar();
|
|
}
|
|
|
|
function updateTaskbar() {
|
|
const container = document.getElementById('taskbarWinContainer');
|
|
if (!container) return;
|
|
container.innerHTML = '';
|
|
|
|
windowRegistry.forEach(id => {
|
|
const win = document.getElementById(id);
|
|
if (!win) return;
|
|
const titleEl = win.querySelector('.title-left span');
|
|
const iconEl = win.querySelector('.title-left i');
|
|
const titleText = titleEl ? titleEl.textContent : id;
|
|
const iconClass = iconEl ? iconEl.className : 'fas fa-terminal';
|
|
|
|
const btn = document.createElement('div');
|
|
btn.className = 'taskbar-win-btn' + (win.classList.contains('active') && !win.classList.contains('minimized') ? ' active' : '');
|
|
btn.innerHTML = `<i class="${iconClass}"></i><span>${titleText}</span>`;
|
|
btn.onclick = () => restoreWindow(id);
|
|
container.appendChild(btn);
|
|
});
|
|
}
|
|
|
|
// ── FastFetch ASCII Art & Specs (Monochrome Glow) ─────────────────────────
|
|
const fastfetchArtEl = document.getElementById('fastfetchArt');
|
|
const fa = cls => `<i class="${cls}" style="width:1.2em;text-align:center;display:inline-block;opacity:0.75;font-size:10px;"></i>`;
|
|
|
|
const fastfetchLinesDesktop = [
|
|
` ▄`,
|
|
` ▟█▙`,
|
|
` ▟███▙ <b style="color:#ffffff; text-shadow:var(--glow-subtle);">oiupoyt</b><span style="opacity:0.4">@</span><b style="color:#ffffff;">archlinux</b>`,
|
|
` ▟█████▙ <span style="opacity:0.35">────────────────────────</span>`,
|
|
` ▟███████▙ ${fa('fas fa-laptop')} <span style="opacity:0.45;font-size:10px">OS</span> Arch Linux x86_64`,
|
|
` ▂▔▀▜██████▙ ${fa('fas fa-microchip')} <span style="opacity:0.45;font-size:10px">Kernel</span> Linux 6.19.8-arch1-1`,
|
|
` ▟██▅▂▝▜█████▙ ${fa('fas fa-box-open')} <span style="opacity:0.45;font-size:10px">Packages</span> 1254 (pacman), 60 (flatpak)`,
|
|
` ▟█████████████▙ ${fa('fas fa-terminal')} <span style="opacity:0.45;font-size:10px">Shell</span> fish 4.5.0`,
|
|
` ▟███████████████▙ ${fa('fas fa-display')} <span style="opacity:0.45;font-size:10px">Terminal</span> kitty 0.46.1`,
|
|
` ▟█████████████████▙ ${fa('fas fa-window-restore')} <span style="opacity:0.45;font-size:10px">WM</span> Hyprland 0.54.2 (Wayland)`,
|
|
` ▟███████████████████▙ ${fa('fas fa-palette')} <span style="opacity:0.45;font-size:10px">Theme</span> monochrome-dark`,
|
|
` ▟█████████▛▀▀▜████████▙ ${fa('fas fa-font')} <span style="opacity:0.45;font-size:10px">Font</span> Space Mono (11pt)`,
|
|
` ▟████████▛ ▜███████▙ ${fa('fas fa-clock')} <span style="opacity:0.45;font-size:10px">Uptime</span> <span id="dynUptime">12 hours, 42 mins</span>`,
|
|
` ▟█████████ ████████▙ ${fa('fas fa-music')} <span style="opacity:0.45;font-size:10px">Music</span> Beat 44`,
|
|
` ▟██████████ █████▆▅▄▃▂ ${fa('fas fa-cat')} <span style="opacity:0.45;font-size:10px">Status</span> you da real kat 🤍`,
|
|
` ▟██████████▛ ▜█████████▙ ${fa('fas fa-network-wired')} <span style="opacity:0.45;font-size:10px">Websites</span> katdrop • kats • jd • toolbox • katproxy`,
|
|
` ▟██████▀▀▀ ▀▀██████▙`,
|
|
` ▟███▀▘ ▝▀███▙`,
|
|
` ▟▛▀ ▀▜▙`,
|
|
];
|
|
|
|
function renderFastfetch() {
|
|
if (!fastfetchArtEl) return;
|
|
if (window.innerWidth <= 640) {
|
|
const logoAscii = [
|
|
` ▟█▙`,
|
|
` ▟███▙`,
|
|
` ▟█████▙`,
|
|
` ▟███████▙`,
|
|
` ▂▔▀▜██████▙`,
|
|
` ▟██▅▂▝▜█████▙`,
|
|
` ▟█████████████▙`,
|
|
`▟██████▀▀ ▀▀████▙`
|
|
].map(l => `<div class="term-row" style="color:#ffffff;text-shadow:var(--glow-subtle);">${l}</div>`).join('');
|
|
|
|
const specItems = [
|
|
`<b style="color:#ffffff; text-shadow:var(--glow-subtle);">oiupoyt</b><span style="opacity:0.4">@</span><b style="color:#ffffff;">archlinux</b>`,
|
|
`<span style="opacity:0.35">────────────────────────</span>`,
|
|
`${fa('fas fa-laptop')} <span style="opacity:0.45;font-size:10px">OS</span> Arch Linux x86_64`,
|
|
`${fa('fas fa-microchip')} <span style="opacity:0.45;font-size:10px">Kernel</span> Linux 6.19.8-arch1-1`,
|
|
`${fa('fas fa-box-open')} <span style="opacity:0.45;font-size:10px">Packages</span> 1254 (pacman), 60 (flatpak)`,
|
|
`${fa('fas fa-terminal')} <span style="opacity:0.45;font-size:10px">Shell</span> fish 4.5.0`,
|
|
`${fa('fas fa-display')} <span style="opacity:0.45;font-size:10px">Terminal</span> kitty 0.46.1`,
|
|
`${fa('fas fa-window-restore')} <span style="opacity:0.45;font-size:10px">WM</span> Hyprland 0.54.2`,
|
|
`${fa('fas fa-palette')} <span style="opacity:0.45;font-size:10px">Theme</span> monochrome-dark`,
|
|
`${fa('fas fa-font')} <span style="opacity:0.45;font-size:10px">Font</span> Space Mono (11pt)`,
|
|
`${fa('fas fa-clock')} <span style="opacity:0.45;font-size:10px">Uptime</span> <span id="dynUptime">12 hours, 42 mins</span>`,
|
|
`${fa('fas fa-music')} <span style="opacity:0.45;font-size:10px">Music</span> Beat 44`,
|
|
`${fa('fas fa-cat')} <span style="opacity:0.45;font-size:10px">Status</span> you da real kat 🤍`,
|
|
`${fa('fas fa-network-wired')} <span style="opacity:0.45;font-size:10px">Websites</span> katdrop • kats • jd • toolbox • katproxy`
|
|
].map(s => `<div class="term-row">${s}</div>`).join('');
|
|
|
|
fastfetchArtEl.innerHTML = `<div style="margin-bottom:8px;text-align:center;">${logoAscii}</div><div>${specItems}</div>`;
|
|
} else {
|
|
fastfetchArtEl.innerHTML = fastfetchLinesDesktop.map(l => `<div class="term-row">${l}</div>`).join('');
|
|
}
|
|
}
|
|
renderFastfetch();
|
|
|
|
// ── Interactive Terminal Shell ───────────────────────────────────────────
|
|
const termInput = document.getElementById('termInput');
|
|
const termHistory = document.getElementById('termHistory');
|
|
const termBody = document.getElementById('termBody');
|
|
let cmdHistory = [];
|
|
let historyIdx = -1;
|
|
|
|
function focusTermInput() {
|
|
termInput.focus();
|
|
}
|
|
|
|
termInput.addEventListener('keydown', e => {
|
|
if (e.key === 'Enter') {
|
|
const cmd = termInput.value.trim();
|
|
if (cmd) {
|
|
if (cmdHistory.length > 60) cmdHistory.shift();
|
|
cmdHistory.push(cmd);
|
|
historyIdx = cmdHistory.length;
|
|
execCommand(cmd);
|
|
}
|
|
termInput.value = '';
|
|
termBody.scrollTop = termBody.scrollHeight;
|
|
} else if (e.key === 'ArrowUp') {
|
|
if (historyIdx > 0) {
|
|
historyIdx--;
|
|
termInput.value = cmdHistory[historyIdx];
|
|
}
|
|
} else if (e.key === 'ArrowDown') {
|
|
if (historyIdx < cmdHistory.length - 1) {
|
|
historyIdx++;
|
|
termInput.value = cmdHistory[historyIdx];
|
|
} else {
|
|
historyIdx = cmdHistory.length;
|
|
termInput.value = '';
|
|
}
|
|
} else if (e.key === 'Tab') {
|
|
e.preventDefault();
|
|
const cur = termInput.value.trim().toLowerCase();
|
|
const available = ['help', 'fastfetch', 'neofetch', 'clear', 'cat', 'cowsay', 'fortune', 'sl', 'cmatrix', 'ls', 'projects', 'skills', 'music', 'matrix', 'whoami', 'date', 'reboot'];
|
|
const match = available.find(c => c.startsWith(cur));
|
|
if (match) termInput.value = match;
|
|
}
|
|
});
|
|
|
|
// ── Linux Fortunes & Cowsay Generator ────────────────────────────────────
|
|
const katFortunes = [
|
|
"Arch Linux: because installing an OS in 40 manual commands builds character.",
|
|
"A cat has 9 lives. A Linux kernel panic has infinite retries.",
|
|
"There are 10 types of people: those who understand binary, and cats who sit on keyboards.",
|
|
"Minecraft server status: running out of RAM. Solution: fed the cat instead.",
|
|
"Hyprland config broke at 3 AM. Worth it for the 1px obsidian border.",
|
|
"sudo rm -rf / --no-preserve-root : The cat walked across the keyboard again.",
|
|
"Fastfetch is not just a tool; it's an obsidian lifestyle.",
|
|
"Real hackers don't sleep, they just wait for pacman -Syu to finish.",
|
|
"Self-hosting everything because trusting the cloud is a vulnerability.",
|
|
"Your CPU is currently 99.8% cooler because katOS is running.",
|
|
"yo sup i am the sillest internet goof"
|
|
];
|
|
|
|
function getRandomFortune() {
|
|
return katFortunes[Math.floor(Math.random() * katFortunes.length)];
|
|
}
|
|
|
|
function formatCowsay(msg) {
|
|
const cleanMsg = (msg || 'meow').replace(/[\r\n]+/g, ' ').trim();
|
|
const maxLen = 40;
|
|
const wrapped = [];
|
|
let words = cleanMsg.split(' ');
|
|
let curLine = '';
|
|
for (let w of words) {
|
|
if ((curLine + ' ' + w).trim().length > maxLen) {
|
|
if (curLine) wrapped.push(curLine);
|
|
curLine = w;
|
|
} else {
|
|
curLine = (curLine ? curLine + ' ' : '') + w;
|
|
}
|
|
}
|
|
if (curLine) wrapped.push(curLine);
|
|
if (wrapped.length === 0) wrapped.push('meow');
|
|
|
|
const width = Math.max(...wrapped.map(l => l.length));
|
|
const borderTop = ' ' + '_'.repeat(width + 2);
|
|
const borderBot = ' ' + '-'.repeat(width + 2);
|
|
const body = wrapped.map((line, idx) => {
|
|
const pad = ' '.repeat(width - line.length);
|
|
if (wrapped.length === 1) return `< ${line} >`;
|
|
if (idx === 0) return `/ ${line}${pad} \\`;
|
|
if (idx === wrapped.length - 1) return `\\ ${line}${pad} /`;
|
|
return `| ${line}${pad} |`;
|
|
}).join('\n');
|
|
|
|
return `${borderTop}\n${body}\n${borderBot}\n \\ ^__^\n \\ (oo)\\_______\n (__)\\ )\\/\\\n ||----w |\n || ||`;
|
|
}
|
|
|
|
// ── Classic "sl" Steam Locomotive Easter Egg ──────────────────────────────
|
|
function runSteamLocomotive() {
|
|
const trainContainer = document.createElement('pre');
|
|
trainContainer.className = 'sl-train';
|
|
trainContainer.style.cssText = 'font-family:monospace; line-height:1.15; color:#ffffff; text-shadow:var(--glow-subtle); white-space:pre; margin:8px 0; overflow:hidden; font-size:10px;';
|
|
termHistory.appendChild(trainContainer);
|
|
termInput.disabled = true;
|
|
|
|
const trainArt = [
|
|
` ( ) (@@) ( ) (@) () @@ O`,
|
|
` (@@@)`,
|
|
` ( )`,
|
|
` (@@@@)`,
|
|
` ( )`,
|
|
` ==== ________ ___________`,
|
|
` _D _| |_______/ \\__I_I_____===__|_________|`,
|
|
` |(_)--- | H\\________/ | | =|___ ___|`,
|
|
` / | | H | | | | | |`,
|
|
` | | | H |__--------------------| [katOS] |`,
|
|
` | ________|___H__/__|_____/[ ] \\___|____|_________|`,
|
|
` (O)____(O) [ ] (O)(O) (O) (O) (O)(O)`
|
|
];
|
|
|
|
let offset = 42;
|
|
const interval = setInterval(() => {
|
|
if (offset < -68) {
|
|
clearInterval(interval);
|
|
trainContainer.remove();
|
|
termInput.disabled = false;
|
|
termInput.focus();
|
|
const doneRow = document.createElement('div');
|
|
doneRow.className = 'term-output';
|
|
doneRow.innerHTML = `<span style="color:#ffffff; text-shadow:var(--glow-subtle);"><i class="fas fa-train"></i> sl: Steam Locomotive chugged past oiupoyt@archlinux!</span>`;
|
|
termHistory.appendChild(doneRow);
|
|
termBody.scrollTop = termBody.scrollHeight;
|
|
return;
|
|
}
|
|
|
|
const indent = Math.max(0, offset);
|
|
const padding = ' '.repeat(indent);
|
|
const sliceAmt = offset < 0 ? Math.abs(offset) : 0;
|
|
|
|
trainContainer.textContent = trainArt
|
|
.map(line => padding + (sliceAmt > 0 ? line.slice(sliceAmt) : line))
|
|
.join('\n');
|
|
|
|
offset -= 3;
|
|
termBody.scrollTop = termBody.scrollHeight;
|
|
}, 50);
|
|
}
|
|
|
|
function execCommand(cmdLine) {
|
|
const parts = cmdLine.split(' ');
|
|
const cmd = parts[0].toLowerCase();
|
|
const arg = parts.slice(1).join(' ');
|
|
|
|
// Prune old terminal nodes to keep DOM lightweight
|
|
while (termHistory.children.length > 60) {
|
|
termHistory.removeChild(termHistory.firstChild);
|
|
}
|
|
|
|
const row = document.createElement('div');
|
|
row.innerHTML = `<span class="term-prompt">oiupoyt@archlinux:~$</span> <span class="term-cmd">${escapeHtml(cmdLine)}</span>`;
|
|
termHistory.appendChild(row);
|
|
|
|
const out = document.createElement('div');
|
|
out.className = 'term-output';
|
|
|
|
switch(cmd) {
|
|
case 'help':
|
|
out.innerHTML = `Available commands:
|
|
<b style="color:#ffffff">fastfetch</b> - display Arch Linux logo & system specs
|
|
<b style="color:#ffffff">cowsay <msg></b> - ASCII cow speaks your text (or fortune)
|
|
<b style="color:#ffffff">fortune</b> - random funny Linux, Arch, or cat fortune
|
|
<b style="color:#ffffff">sl</b> - classic steam locomotive runs across terminal
|
|
<b style="color:#ffffff">cmatrix</b> - stream falling KatOS digital rain
|
|
<b style="color:#ffffff">ls</b> - list virtual directory contents
|
|
<b style="color:#ffffff">cat <file></b> - display contents of a file (e.g. cat about.md)
|
|
<b style="color:#ffffff">projects</b> - list active web projects & links
|
|
<b style="color:#ffffff">skills</b> - show competencies & skills
|
|
<b style="color:#ffffff">music</b> - music play | pause | vol <0-100>
|
|
<b style="color:#ffffff">bg <mode></b> - change background (liquid, horizon, matrix, video)
|
|
<b style="color:#ffffff">catpic</b> - fetch a fresh cat image
|
|
<b style="color:#ffffff">whoami</b> - print current session bio
|
|
<b style="color:#ffffff">donate</b> - crypto donation addresses (eth / btc)
|
|
<b style="color:#ffffff">clear</b> - clear terminal screen
|
|
<b style="color:#ffffff">date</b> - show current date and time
|
|
<b style="color:#ffffff">reboot</b> - restart katOS desktop`;
|
|
break;
|
|
|
|
case 'fastfetch':
|
|
case 'neofetch':
|
|
fastfetchArtEl.style.display = 'block';
|
|
renderFastfetch();
|
|
out.textContent = '';
|
|
break;
|
|
|
|
case 'cowsay':
|
|
const cowMsg = arg.trim() || getRandomFortune();
|
|
out.innerHTML = `<pre style="font-family:monospace;margin:0;color:#fff;text-shadow:var(--glow-subtle);">${escapeHtml(formatCowsay(cowMsg))}</pre>`;
|
|
break;
|
|
|
|
case 'fortune':
|
|
const fort = getRandomFortune();
|
|
out.innerHTML = `<span style="color:#ffffff;text-shadow:var(--glow-subtle);font-style:italic;">"${escapeHtml(fort)}"</span>`;
|
|
break;
|
|
|
|
case 'sl':
|
|
runSteamLocomotive();
|
|
return;
|
|
|
|
case 'cmatrix':
|
|
setBgMode('matrix-rain');
|
|
out.innerHTML = `<span style="color:#00ff66; text-shadow:0 0 8px rgba(0,255,100,0.8); font-weight:bold;">[ CMATRIX PROTOCOL ENGAGED ]</span>\n"Wake up, oiupoyt... The matrix has you."\nKatOS digital rain is now cascading.\nType <b style="color:#ffffff">bg liquid</b> to return to Liquid Void.`;
|
|
break;
|
|
|
|
case 'ls':
|
|
out.innerHTML = `<span style="color:#ffffff">about.md</span> <span style="color:#ffffff">skillz.bash</span> <span style="color:#ffffff">websites.html</span> <span style="color:#ffffff">links.zip</span> <span style="color:#888">music.mp3</span> <span style="color:#888">backgroundVideo.mp4</span>`;
|
|
break;
|
|
|
|
case 'cat':
|
|
if (!arg) {
|
|
out.textContent = 'cat: missing file operand. try: cat about.md (or: catpic)';
|
|
} else if (arg.includes('about')) {
|
|
out.textContent = `yo sup i am the sillest internet goof\nminecraft shyt, networking experiments, and linux customization.\ni was on the ███████████████\nyo slide me some rq eth: 0xB77826c34B8FdfD7F8bD600F0F09Fe20658FA01f\nbtc: bc1qnu8s3awtjxpcewc72y7j6cc38pew0c07ngjfwk\ni use arch yea\nyou da real kat 🤍.`;
|
|
} else if (arg.includes('skill')) {
|
|
out.textContent = `linux / arch • Minecraft Servers • Self Hosting • server networking • Java / Plugins • learning rust • yappin • im crine • tweaking • hahahahahha • cool content • very funny • veri fond of cats`;
|
|
} else if (arg.includes('website')) {
|
|
out.innerHTML = `• git: https://git.oiupoyt.space\n• katdrop: https://katdrop.oiupoyt.space\n• kats: https://kats.oiupoyt.space\n• jd: https://jd.oiupoyt.space\n• toolbox: https://toolbox.oiupoyt.space\n• katproxy: https://katproxy.oiupoyt.space`;
|
|
} else if (arg.includes('link')) {
|
|
out.innerHTML = `• Forgejo: https://git.oiupoyt.space/oiupoyt\n• GitHub: https://github.com/oiupoyt\n• Discord: @oiupoyt\n• Instagram: @oiupoyt\n• YouTube: @OIUPOYT\n• Modrinth: oiupoyt\n• Roblox: oiupoyt`;
|
|
} else {
|
|
out.textContent = `cat: ${arg}: Permission denied or file is binary`;
|
|
}
|
|
break;
|
|
|
|
case 'donate':
|
|
case 'crypto':
|
|
out.innerHTML = `yo slide me some rq:\n• eth: <span style="color:#ffffff;">0xB77826c34B8FdfD7F8bD600F0F09Fe20658FA01f</span>\n• btc: <span style="color:#ffffff;">bc1qnu8s3awtjxpcewc72y7j6cc38pew0c07ngjfwk</span>`;
|
|
break;
|
|
|
|
case 'projects':
|
|
out.innerHTML = `oiupoyt projects:
|
|
1. <a href="https://git.oiupoyt.space" target="_blank" style="color:#ffffff; text-decoration:underline;">git.oiupoyt.space</a> - self-hosted personal git platform
|
|
2. <a href="https://katdrop.oiupoyt.space" target="_blank" style="color:#ffffff; text-decoration:underline;">katdrop.oiupoyt.space</a> - minimal 10-min file transfer service
|
|
3. <a href="https://kats.oiupoyt.space" target="_blank" style="color:#ffffff; text-decoration:underline;">kats.oiupoyt.space</a> - look at cat. feel good.
|
|
4. <a href="https://jd.oiupoyt.space" target="_blank" style="color:#ffffff; text-decoration:underline;">jd.oiupoyt.space</a> - youtube & instagram media downloader
|
|
5. <a href="https://toolbox.oiupoyt.space" target="_blank" style="color:#ffffff; text-decoration:underline;">toolbox.oiupoyt.space</a> - client-side developer utility suite
|
|
6. <a href="https://katproxy.oiupoyt.space" target="_blank" style="color:#ffffff; text-decoration:underline;">katproxy.oiupoyt.space</a> - minimal proxy list & latency prober`;
|
|
break;
|
|
|
|
case 'skills':
|
|
out.textContent = 'linux / arch, minecraft servers, self hosting, server networking, java / plugins, yappin, im crine, tweaking, cats';
|
|
break;
|
|
|
|
case 'music':
|
|
if (arg === 'play') {
|
|
playMusic();
|
|
out.textContent = 'audio: playing';
|
|
} else if (arg === 'pause') {
|
|
pauseMusic();
|
|
out.textContent = 'audio: paused';
|
|
} else if (arg.startsWith('vol')) {
|
|
const val = parseInt(arg.split(' ')[1]);
|
|
if (!isNaN(val) && val >= 0 && val <= 100) {
|
|
setVolume(val);
|
|
out.textContent = `audio: volume set to ${val}%`;
|
|
} else {
|
|
out.textContent = 'usage: music vol <0-100>';
|
|
}
|
|
} else {
|
|
out.textContent = 'usage: music [play | pause | vol <0-100>]';
|
|
}
|
|
break;
|
|
|
|
case 'catpic':
|
|
case 'cat-image':
|
|
out.innerHTML = `Fetching cat...`;
|
|
fetch('https://api.thecatapi.com/v1/images/search')
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
if (data && data[0] && data[0].url) {
|
|
out.innerHTML = `<img src="${data[0].url}" style="max-width:280px;max-height:200px;border:1px solid #444;margin-top:6px;display:block;"><span style="font-size:10px;color:#888;">meow</span>`;
|
|
termBody.scrollTop = termBody.scrollHeight;
|
|
}
|
|
}).catch(() => {
|
|
out.textContent = 'cat error: no cat found :(';
|
|
});
|
|
break;
|
|
|
|
case 'bg':
|
|
case 'background':
|
|
if (arg === 'liquid' || arg === 'void' || arg === 'fluid') {
|
|
setBgMode('liquid-void');
|
|
out.textContent = 'background set to: Liquid Void Fluid';
|
|
} else if (arg === 'cyber' || arg === 'horizon') {
|
|
setBgMode('cyber-horizon');
|
|
out.textContent = 'background set to: Cyber 3D Horizon';
|
|
} else if (arg === 'matrix' || arg === 'rain') {
|
|
setBgMode('matrix-rain');
|
|
out.textContent = 'background set to: KatOS Digital Rain';
|
|
} else if (arg === 'video') {
|
|
setBgMode('video-ambient');
|
|
out.textContent = 'background set to: Ambient Video';
|
|
} else {
|
|
cycleBgMode();
|
|
out.textContent = `background switched to: ${bgModes[currentBgModeIdx].name}\nusage: bg [liquid | horizon | matrix | video]`;
|
|
}
|
|
break;
|
|
|
|
case 'matrix':
|
|
setBgMode('matrix-rain');
|
|
out.textContent = 'background set to: KatOS Digital Rain';
|
|
break;
|
|
|
|
case 'clear':
|
|
termHistory.innerHTML = '';
|
|
fastfetchArtEl.style.display = 'none';
|
|
return;
|
|
|
|
case 'whoami':
|
|
out.textContent = 'oiupoyt — linux enthusiast, minecraft tinkerer, cat lover';
|
|
break;
|
|
|
|
case 'uname':
|
|
case 'uname -a':
|
|
out.textContent = 'Linux katOS 6.19.8-arch1-1 #1 SMP PREEMPT_DYNAMIC x86_64 GNU/Linux';
|
|
break;
|
|
|
|
case 'date':
|
|
out.textContent = new Date().toString();
|
|
break;
|
|
|
|
case 'reboot':
|
|
location.reload();
|
|
return;
|
|
|
|
default:
|
|
out.textContent = `bash: ${cmd}: command not found. type 'help' for available commands.`;
|
|
}
|
|
|
|
termHistory.appendChild(out);
|
|
termBody.scrollTop = termBody.scrollHeight;
|
|
}
|
|
|
|
function spawnFastfetch() {
|
|
restoreWindow('win-term');
|
|
fastfetchArtEl.style.display = 'block';
|
|
}
|
|
|
|
function escapeHtml(str) {
|
|
return String(str).replace(/[&<>'"]/g, t => ({
|
|
'&': '&', '<': '<', '>': '>', "'": ''', '"': '"'
|
|
}[t] || t));
|
|
}
|
|
|
|
// ── Audio & Visualizer Engine (Glow White Monochrome) ─────────────────────
|
|
const bgMusic = document.getElementById('bgMusic');
|
|
const playBtn = document.getElementById('playBtn');
|
|
const playIcon = document.getElementById('playIcon');
|
|
const musicDisc = document.getElementById('musicDisc');
|
|
const volSlider = document.getElementById('volSlider');
|
|
const vizCanvas = document.getElementById('vizCanvas');
|
|
const vizCtx = vizCanvas.getContext('2d');
|
|
const vizIdleMsg = document.getElementById('vizIdleMsg');
|
|
const vizModeName = document.getElementById('vizModeName');
|
|
|
|
let isPlaying = false;
|
|
let audioCtx, analyser, source, vizAnimId;
|
|
let vizConnected = false;
|
|
|
|
const vizModes = ['mirror pulse', 'spectrum bars'];
|
|
let currentVizModeIdx = 0;
|
|
|
|
function setupAudio() {
|
|
if (vizConnected) return;
|
|
try {
|
|
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
|
analyser = audioCtx.createAnalyser();
|
|
analyser.fftSize = 256;
|
|
analyser.smoothingTimeConstant = 0.82;
|
|
source = audioCtx.createMediaElementSource(bgMusic);
|
|
source.connect(analyser);
|
|
analyser.connect(audioCtx.destination);
|
|
vizConnected = true;
|
|
} catch (err) {
|
|
console.warn('AudioContext init error:', err);
|
|
}
|
|
}
|
|
|
|
function playMusic() {
|
|
setupAudio();
|
|
if (audioCtx && audioCtx.state === 'suspended') {
|
|
audioCtx.resume();
|
|
}
|
|
bgMusic.play().then(() => {
|
|
isPlaying = true;
|
|
playIcon.className = 'fas fa-pause';
|
|
musicDisc.classList.remove('paused');
|
|
startVisualizer();
|
|
}).catch(err => {
|
|
console.warn('Audio play prevented:', err);
|
|
});
|
|
}
|
|
|
|
function pauseMusic() {
|
|
bgMusic.pause();
|
|
isPlaying = false;
|
|
playIcon.className = 'fas fa-play';
|
|
musicDisc.classList.add('paused');
|
|
stopVisualizer();
|
|
}
|
|
|
|
function togglePlay() {
|
|
if (isPlaying) {
|
|
pauseMusic();
|
|
} else {
|
|
playMusic();
|
|
}
|
|
}
|
|
|
|
function setVolume(val) {
|
|
bgMusic.volume = val / 100;
|
|
volSlider.value = val;
|
|
}
|
|
|
|
volSlider.addEventListener('input', e => {
|
|
setVolume(e.target.value);
|
|
});
|
|
|
|
bgMusic.volume = volSlider.value / 100;
|
|
bgMusic.addEventListener('ended', () => {
|
|
pauseMusic();
|
|
});
|
|
|
|
// ── Visualizer Rendering (Glow White Spectrum) ────────────────────────────
|
|
function cycleVizMode() {
|
|
currentVizModeIdx = (currentVizModeIdx + 1) % vizModes.length;
|
|
vizModeName.textContent = vizModes[currentVizModeIdx];
|
|
}
|
|
|
|
vizCanvas.addEventListener('click', cycleVizMode);
|
|
|
|
const sharedFreqData = new Uint8Array(128);
|
|
|
|
function resizeVizCanvas() {
|
|
const rect = vizCanvas.getBoundingClientRect();
|
|
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
vizCanvas.width = (rect.width || 500) * dpr;
|
|
vizCanvas.height = (rect.height || 240) * dpr;
|
|
}
|
|
|
|
function startVisualizer() {
|
|
if (!analyser) return;
|
|
vizIdleMsg.style.opacity = '0';
|
|
resizeVizCanvas();
|
|
cancelAnimationFrame(vizAnimId);
|
|
drawVisualizerFrame();
|
|
}
|
|
|
|
function stopVisualizer() {
|
|
cancelAnimationFrame(vizAnimId);
|
|
resizeVizCanvas();
|
|
const W = vizCanvas.width;
|
|
const H = vizCanvas.height;
|
|
vizCtx.clearRect(0, 0, W, H);
|
|
|
|
// Baseline resting line
|
|
vizCtx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
|
|
vizCtx.lineWidth = 1;
|
|
vizCtx.beginPath();
|
|
const restY = vizModes[currentVizModeIdx] === 'mirror pulse' ? (H / 2) : (H * 0.92);
|
|
vizCtx.moveTo(0, restY);
|
|
vizCtx.lineTo(W, restY);
|
|
vizCtx.stroke();
|
|
|
|
vizIdleMsg.style.opacity = '1';
|
|
}
|
|
|
|
function drawVisualizerFrame() {
|
|
vizAnimId = requestAnimationFrame(drawVisualizerFrame);
|
|
if (document.hidden) return; // Pause rendering on inactive tab
|
|
|
|
const W = vizCanvas.width;
|
|
const H = vizCanvas.height;
|
|
if (W === 0 || H === 0) return;
|
|
|
|
vizCtx.clearRect(0, 0, W, H);
|
|
|
|
analyser.getByteFrequencyData(sharedFreqData);
|
|
const freqData = sharedFreqData;
|
|
const bufLen = sharedFreqData.length;
|
|
|
|
const mode = vizModes[currentVizModeIdx];
|
|
|
|
if (mode === 'spectrum bars') {
|
|
// Mode 1: Squared Glowing White Spectrum Bars
|
|
const barCount = 26;
|
|
const gap = W * 0.012;
|
|
const barW = (W - gap * (barCount + 1)) / barCount;
|
|
const step = Math.floor(bufLen / barCount);
|
|
const maxH = H * 0.82;
|
|
const baseY = H * 0.92;
|
|
|
|
for (let i = 0; i < barCount; i++) {
|
|
let sum = 0;
|
|
for (let k = 0; k < step; k++) sum += freqData[i * step + k];
|
|
const ratio = (sum / step) / 255;
|
|
const bh = ratio * maxH;
|
|
const x = gap + i * (barW + gap);
|
|
const y = baseY - bh;
|
|
|
|
const alpha = 0.25 + ratio * 0.75;
|
|
const grad = vizCtx.createLinearGradient(0, y, 0, baseY);
|
|
grad.addColorStop(0, `rgba(255, 255, 255, ${alpha})`);
|
|
grad.addColorStop(0.5, `rgba(220, 220, 220, ${alpha * 0.6})`);
|
|
grad.addColorStop(1, `rgba(180, 180, 180, ${alpha * 0.1})`);
|
|
|
|
vizCtx.fillStyle = grad;
|
|
vizCtx.fillRect(x, y, barW, bh);
|
|
|
|
// Glowing white peak cap
|
|
if (bh > 4) {
|
|
vizCtx.fillStyle = `rgba(255, 255, 255, ${Math.min(1, alpha + 0.3)})`;
|
|
vizCtx.shadowColor = 'rgba(255, 255, 255, 0.8)';
|
|
vizCtx.shadowBlur = 6;
|
|
vizCtx.fillRect(x, y - 3, barW, 2);
|
|
vizCtx.shadowBlur = 0;
|
|
}
|
|
}
|
|
|
|
// Base line
|
|
vizCtx.strokeStyle = 'rgba(255, 255, 255, 0.12)';
|
|
vizCtx.lineWidth = 1;
|
|
vizCtx.beginPath();
|
|
vizCtx.moveTo(0, baseY);
|
|
vizCtx.lineTo(W, baseY);
|
|
vizCtx.stroke();
|
|
|
|
} else if (mode === 'mirror pulse') {
|
|
// Mode 2: Symmetric Center-Out Pulse
|
|
const barCount = 18;
|
|
const center = W / 2;
|
|
const barW = (W / 2) / (barCount + 1);
|
|
const maxH = H * 0.75;
|
|
const centerY = H / 2;
|
|
|
|
for (let i = 0; i < barCount; i++) {
|
|
const ratio = freqData[i * 4] / 255;
|
|
const bh = ratio * maxH;
|
|
const alpha = 0.2 + ratio * 0.8;
|
|
|
|
vizCtx.fillStyle = `rgba(255, 255, 255, ${alpha})`;
|
|
vizCtx.fillRect(center + i * barW, centerY - bh / 2, barW - 2, bh);
|
|
vizCtx.fillRect(center - (i + 1) * barW, centerY - bh / 2, barW - 2, bh);
|
|
}
|
|
|
|
vizCtx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
|
|
vizCtx.lineWidth = 1;
|
|
vizCtx.beginPath();
|
|
vizCtx.moveTo(0, centerY);
|
|
vizCtx.lineTo(W, centerY);
|
|
vizCtx.stroke();
|
|
}
|
|
}
|
|
|
|
// ── Unique Cybernetic & Audio-Reactive Background Engine ────────────────
|
|
const bgCanvas = document.getElementById('bgCanvas');
|
|
const bgCtx = bgCanvas.getContext('2d');
|
|
const bgVideo = document.getElementById('bgVideo');
|
|
const katToast = document.getElementById('katToast');
|
|
let toastTimer = null;
|
|
|
|
function showToast(text) {
|
|
if (!katToast) return;
|
|
katToast.textContent = text;
|
|
katToast.classList.add('show');
|
|
clearTimeout(toastTimer);
|
|
toastTimer = setTimeout(() => {
|
|
katToast.classList.remove('show');
|
|
}, 1900);
|
|
}
|
|
|
|
function safeStorageGet(key, def = null) {
|
|
try { return localStorage.getItem(key) || def; } catch(e) { return def; }
|
|
}
|
|
function safeStorageSet(key, val) {
|
|
try { localStorage.setItem(key, val); } catch(e) {}
|
|
}
|
|
|
|
const bgModes = [
|
|
{ id: 'liquid-void', name: 'Liquid Void Fluid' },
|
|
{ id: 'cyber-horizon', name: 'Cyber 3D Horizon' },
|
|
{ id: 'matrix-rain', name: 'KatOS Digital Rain' },
|
|
{ id: 'video-ambient', name: 'Ambient Video' }
|
|
];
|
|
let currentBgModeIdx = 0; // Default: 'liquid-void'
|
|
const savedBg = safeStorageGet('katos_bg_mode');
|
|
if (savedBg && savedBg !== 'video-ambient') {
|
|
const idx = bgModes.findIndex(m => m.id === savedBg);
|
|
if (idx !== -1) currentBgModeIdx = idx;
|
|
} else {
|
|
currentBgModeIdx = 0;
|
|
safeStorageSet('katos_bg_mode', 'liquid-void');
|
|
}
|
|
|
|
function setBgMode(modeId, silent = false) {
|
|
const idx = bgModes.findIndex(m => m.id === modeId);
|
|
if (idx !== -1) {
|
|
currentBgModeIdx = idx;
|
|
applyBgMode(silent);
|
|
}
|
|
}
|
|
|
|
function cycleBgMode() {
|
|
currentBgModeIdx = (currentBgModeIdx + 1) % bgModes.length;
|
|
applyBgMode(false);
|
|
}
|
|
|
|
function applyBgMode(silent = false) {
|
|
const mode = bgModes[currentBgModeIdx];
|
|
safeStorageSet('katos_bg_mode', mode.id);
|
|
if (mode.id === 'video-ambient') {
|
|
bgVideo.style.display = 'block';
|
|
tryLoadBgVideo();
|
|
setTimeout(() => { bgVideo.style.opacity = '0.55'; }, 20);
|
|
} else {
|
|
bgVideo.style.opacity = '0';
|
|
try { bgVideo.pause(); } catch(e) {}
|
|
setTimeout(() => {
|
|
if (bgModes[currentBgModeIdx].id !== 'video-ambient') bgVideo.style.display = 'none';
|
|
}, 300);
|
|
}
|
|
if (!silent) showToast(`[ BG: ${mode.name} ]`);
|
|
}
|
|
|
|
// Interactive Mouse Tracking (Position & Kinetic Velocity)
|
|
const bgMouse = {
|
|
x: window.innerWidth / 2,
|
|
y: window.innerHeight / 2,
|
|
targetX: window.innerWidth / 2,
|
|
targetY: window.innerHeight / 2,
|
|
vx: 0,
|
|
vy: 0,
|
|
active: false
|
|
};
|
|
|
|
window.addEventListener('mousemove', e => {
|
|
bgMouse.targetX = e.clientX;
|
|
bgMouse.targetY = e.clientY;
|
|
bgMouse.active = true;
|
|
}, { passive: true });
|
|
|
|
window.addEventListener('mouseleave', () => {
|
|
bgMouse.active = false;
|
|
}, { passive: true });
|
|
|
|
// Gravitational Shockwaves on Click
|
|
const shockwaves = [];
|
|
window.addEventListener('mousedown', e => {
|
|
if (e.target.closest('.window-body') || e.target.closest('input') || e.target.closest('.player-btn')) return;
|
|
if (shockwaves.length > 5) shockwaves.shift();
|
|
shockwaves.push({
|
|
x: e.clientX,
|
|
y: e.clientY,
|
|
radius: 10,
|
|
maxRadius: Math.max(window.innerWidth, window.innerHeight) * 0.45,
|
|
alpha: 0.65,
|
|
power: 1.2
|
|
});
|
|
});
|
|
|
|
// Mobile Touch Interaction for Liquid Void Fluid
|
|
window.addEventListener('touchstart', e => {
|
|
if (e.touches.length > 0) {
|
|
const t = e.touches[0];
|
|
bgMouse.targetX = t.clientX;
|
|
bgMouse.targetY = t.clientY;
|
|
bgMouse.active = true;
|
|
if (!e.target.closest('.window-body') && !e.target.closest('input') && !e.target.closest('button') && !e.target.closest('.player-btn')) {
|
|
if (shockwaves.length > 5) shockwaves.shift();
|
|
shockwaves.push({
|
|
x: t.clientX,
|
|
y: t.clientY,
|
|
radius: 10,
|
|
maxRadius: Math.max(window.innerWidth, window.innerHeight) * 0.45,
|
|
alpha: 0.65,
|
|
power: 1.2
|
|
});
|
|
}
|
|
}
|
|
}, { passive: true });
|
|
|
|
window.addEventListener('touchmove', e => {
|
|
if (e.touches.length > 0) {
|
|
bgMouse.targetX = e.touches[0].clientX;
|
|
bgMouse.targetY = e.touches[0].clientY;
|
|
bgMouse.active = true;
|
|
}
|
|
}, { passive: true });
|
|
|
|
window.addEventListener('touchend', () => {
|
|
bgMouse.active = false;
|
|
}, { passive: true });
|
|
|
|
// Pause CPU/GPU canvas rendering when user switches tabs or minimizes window
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (!document.hidden && isPlaying) {
|
|
startVisualizer();
|
|
}
|
|
});
|
|
|
|
// ── Liquid Void Kinetic Particle Fluid ──────────────────────────────────
|
|
const FLUID_PARTICLE_COUNT = 380;
|
|
const fluidParticles = [];
|
|
|
|
function initFluidParticles() {
|
|
fluidParticles.length = 0;
|
|
const W = bgCanvas.width;
|
|
const H = bgCanvas.height;
|
|
for (let i = 0; i < FLUID_PARTICLE_COUNT; i++) {
|
|
const x = Math.random() * W;
|
|
const y = Math.random() * H;
|
|
fluidParticles.push({
|
|
x: x,
|
|
y: y,
|
|
oldX: x,
|
|
oldY: y,
|
|
vx: (Math.random() - 0.5) * 0.5,
|
|
vy: (Math.random() - 0.5) * 0.5,
|
|
speed: 0.85 + Math.random() * 1.3,
|
|
size: 0.7 + Math.random() * 1.7,
|
|
alpha: 0.22 + Math.random() * 0.65,
|
|
life: Math.random() * 500,
|
|
maxLife: 300 + Math.random() * 600
|
|
});
|
|
}
|
|
}
|
|
|
|
// Incompressible Curl-Noise Flow Field Generator (Zero Allocation & Fast Math)
|
|
let curlVx = 0, curlVy = 0;
|
|
function computeCurl(x, y, t) {
|
|
const s1 = 0.0016;
|
|
const s2 = 0.0034;
|
|
const t1 = t * 0.00035;
|
|
const t2 = t * 0.0003;
|
|
const t3 = t * 0.00045;
|
|
const vx = -s1 * Math.sin(x * s1 + t1) * Math.sin(y * s1 + t2) + 0.45 * s2 * Math.cos((x + y) * s2 - t3);
|
|
const vy = -(s1 * Math.cos(x * s1 + t1) * Math.sin(y * s1 + t2) + 0.45 * s2 * Math.cos((x + y) * s2 - t3));
|
|
const invLen = 1 / (Math.sqrt(vx * vx + vy * vy) || 1);
|
|
curlVx = vx * invLen;
|
|
curlVy = vy * invLen;
|
|
}
|
|
|
|
// Matrix Rain Setup
|
|
let mCols = 0;
|
|
let mDrops = [];
|
|
const mChars = '01KATOSOIUPOYTARCHLINUX7942#$><;:~^._.^';
|
|
function initMatrixDrops() {
|
|
mCols = Math.floor(bgCanvas.width / 18);
|
|
mDrops = Array.from({ length: mCols }, () => Math.floor(Math.random() * -45));
|
|
}
|
|
|
|
let lastTrackedWidth = window.innerWidth;
|
|
function resizeBgCanvas() {
|
|
bgCanvas.width = window.innerWidth;
|
|
bgCanvas.height = window.innerHeight;
|
|
initFluidParticles();
|
|
initMatrixDrops();
|
|
renderFastfetch();
|
|
if (typeof resizeVizCanvas === 'function') resizeVizCanvas();
|
|
}
|
|
window.addEventListener('resize', () => {
|
|
resizeBgCanvas();
|
|
if (Math.abs(window.innerWidth - lastTrackedWidth) > 60) {
|
|
lastTrackedWidth = window.innerWidth;
|
|
autoSpaceWindows();
|
|
}
|
|
});
|
|
resizeBgCanvas();
|
|
|
|
// Audio frequency extractor (zero garbage collection overhead)
|
|
function getAudioBeat() {
|
|
if (!isPlaying || !analyser) return { bass: 0, mid: 0, energy: 0 };
|
|
analyser.getByteFrequencyData(sharedFreqData);
|
|
let bSum = 0; for (let i = 0; i < 5; i++) bSum += sharedFreqData[i];
|
|
let mSum = 0; for (let i = 5; i < 20; i++) mSum += sharedFreqData[i];
|
|
const bass = (bSum * 0.2) * 0.00392156862745098;
|
|
const mid = (mSum * 0.06666666666666667) * 0.00392156862745098;
|
|
return { bass, mid, energy: (bass * 0.65 + mid * 0.35) };
|
|
}
|
|
|
|
let lastAudioBeat = 0;
|
|
let gridOffset = 0;
|
|
|
|
function renderBgFrame(timestamp) {
|
|
requestAnimationFrame(renderBgFrame);
|
|
if (document.hidden) return; // Full GPU/CPU pause on inactive tab
|
|
const W = bgCanvas.width;
|
|
const H = bgCanvas.height;
|
|
if (W === 0 || H === 0) return;
|
|
|
|
// Mouse velocity & position tracking
|
|
bgMouse.vx = (bgMouse.targetX - bgMouse.x);
|
|
bgMouse.vy = (bgMouse.targetY - bgMouse.y);
|
|
bgMouse.x += bgMouse.vx * 0.1;
|
|
bgMouse.y += bgMouse.vy * 0.1;
|
|
|
|
const audio = getAudioBeat();
|
|
|
|
|
|
// Update shockwaves
|
|
for (let i = shockwaves.length - 1; i >= 0; i--) {
|
|
const sw = shockwaves[i];
|
|
sw.radius += 6.5;
|
|
sw.alpha *= 0.955;
|
|
if (sw.radius > sw.maxRadius || sw.alpha < 0.01) {
|
|
shockwaves.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
const currentMode = bgModes[currentBgModeIdx].id;
|
|
|
|
if (currentMode === 'video-ambient') {
|
|
bgCtx.clearRect(0, 0, W, H);
|
|
return;
|
|
}
|
|
|
|
if (currentMode === 'matrix-rain') {
|
|
// High-definition monochrome matrix rain
|
|
bgCtx.fillStyle = 'rgba(0, 0, 0, 0.09)';
|
|
bgCtx.fillRect(0, 0, W, H);
|
|
bgCtx.font = '12px "JetBrains Mono", monospace';
|
|
for (let i = 0; i < mDrops.length; i++) {
|
|
const char = mChars.charAt(Math.floor(Math.random() * mChars.length));
|
|
const x = i * 18;
|
|
const y = mDrops[i] * 18;
|
|
if (y >= 0) {
|
|
bgCtx.fillStyle = '#ffffff';
|
|
bgCtx.shadowColor = '#ffffff';
|
|
bgCtx.shadowBlur = 8;
|
|
bgCtx.fillText(char, x, y);
|
|
bgCtx.shadowBlur = 0;
|
|
bgCtx.fillStyle = 'rgba(210, 210, 210, 0.45)';
|
|
const prevChar = mChars.charAt(Math.floor(Math.random() * mChars.length));
|
|
bgCtx.fillText(prevChar, x, y - 18);
|
|
}
|
|
if (y > H && Math.random() > 0.978) mDrops[i] = 0;
|
|
mDrops[i]++;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (currentMode === 'liquid-void') {
|
|
// ── "Liquid Void" Incompressible Kinetic Particle Fluid ─────────────
|
|
bgCtx.fillStyle = 'rgba(0, 0, 0, 0.14)';
|
|
bgCtx.fillRect(0, 0, W, H);
|
|
|
|
// Render expanding shockwaves
|
|
for (let i = 0; i < shockwaves.length; i++) {
|
|
const sw = shockwaves[i];
|
|
bgCtx.strokeStyle = `rgba(255, 255, 255, ${sw.alpha * 0.35})`;
|
|
bgCtx.lineWidth = 1.2;
|
|
bgCtx.beginPath();
|
|
bgCtx.arc(sw.x, sw.y, sw.radius, 0, Math.PI * 2);
|
|
bgCtx.stroke();
|
|
}
|
|
|
|
const flowSpeed = (1.4 + audio.bass * 2.2);
|
|
|
|
for (let i = 0; i < fluidParticles.length; i++) {
|
|
const p = fluidParticles[i];
|
|
p.oldX = p.x;
|
|
p.oldY = p.y;
|
|
|
|
// 1. Natural Incompressible Curl Flow Field (Zero Allocation)
|
|
computeCurl(p.x, p.y, timestamp);
|
|
p.vx += curlVx * 0.22 * p.speed;
|
|
p.vy += curlVy * 0.22 * p.speed;
|
|
|
|
// 2. Interactive Mouse Vortex & Kinetic Attraction
|
|
if (bgMouse.active) {
|
|
const mdx = bgMouse.x - p.x;
|
|
const mdy = bgMouse.y - p.y;
|
|
const mDistSq = mdx * mdx + mdy * mdy;
|
|
if (mDistSq < 48400 && mDistSq > 0.0001) {
|
|
const mDist = Math.sqrt(mDistSq);
|
|
const factor = (1 - mDist * 0.00454545);
|
|
const invMDist = 1 / mDist;
|
|
|
|
// Tangential vortex whirlpool force
|
|
const swirlX = -mdy * invMDist;
|
|
const swirlY = mdx * invMDist;
|
|
p.vx += swirlX * factor * 2.8;
|
|
p.vy += swirlY * factor * 2.8;
|
|
|
|
// Gentle radial pull into the vortex center
|
|
p.vx += (mdx * invMDist) * factor * 1.1;
|
|
p.vy += (mdy * invMDist) * factor * 1.1;
|
|
|
|
// Kinetic velocity transfer from rapid cursor movement
|
|
p.vx += bgMouse.vx * factor * 0.18;
|
|
p.vy += bgMouse.vy * factor * 0.18;
|
|
}
|
|
}
|
|
|
|
// 3. Shockwave Impulse on particles
|
|
for (let k = 0; k < shockwaves.length; k++) {
|
|
const sw = shockwaves[k];
|
|
const sdx = p.x - sw.x;
|
|
const sdy = p.y - sw.y;
|
|
const sDist = Math.sqrt(sdx * sdx + sdy * sdy);
|
|
const ringDist = Math.abs(sDist - sw.radius);
|
|
if (ringDist < 45 && sDist > 0.001) {
|
|
const push = (1 - ringDist * 0.02222) * 3.5 * sw.alpha * sw.power;
|
|
const invSDist = 1 / sDist;
|
|
p.vx += (sdx * invSDist) * push;
|
|
p.vy += (sdy * invSDist) * push;
|
|
}
|
|
}
|
|
|
|
// Fluid viscosity / drag
|
|
p.vx *= 0.92;
|
|
p.vy *= 0.92;
|
|
|
|
p.x += p.vx * flowSpeed;
|
|
p.y += p.vy * flowSpeed;
|
|
|
|
// Boundary wrapping with clean respawn
|
|
p.life++;
|
|
if (p.x < -10 || p.x > W + 10 || p.y < -10 || p.y > H + 10 || p.life > p.maxLife) {
|
|
p.x = Math.random() * W;
|
|
p.y = Math.random() * H;
|
|
p.oldX = p.x;
|
|
p.oldY = p.y;
|
|
p.vx = 0;
|
|
p.vy = 0;
|
|
p.life = 0;
|
|
}
|
|
|
|
// 4. Render Silky Luminous Trail
|
|
const vMag = Math.sqrt(p.vx * p.vx + p.vy * p.vy);
|
|
const glowAlpha = Math.min(1, p.alpha * (0.8 + audio.bass * 0.9) + vMag * 0.08);
|
|
|
|
bgCtx.strokeStyle = `rgba(255, 255, 255, ${glowAlpha})`;
|
|
bgCtx.lineWidth = p.size * (1 + audio.bass * 0.45);
|
|
bgCtx.beginPath();
|
|
bgCtx.moveTo(p.oldX, p.oldY);
|
|
bgCtx.lineTo(p.x, p.y);
|
|
bgCtx.stroke();
|
|
|
|
// Bright stardust head point
|
|
bgCtx.fillStyle = `rgba(255, 255, 255, ${Math.min(1, glowAlpha + 0.3)})`;
|
|
bgCtx.fillRect(p.x - p.size / 2, p.y - p.size / 2, p.size, p.size);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (currentMode === 'cyber-horizon') {
|
|
// ── Clean 3D Perspective Horizon (Zero text or clutter) ──────────────
|
|
bgCtx.fillStyle = 'rgba(0, 0, 0, 0.22)';
|
|
bgCtx.fillRect(0, 0, W, H);
|
|
const horizonY = H * 0.65;
|
|
|
|
// Horizon glow
|
|
const hGrad = bgCtx.createLinearGradient(0, horizonY - 35, 0, horizonY + 25);
|
|
hGrad.addColorStop(0, 'rgba(255, 255, 255, 0)');
|
|
hGrad.addColorStop(0.5, `rgba(255, 255, 255, ${0.1 + audio.bass * 0.2})`);
|
|
hGrad.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
bgCtx.fillStyle = hGrad;
|
|
bgCtx.fillRect(0, horizonY - 35, W, 60);
|
|
|
|
// Horizon sharp divider
|
|
bgCtx.strokeStyle = `rgba(255, 255, 255, ${0.4 + audio.bass * 0.4})`;
|
|
bgCtx.lineWidth = 1;
|
|
bgCtx.beginPath();
|
|
bgCtx.moveTo(0, horizonY);
|
|
bgCtx.lineTo(W, horizonY);
|
|
bgCtx.stroke();
|
|
|
|
// Longitudinal radiating lines
|
|
const lineCount = 28;
|
|
const centerX = W / 2;
|
|
const bottomSpread = W * 1.4;
|
|
bgCtx.lineWidth = 1;
|
|
for (let i = -lineCount / 2; i <= lineCount / 2; i++) {
|
|
const topX = centerX + i * 16;
|
|
const bottomX = centerX + (i / (lineCount / 2)) * (bottomSpread / 2);
|
|
const lineAlpha = 0.04 + (1 - Math.abs(i) / (lineCount / 2)) * 0.12 + audio.bass * 0.08;
|
|
bgCtx.strokeStyle = `rgba(255, 255, 255, ${lineAlpha})`;
|
|
bgCtx.beginPath();
|
|
bgCtx.moveTo(topX, horizonY);
|
|
bgCtx.lineTo(bottomX, H);
|
|
bgCtx.stroke();
|
|
}
|
|
|
|
// Transverse rolling perspective lines
|
|
gridOffset = (gridOffset + 0.005 + audio.bass * 0.01) % 1;
|
|
const transLines = 14;
|
|
for (let k = 0; k < transLines; k++) {
|
|
const progress = (k + gridOffset) / transLines;
|
|
const py = horizonY + (H - horizonY) * Math.pow(progress, 2.4);
|
|
const tAlpha = Math.min(0.35, progress * 0.38 + audio.bass * 0.15);
|
|
bgCtx.strokeStyle = `rgba(255, 255, 255, ${tAlpha})`;
|
|
bgCtx.beginPath();
|
|
const stepX = 20;
|
|
for (let x = 0; x <= W; x += stepX) {
|
|
const wave = Math.sin(x * 0.007 + timestamp * 0.003) * audio.bass * 14 * progress;
|
|
if (x === 0) bgCtx.moveTo(x, py + wave);
|
|
else bgCtx.lineTo(x, py + wave);
|
|
}
|
|
bgCtx.stroke();
|
|
}
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(renderBgFrame);
|
|
applyBgMode(true);
|
|
|
|
// ── Desktop Right-Click Context Menu ─────────────────────────────────────
|
|
const ctxMenu = document.getElementById('ctxMenu');
|
|
|
|
document.addEventListener('contextmenu', e => {
|
|
if (e.target.closest('.window-body') || e.target.closest('input')) return;
|
|
e.preventDefault();
|
|
ctxMenu.style.display = 'block';
|
|
let x = e.clientX;
|
|
let y = e.clientY;
|
|
if (x + 190 > window.innerWidth) x = window.innerWidth - 195;
|
|
if (y + 200 > window.innerHeight) y = window.innerHeight - 205;
|
|
ctxMenu.style.left = x + 'px';
|
|
ctxMenu.style.top = y + 'px';
|
|
});
|
|
|
|
document.addEventListener('click', e => {
|
|
if (!e.target.closest('.context-menu')) {
|
|
ctxMenu.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
// ── Clock ────────────────────────────────────────────────────────────────
|
|
function updateClock() {
|
|
const now = new Date();
|
|
const h = String(now.getHours()).padStart(2, '0');
|
|
const m = String(now.getMinutes()).padStart(2, '0');
|
|
const s = String(now.getSeconds()).padStart(2, '0');
|
|
document.getElementById('clock').textContent = `${h}:${m}:${s}`;
|
|
}
|
|
updateClock();
|
|
setInterval(updateClock, 1000);
|
|
|
|
// ── Background Video Loader ──────────────────────────────────────────────
|
|
async function tryLoadBgVideo() {
|
|
if (bgModes[currentBgModeIdx].id !== 'video-ambient') return;
|
|
for (const name of ['backgroundVideo.mp4', 'backgroundVideo.webm']) {
|
|
try {
|
|
const res = await fetch(name);
|
|
if (res.ok) {
|
|
const url = URL.createObjectURL(await res.blob());
|
|
bgVideo.src = url;
|
|
bgVideo.load();
|
|
bgVideo.play().catch(() => {});
|
|
return;
|
|
}
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
|
|
// ── Loading Screen Enter ─────────────────────────────────────────────────
|
|
function enterKatOS() {
|
|
const loadingScreen = document.getElementById('loadingScreen');
|
|
if (loadingScreen && !loadingScreen.classList.contains('hidden')) {
|
|
loadingScreen.classList.add('hidden');
|
|
applyBgMode(true);
|
|
setTimeout(() => {
|
|
playMusic();
|
|
focusTermInput();
|
|
}, 250);
|
|
}
|
|
}
|
|
window.enterKatOS = enterKatOS;
|
|
|
|
const loadingScreen = document.getElementById('loadingScreen');
|
|
if (loadingScreen) {
|
|
loadingScreen.addEventListener('click', enterKatOS);
|
|
}
|
|
|
|
// Keyboard Shortcut (Space to play/pause music, Enter/Space to enter from boot screen)
|
|
window.addEventListener('keydown', e => {
|
|
const loadingEl = document.getElementById('loadingScreen');
|
|
if (loadingEl && !loadingEl.classList.contains('hidden')) {
|
|
e.preventDefault();
|
|
enterKatOS();
|
|
return;
|
|
}
|
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
if (e.code === 'Space') {
|
|
e.preventDefault();
|
|
togglePlay();
|
|
}
|
|
});
|
|
|
|
// Initialize System
|
|
initWindows();
|
|
</script>
|
|
</body>
|
|
</html>
|