2676 lines
94 KiB
HTML
2676 lines
94 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>
|
|
|
|
<!-- 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;
|
|
}
|
|
|
|
/* ── Desktop ASCII Cat Companion ─────────────────────────────────────────── */
|
|
.kat-pet-widget {
|
|
position: fixed;
|
|
bottom: calc(var(--taskbar-h) + 12px);
|
|
right: 18px;
|
|
z-index: 85;
|
|
background: rgba(8, 8, 12, 0.82);
|
|
border: 1px solid rgba(255, 255, 255, 0.24);
|
|
backdrop-filter: blur(14px);
|
|
-webkit-backdrop-filter: blur(14px);
|
|
padding: 6px 12px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
user-select: none;
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.7), 0 0 10px rgba(255, 255, 255, 0.08);
|
|
transition: border-color 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
|
|
}
|
|
|
|
.kat-pet-widget:hover, .kat-pet-widget:active {
|
|
border-color: #ffffff;
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 24px rgba(0, 0, 0, 0.85), 0 0 16px rgba(255, 255, 255, 0.35);
|
|
}
|
|
|
|
.kat-pet-ascii {
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
color: #ffffff;
|
|
text-shadow: var(--glow-subtle);
|
|
display: inline-block;
|
|
transition: transform 0.12s ease;
|
|
}
|
|
|
|
.kat-pet-name {
|
|
font-size: 10px;
|
|
color: #888888;
|
|
letter-spacing: 0.08em;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.kat-pet-bubble {
|
|
position: absolute;
|
|
bottom: calc(100% + 9px);
|
|
right: 0;
|
|
background: rgba(8, 8, 12, 0.96);
|
|
border: 1px solid rgba(255, 255, 255, 0.45);
|
|
box-shadow: 0 0 20px rgba(255, 255, 255, 0.25);
|
|
color: #ffffff;
|
|
padding: 6px 12px;
|
|
font-size: 11px;
|
|
white-space: nowrap;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transform: translateY(6px);
|
|
transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
|
}
|
|
|
|
.kat-pet-bubble.show {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.kat-pet-bubble::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 100%;
|
|
right: 18px;
|
|
border-width: 5px;
|
|
border-style: solid;
|
|
border-color: rgba(255, 255, 255, 0.45) transparent transparent transparent;
|
|
}
|
|
|
|
/* ── 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;
|
|
}
|
|
|
|
.kat-pet-widget {
|
|
bottom: calc(var(--taskbar-h) + 8px) !important;
|
|
right: 10px !important;
|
|
padding: 5px 9px !important;
|
|
}
|
|
.kat-pet-name {
|
|
display: none !important;
|
|
}
|
|
}
|
|
</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-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;">i cant stop loving linux, pls speed i need this</p>
|
|
<p style="color:#aaa; margin-bottom: 12px;">minecraft shyt, networking experiments, and linux customization.</p>
|
|
<p style="color:#555; margin-bottom: 12px; font-family:monospace;">i was on the ███████████████</p>
|
|
<p style="color:#fff; text-shadow:var(--glow-subtle); margin-bottom: 12px;">cats > people haha.</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>
|
|
|
|
<!-- 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>
|
|
<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://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>
|
|
</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://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>
|
|
|
|
<!-- Interactive Desktop ASCII Kat Companion -->
|
|
<div class="kat-pet-widget" id="katPet" onclick="interactKatPet()" title="Click to pet kat! (or type 'pet cat' / 'feed cat' in terminal)">
|
|
<div class="kat-pet-bubble" id="katPetBubble">purr 🤍</div>
|
|
<span class="kat-pet-ascii" id="katPetAscii">/\_/\ (=^.^=)</span>
|
|
<span class="kat-pet-name"> </span>
|
|
</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-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) {
|
|
// Broad monitor: spread out organically across entire screen width
|
|
setWinPos('win-about', 45, 60, 350, null);
|
|
setWinPos('win-skills', 380, 110, 360, null);
|
|
setWinPos('win-websites', 85, 510, 355, null);
|
|
setWinPos('win-links', 435, 560, 340, null);
|
|
setWinPos('win-term', 40, 955, Math.min(620, W - 990), 378);
|
|
setWinPos('win-viz', 450, 1060, Math.min(680, W - 1080), 305);
|
|
} else if (W >= 1150) {
|
|
// Standard laptop / desktop: spread out with natural asymmetry & zero overlap
|
|
setWinPos('win-about', 28, 32, 335, null);
|
|
setWinPos('win-skills', 345, 55, 345, null);
|
|
setWinPos('win-websites', 48, 418, 340, null);
|
|
setWinPos('win-links', 365, 438, 330, null);
|
|
setWinPos('win-term', 22, 785, Math.min(560, W - 805), 378);
|
|
setWinPos('win-viz', 420, 790, Math.min(620, W - 810), 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', 355, 420, 325, 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`,
|
|
` ▟██████▀▀▀ ▀▀██████▙`,
|
|
` ▟███▀▘ ▝▀███▙`,
|
|
` ▟▛▀ ▀▜▙`,
|
|
];
|
|
|
|
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`
|
|
].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', 'pet', 'feed', 'ls', 'projects', 'skills', 'music', 'matrix', 'whoami', 'date', 'reboot'];
|
|
const match = available.find(c => c.startsWith(cur));
|
|
if (match) termInput.value = match;
|
|
}
|
|
});
|
|
|
|
// ── Desktop ASCII Cat Companion Interaction & Quotes ─────────────────────
|
|
const katPetAscii = document.getElementById('katPetAscii');
|
|
const katPetBubble = document.getElementById('katPetBubble');
|
|
let katBubbleTimer = null;
|
|
const katPhrases = [
|
|
"purrrrrrrr 🤍",
|
|
"mrrp! :3",
|
|
"cats > people haha",
|
|
"i use arch btw",
|
|
"you da real kat 🤍",
|
|
"*happy headbonk*",
|
|
"feed me fish! (type 'feed cat')",
|
|
"*bobs head to music*",
|
|
"minecraft shyt & linux",
|
|
"sudo give pets"
|
|
];
|
|
|
|
function interactKatPet(action = 'click') {
|
|
if (!katPetAscii || !katPetBubble) return;
|
|
clearTimeout(katBubbleTimer);
|
|
|
|
if (action === 'feed') {
|
|
katPetAscii.textContent = '/\\_/\\ (>.<) 🐟';
|
|
katPetBubble.textContent = '*nom nom* yummy fish! 🤍';
|
|
} else if (action === 'pet') {
|
|
katPetAscii.textContent = '/\\_/\\ (^.^)* 🤍';
|
|
katPetBubble.textContent = 'purrrrrrrr... 🤍';
|
|
} else {
|
|
const phrase = katPhrases[Math.floor(Math.random() * katPhrases.length)];
|
|
katPetAscii.textContent = '/\\_/\\ (^.^)*';
|
|
katPetBubble.textContent = phrase;
|
|
}
|
|
|
|
katPetBubble.classList.add('show');
|
|
|
|
katBubbleTimer = setTimeout(() => {
|
|
katPetBubble.classList.remove('show');
|
|
katPetAscii.textContent = '/\\_/\\ (=^.^=)';
|
|
}, 2600);
|
|
}
|
|
|
|
// ── 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.",
|
|
"Cats > People. This has been mathematically proven by Arch Linux.",
|
|
"Self-hosting everything because trusting the cloud is a vulnerability.",
|
|
"Your CPU is currently 99.8% cooler because katOS is running.",
|
|
"i cant stop loving linux, pls speed i need this."
|
|
];
|
|
|
|
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">pet / feed</b> - pet or feed your desktop kat companion
|
|
<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">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 'pet':
|
|
case 'petcat':
|
|
case 'pet-cat':
|
|
interactKatPet('pet');
|
|
out.innerHTML = `<span style="color:#ffffff; text-shadow:var(--glow-subtle);">/\\_/\\ (^.^)* *purrrrrrrrr* 🤍</span>\nYou petted the desktop kat! Kat is purring happily.`;
|
|
break;
|
|
|
|
case 'feed':
|
|
case 'feedcat':
|
|
case 'feed-cat':
|
|
interactKatPet('feed');
|
|
out.innerHTML = `<span style="color:#ffffff; text-shadow:var(--glow-subtle);">/\\_/\\ (>.<) *nom nom nom* 🐟</span>\nYou fed the desktop kat a fresh fish! (+20 kat affinity)`;
|
|
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: pet cat, feed cat, catpic)';
|
|
} else if (arg.includes('pet')) {
|
|
interactKatPet('pet');
|
|
out.innerHTML = `<span style="color:#ffffff; text-shadow:var(--glow-subtle);">/\\_/\\ (^.^)* *purrrrrrrrr* 🤍</span>\nYou petted the desktop kat! Kat is purring happily.`;
|
|
} else if (arg.includes('feed')) {
|
|
interactKatPet('feed');
|
|
out.innerHTML = `<span style="color:#ffffff; text-shadow:var(--glow-subtle);">/\\_/\\ (>.<) *nom nom nom* 🐟</span>\nYou fed the desktop kat a fresh fish! (+20 kat affinity)`;
|
|
} else if (arg.includes('about')) {
|
|
out.textContent = `i cant stop loving linux, pls speed i need this\nminecraft shyt, networking experiments, and linux customization.\ni was on the ███████████████\ncats > people haha.\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 • yappin • im crine • tweaking • hahahahahha • cool content • very funny • veri fond of cats`;
|
|
} else if (arg.includes('website')) {
|
|
out.innerHTML = `• katdrop: https://katdrop.oiupoyt.space\n• kats: https://kats.oiupoyt.space\n• jd: https://jd.oiupoyt.space\n• toolbox: https://toolbox.oiupoyt.space`;
|
|
} else if (arg.includes('link')) {
|
|
out.innerHTML = `• 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 'projects':
|
|
out.innerHTML = `oiupoyt projects:
|
|
1. <a href="https://katdrop.oiupoyt.space" target="_blank" style="color:#ffffff; text-decoration:underline;">katdrop.oiupoyt.space</a> - minimal 10-min file transfer service
|
|
2. <a href="https://kats.oiupoyt.space" target="_blank" style="color:#ffffff; text-decoration:underline;">kats.oiupoyt.space</a> - look at cat. feel good.
|
|
3. <a href="https://jd.oiupoyt.space" target="_blank" style="color:#ffffff; text-decoration:underline;">jd.oiupoyt.space</a> - youtube & instagram media downloader
|
|
4. <a href="https://toolbox.oiupoyt.space" target="_blank" style="color:#ffffff; text-decoration:underline;">toolbox.oiupoyt.space</a> - client-side developer utility suite`;
|
|
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();
|
|
|
|
// Reactive Kat Pet bobbing with audio bass beat
|
|
if (katPetAscii) {
|
|
if (audio.bass > 0.62) {
|
|
katPetAscii.style.transform = 'translateY(-2.5px) scale(1.08)';
|
|
} else {
|
|
katPetAscii.style.transform = 'none';
|
|
}
|
|
}
|
|
|
|
// 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>
|