2069 lines
71 KiB
HTML
2069 lines
71 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: JetBrains Mono Nerd 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=Fira+Code:wght@300;400;500;600;700&family=JetBrains+Mono:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&family=Space+Mono:ital,wght@0,400;0,700;1,400&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: 'JetBrains Mono', 'Fira Code', 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: 'JetBrains Mono', 'Fira Code', 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;
|
|
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;
|
|
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: 'JetBrains Mono', 'Fira Code', monospace !important;
|
|
font-size: 11px;
|
|
line-height: 1.48;
|
|
padding: 12px 14px;
|
|
color: #e5e5e5;
|
|
background: transparent !important;
|
|
overflow-y: auto;
|
|
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: 'JetBrains Mono', monospace;
|
|
font-size: 52px;
|
|
font-weight: 700;
|
|
letter-spacing: -0.04em;
|
|
color: #ffffff;
|
|
text-shadow: 0 0 24px rgba(255, 255, 255, 0.8), 0 0 45px rgba(255, 255, 255, 0.3);
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.boot-sub {
|
|
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;
|
|
}
|
|
|
|
/* ── Responsive Window Bounds ────────────────────────────────────────────── */
|
|
@media (max-width: 900px) {
|
|
.window {
|
|
position: static !important;
|
|
width: 100% !important;
|
|
height: auto !important;
|
|
margin-bottom: 12px;
|
|
}
|
|
.desktop {
|
|
overflow-y: auto;
|
|
padding: 12px;
|
|
height: calc(100vh - var(--taskbar-h));
|
|
}
|
|
.taskbar-windows {
|
|
display: none;
|
|
}
|
|
.music-track {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Minimal Boot Screen (Click to Start Audio & Video) -->
|
|
<div id="loadingScreen">
|
|
<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">
|
|
|
|
<!-- 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">spectrum bars</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="restoreAllWindows()" title="katOS Launcher">
|
|
<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">Baiaa081 - Noite Isolada</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: snug fit without dead space, skills lowered below websites
|
|
setWinPos('win-about', 30, 32, 380, 215);
|
|
setWinPos('win-skills', 315, 50, 385, 248);
|
|
setWinPos('win-websites', 48, 440, 375, 248);
|
|
setWinPos('win-links', 325, 470, 350, 285);
|
|
setWinPos('win-term', 25, 895, Math.min(910, W - 935), 465);
|
|
setWinPos('win-viz', 520, 870, Math.min(935, W - 910), 285);
|
|
} else if (W >= 1150) {
|
|
// Standard laptop / desktop: non-parallel coordinates & no overlap
|
|
setWinPos('win-about', 22, 22, 350, 215);
|
|
setWinPos('win-skills', 305, 40, 360, 248);
|
|
setWinPos('win-websites', 40, 400, 350, 248);
|
|
setWinPos('win-links', 315, 425, 335, 285);
|
|
setWinPos('win-term', 20, 835, Math.max(480, W - 865), 445);
|
|
setWinPos('win-viz', 495, 810, Math.max(500, W - 840), 275);
|
|
} else if (W >= 800) {
|
|
// Tablet / compact
|
|
setWinPos('win-about', 18, 16, 340, 215);
|
|
setWinPos('win-skills', 295, 30, 345, 248);
|
|
setWinPos('win-websites', 35, 385, 335, 248);
|
|
setWinPos('win-links', 305, 410, 325, 285);
|
|
setWinPos('win-term', 620, 22, W - 48, 435);
|
|
setWinPos('win-viz', 1080, 38, W - 68, 270);
|
|
}
|
|
|
|
restoreAllWindows();
|
|
bringToFront(document.getElementById('win-term'));
|
|
}
|
|
|
|
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';
|
|
}
|
|
|
|
// ── 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;
|
|
|
|
function onMouseMove(moveEvent) {
|
|
let newX = moveEvent.clientX - shiftX;
|
|
let newY = moveEvent.clientY - shiftY;
|
|
|
|
const maxX = window.innerWidth - 60;
|
|
const maxY = window.innerHeight - 70;
|
|
if (newX < 0) newX = 0;
|
|
if (newX > maxX) newX = maxX;
|
|
if (newY < 0) newY = 0;
|
|
if (newY > maxY) newY = maxY;
|
|
|
|
win.style.left = newX + 'px';
|
|
win.style.top = newY + 'px';
|
|
}
|
|
|
|
function onMouseUp() {
|
|
document.removeEventListener('mousemove', onMouseMove);
|
|
document.removeEventListener('mouseup', onMouseUp);
|
|
}
|
|
|
|
document.addEventListener('mousemove', onMouseMove);
|
|
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 fastfetchLines = [
|
|
` ▄`,
|
|
` ▟█▙`,
|
|
` ▟███▙ <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> JetBrains 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> Baiaa081 - Noite Isolada`,
|
|
` ▟██████████ █████▆▅▄▃▂ ${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`,
|
|
` ▟██████▀▀▀ ▀▀██████▙`,
|
|
` ▟███▀▘ ▝▀███▙`,
|
|
` ▟▛▀ ▀▜▙`,
|
|
];
|
|
fastfetchArtEl.innerHTML = fastfetchLines.map(l => `<div class="term-row">${l}</div>`).join('');
|
|
|
|
// ── 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) {
|
|
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', 'ls', 'projects', 'skills', 'music', 'matrix', 'whoami', 'date', 'reboot'];
|
|
const match = available.find(c => c.startsWith(cur));
|
|
if (match) termInput.value = match;
|
|
}
|
|
});
|
|
|
|
function execCommand(cmdLine) {
|
|
const parts = cmdLine.split(' ');
|
|
const cmd = parts[0].toLowerCase();
|
|
const arg = parts.slice(1).join(' ');
|
|
|
|
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">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 (horizon, neural, 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';
|
|
out.textContent = '';
|
|
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';
|
|
} 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':
|
|
case 'cat':
|
|
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 === 'cyber' || arg === 'horizon') {
|
|
setBgMode('cyber-horizon');
|
|
out.textContent = 'background set to: Cyber 3D Horizon';
|
|
} else if (arg === 'neural' || arg === 'stars' || arg === 'mesh') {
|
|
setBgMode('neural-mesh');
|
|
out.textContent = 'background set to: Neural Star-Mesh';
|
|
} 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 [horizon | neural | 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 = ['spectrum bars', 'mirror pulse'];
|
|
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);
|
|
|
|
function resizeVizCanvas() {
|
|
const rect = vizCanvas.getBoundingClientRect();
|
|
const dpr = window.devicePixelRatio || 1;
|
|
vizCanvas.width = rect.width * dpr;
|
|
vizCanvas.height = rect.height * dpr;
|
|
}
|
|
|
|
function startVisualizer() {
|
|
if (!analyser) return;
|
|
vizIdleMsg.style.opacity = '0';
|
|
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();
|
|
vizCtx.moveTo(0, H * 0.92);
|
|
vizCtx.lineTo(W, H * 0.92);
|
|
vizCtx.stroke();
|
|
|
|
vizIdleMsg.style.opacity = '1';
|
|
}
|
|
|
|
function drawVisualizerFrame() {
|
|
vizAnimId = requestAnimationFrame(drawVisualizerFrame);
|
|
resizeVizCanvas();
|
|
|
|
const W = vizCanvas.width;
|
|
const H = vizCanvas.height;
|
|
if (W === 0 || H === 0) return;
|
|
|
|
vizCtx.clearRect(0, 0, W, H);
|
|
|
|
const bufLen = analyser.frequencyBinCount;
|
|
const freqData = new Uint8Array(bufLen);
|
|
analyser.getByteFrequencyData(freqData);
|
|
|
|
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 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);
|
|
}
|
|
|
|
const bgModes = [
|
|
{ id: 'cyber-horizon', name: 'Cyber 3D Horizon' },
|
|
{ id: 'neural-mesh', name: 'Neural Star-Mesh' },
|
|
{ id: 'matrix-rain', name: 'KatOS Digital Rain' },
|
|
{ id: 'video-ambient', name: 'Ambient Video' }
|
|
];
|
|
let currentBgModeIdx = 0;
|
|
const savedBg = localStorage.getItem('katos_bg_mode');
|
|
if (savedBg) {
|
|
const idx = bgModes.findIndex(m => m.id === savedBg);
|
|
if (idx !== -1) currentBgModeIdx = idx;
|
|
}
|
|
|
|
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];
|
|
localStorage.setItem('katos_bg_mode', mode.id);
|
|
if (mode.id === 'video-ambient') {
|
|
bgVideo.style.display = 'block';
|
|
setTimeout(() => { bgVideo.style.opacity = '0.55'; }, 20);
|
|
} else {
|
|
bgVideo.style.opacity = '0';
|
|
setTimeout(() => {
|
|
if (bgModes[currentBgModeIdx].id !== 'video-ambient') bgVideo.style.display = 'none';
|
|
}, 500);
|
|
}
|
|
if (!silent) showToast(`[ BG: ${mode.name} ]`);
|
|
}
|
|
|
|
// Mouse Tracking with Smooth Lerp
|
|
const bgMouse = {
|
|
x: window.innerWidth / 2,
|
|
y: window.innerHeight / 2,
|
|
targetX: window.innerWidth / 2,
|
|
targetY: window.innerHeight / 2,
|
|
active: false
|
|
};
|
|
|
|
window.addEventListener('mousemove', e => {
|
|
bgMouse.targetX = e.clientX;
|
|
bgMouse.targetY = e.clientY;
|
|
bgMouse.active = true;
|
|
});
|
|
|
|
window.addEventListener('mouseleave', () => {
|
|
bgMouse.active = false;
|
|
});
|
|
|
|
// Star / Constellation Node Pool
|
|
const STAR_COUNT = 85;
|
|
const stars = [];
|
|
function initStars() {
|
|
stars.length = 0;
|
|
const W = bgCanvas.width;
|
|
const H = bgCanvas.height;
|
|
for (let i = 0; i < STAR_COUNT; i++) {
|
|
stars.push({
|
|
x: Math.random() * W,
|
|
y: Math.random() * H,
|
|
vx: (Math.random() - 0.5) * 0.35,
|
|
vy: (Math.random() - 0.5) * 0.3,
|
|
size: 0.8 + Math.random() * 1.8,
|
|
alpha: 0.2 + Math.random() * 0.65,
|
|
pulse: Math.random() * Math.PI * 2,
|
|
pulseSpeed: 0.02 + Math.random() * 0.03
|
|
});
|
|
}
|
|
}
|
|
|
|
// Matrix Rain Columns
|
|
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));
|
|
}
|
|
|
|
function resizeBgCanvas() {
|
|
bgCanvas.width = window.innerWidth;
|
|
bgCanvas.height = window.innerHeight;
|
|
initStars();
|
|
initMatrixDrops();
|
|
}
|
|
window.addEventListener('resize', resizeBgCanvas);
|
|
resizeBgCanvas();
|
|
|
|
// Audio frequency extractor helper
|
|
function getAudioBeat() {
|
|
if (!isPlaying || !analyser) return { bass: 0, mid: 0, energy: 0 };
|
|
const freqData = new Uint8Array(analyser.frequencyBinCount);
|
|
analyser.getByteFrequencyData(freqData);
|
|
let bSum = 0; for (let i = 0; i < 5; i++) bSum += freqData[i];
|
|
let mSum = 0; for (let i = 5; i < 20; i++) mSum += freqData[i];
|
|
const bass = (bSum / 5) / 255;
|
|
const mid = (mSum / 15) / 255;
|
|
return { bass, mid, energy: (bass * 0.65 + mid * 0.35) };
|
|
}
|
|
|
|
// Expanding shockwave pulse on bass hits
|
|
const shockwaves = [];
|
|
let lastBeatTime = 0;
|
|
let gridOffset = 0;
|
|
let radarAngle = 0;
|
|
|
|
function renderBgFrame(timestamp) {
|
|
requestAnimationFrame(renderBgFrame);
|
|
const W = bgCanvas.width;
|
|
const H = bgCanvas.height;
|
|
if (W === 0 || H === 0) return;
|
|
|
|
// Smooth mouse lerp
|
|
bgMouse.x += (bgMouse.targetX - bgMouse.x) * 0.08;
|
|
bgMouse.y += (bgMouse.targetY - bgMouse.y) * 0.08;
|
|
|
|
const audio = getAudioBeat();
|
|
|
|
// Trigger beat shockwave on intense bass
|
|
if (audio.bass > 0.65 && timestamp - lastBeatTime > 340) {
|
|
lastBeatTime = timestamp;
|
|
shockwaves.push({
|
|
x: W / 2,
|
|
y: H * 0.65,
|
|
radius: 8,
|
|
maxRadius: Math.max(W, H) * 0.8,
|
|
alpha: 0.55
|
|
});
|
|
}
|
|
|
|
const currentMode = bgModes[currentBgModeIdx].id;
|
|
|
|
if (currentMode === 'video-ambient') {
|
|
bgCtx.clearRect(0, 0, W, H);
|
|
return;
|
|
}
|
|
|
|
if (currentMode === 'matrix-rain') {
|
|
// High-definition monochrome KatOS 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;
|
|
}
|
|
|
|
// Default modes: 'cyber-horizon' or 'neural-mesh'
|
|
bgCtx.fillStyle = 'rgba(0, 0, 0, 0.22)';
|
|
bgCtx.fillRect(0, 0, W, H);
|
|
|
|
const isHorizon = (currentMode === 'cyber-horizon');
|
|
const horizonY = isHorizon ? H * 0.65 : H + 200;
|
|
|
|
// 1. Draw Starfield & Constellation Network
|
|
const activeStars = isHorizon ? stars.filter(s => s.y < horizonY + 25) : stars;
|
|
|
|
for (let i = 0; i < activeStars.length; i++) {
|
|
const s = activeStars[i];
|
|
s.x += s.vx * (1 + audio.bass * 0.5);
|
|
s.y += s.vy * (1 + audio.bass * 0.5);
|
|
s.pulse += s.pulseSpeed;
|
|
|
|
if (s.x < 0) s.x = W;
|
|
if (s.x > W) s.x = 0;
|
|
if (s.y < 0) s.y = isHorizon ? horizonY : H;
|
|
if (s.y > (isHorizon ? horizonY + 15 : H)) s.y = 0;
|
|
|
|
// Mouse gravity pull (subtle warp)
|
|
const dx = bgMouse.x - s.x;
|
|
const dy = bgMouse.y - s.y;
|
|
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
if (bgMouse.active && dist < 140) {
|
|
const force = (1 - dist / 140) * 0.015;
|
|
s.x += dx * force;
|
|
s.y += dy * force;
|
|
|
|
// Glowing laser thread connecting cursor to node
|
|
bgCtx.strokeStyle = `rgba(255, 255, 255, ${(1 - dist / 140) * 0.32})`;
|
|
bgCtx.lineWidth = 1;
|
|
bgCtx.beginPath();
|
|
bgCtx.moveTo(bgMouse.x, bgMouse.y);
|
|
bgCtx.lineTo(s.x, s.y);
|
|
bgCtx.stroke();
|
|
}
|
|
|
|
// Connections between neighboring stars
|
|
for (let j = i + 1; j < activeStars.length; j++) {
|
|
const s2 = activeStars[j];
|
|
const cdx = s.x - s2.x;
|
|
const cdy = s.y - s2.y;
|
|
const cDist = Math.sqrt(cdx * cdx + cdy * cdy);
|
|
if (cDist < 85) {
|
|
const lineAlpha = (1 - cDist / 85) * 0.16 * (1 + audio.mid * 0.8);
|
|
bgCtx.strokeStyle = `rgba(255, 255, 255, ${lineAlpha})`;
|
|
bgCtx.lineWidth = 0.8;
|
|
bgCtx.beginPath();
|
|
bgCtx.moveTo(s.x, s.y);
|
|
bgCtx.lineTo(s2.x, s2.y);
|
|
bgCtx.stroke();
|
|
}
|
|
}
|
|
|
|
// Draw star node
|
|
const glowBoost = audio.bass * 0.65;
|
|
const currentAlpha = Math.min(1, s.alpha * (0.8 + 0.3 * Math.sin(s.pulse)) + glowBoost);
|
|
bgCtx.fillStyle = `rgba(255, 255, 255, ${currentAlpha})`;
|
|
bgCtx.beginPath();
|
|
bgCtx.arc(s.x, s.y, s.size * (1 + glowBoost * 0.5), 0, Math.PI * 2);
|
|
bgCtx.fill();
|
|
}
|
|
|
|
// 2. Render expanding shockwaves
|
|
for (let i = shockwaves.length - 1; i >= 0; i--) {
|
|
const sw = shockwaves[i];
|
|
sw.radius += 5.5;
|
|
sw.alpha *= 0.96;
|
|
if (sw.radius > sw.maxRadius || sw.alpha < 0.01) {
|
|
shockwaves.splice(i, 1);
|
|
continue;
|
|
}
|
|
bgCtx.strokeStyle = `rgba(255, 255, 255, ${sw.alpha * 0.28})`;
|
|
bgCtx.lineWidth = 1.5;
|
|
bgCtx.beginPath();
|
|
bgCtx.ellipse(sw.x, sw.y, sw.radius, sw.radius * 0.35, 0, 0, Math.PI * 2);
|
|
bgCtx.stroke();
|
|
}
|
|
|
|
// 3. Render 3D Perspective Cyber-Grid (if in cyber-horizon mode)
|
|
if (isHorizon) {
|
|
// Horizon glow aura
|
|
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 line
|
|
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 perspective 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);
|
|
|
|
// Audio-reactive terrain ripple across the line
|
|
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();
|
|
}
|
|
|
|
// Center Horizon Cyber Kat Mark
|
|
bgCtx.font = '10px "JetBrains Mono", monospace';
|
|
bgCtx.fillStyle = `rgba(255, 255, 255, ${0.45 + audio.bass * 0.4})`;
|
|
bgCtx.textAlign = 'center';
|
|
bgCtx.fillText('[ · ᆺ · katOS ]', centerX, horizonY - 10);
|
|
}
|
|
|
|
// 4. Tactical Radar (Bottom-Left Corner)
|
|
const rX = 54;
|
|
const rY = H - 56;
|
|
const rRad = 32;
|
|
radarAngle = (radarAngle + 0.02) % (Math.PI * 2);
|
|
|
|
// Radar rings
|
|
bgCtx.strokeStyle = 'rgba(255, 255, 255, 0.08)';
|
|
bgCtx.lineWidth = 1;
|
|
bgCtx.beginPath(); bgCtx.arc(rX, rY, rRad * 0.4, 0, Math.PI * 2); bgCtx.stroke();
|
|
bgCtx.beginPath(); bgCtx.arc(rX, rY, rRad * 0.75, 0, Math.PI * 2); bgCtx.stroke();
|
|
bgCtx.beginPath(); bgCtx.arc(rX, rY, rRad, 0, Math.PI * 2); bgCtx.stroke();
|
|
|
|
// Radar crosshairs
|
|
bgCtx.beginPath();
|
|
bgCtx.moveTo(rX - rRad, rY); bgCtx.lineTo(rX + rRad, rY);
|
|
bgCtx.moveTo(rX, rY - rRad); bgCtx.lineTo(rX, rY + rRad);
|
|
bgCtx.stroke();
|
|
|
|
// Radar rotating sweep line
|
|
const sweepX = rX + Math.cos(radarAngle) * rRad;
|
|
const sweepY = rY + Math.sin(radarAngle) * rRad;
|
|
bgCtx.strokeStyle = 'rgba(255, 255, 255, 0.45)';
|
|
bgCtx.beginPath();
|
|
bgCtx.moveTo(rX, rY);
|
|
bgCtx.lineTo(sweepX, sweepY);
|
|
bgCtx.stroke();
|
|
|
|
// Radar text
|
|
bgCtx.font = '8px "JetBrains Mono", monospace';
|
|
bgCtx.fillStyle = 'rgba(255, 255, 255, 0.35)';
|
|
bgCtx.textAlign = 'left';
|
|
bgCtx.fillText('SWEEP 360°', rX - 22, rY + rRad + 12);
|
|
|
|
// 5. Top-Right Tactical Telemetry Readout
|
|
bgCtx.font = '9px "JetBrains Mono", monospace';
|
|
bgCtx.fillStyle = 'rgba(255, 255, 255, 0.28)';
|
|
bgCtx.textAlign = 'right';
|
|
bgCtx.fillText('katOS // ARCH_LINUX_WAYLAND', W - 18, 22);
|
|
bgCtx.fillText(isPlaying ? 'AUDIO FLUX: ACTIVE [●]' : 'AUDIO FLUX: IDLE [○]', W - 18, 34);
|
|
}
|
|
|
|
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 ──────────────────────────────────────────────
|
|
const bgVideo = document.getElementById('bgVideo');
|
|
async function tryLoadBgVideo() {
|
|
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 ─────────────────────────────────────────────────
|
|
const loadingScreen = document.getElementById('loadingScreen');
|
|
loadingScreen.addEventListener('click', () => {
|
|
loadingScreen.classList.add('hidden');
|
|
tryLoadBgVideo();
|
|
setTimeout(() => {
|
|
playMusic();
|
|
focusTermInput();
|
|
}, 300);
|
|
});
|
|
|
|
// Keyboard Shortcut (Space to play/pause music)
|
|
window.addEventListener('keydown', e => {
|
|
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
if (e.code === 'Space') {
|
|
e.preventDefault();
|
|
togglePlay();
|
|
}
|
|
});
|
|
|
|
// Initialize System
|
|
initWindows();
|
|
</script>
|
|
</body>
|
|
</html>
|