2025-06-05 13:44:25 +02:00
|
|
|
|
<img?php $statePath='/home/surillya/.thos_state.json' ; $state=file_exists($statePath) ?
|
|
|
|
|
json_decode(file_get_contents($statePath), true) : null; if (!file_exists($statePath)) { header('Location: oobe.php');
|
|
|
|
|
exit(); } ?>
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html lang="en">
|
|
|
|
|
|
|
|
|
|
<head>
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
<title>SurillyaOS</title>
|
|
|
|
|
<style>
|
|
|
|
|
:root {
|
|
|
|
|
--accent: #4CAF50;
|
|
|
|
|
--window-bg: rgba(28, 28, 28, 0.6);
|
|
|
|
|
--window-border: rgba(255, 255, 255, 0.1);
|
|
|
|
|
--window-blur: blur(20px);
|
|
|
|
|
--header-bg: rgba(30, 30, 30, 0.6);
|
|
|
|
|
--header-color: white;
|
|
|
|
|
--header-hover: var(--accent);
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
.theme-glassy-dark {
|
|
|
|
|
--window-bg: rgba(28, 28, 28, 0.6);
|
|
|
|
|
--window-border: rgba(255, 255, 255, 0.1);
|
|
|
|
|
--window-blur: blur(20px);
|
|
|
|
|
--header-bg: rgba(30, 30, 30, 0.6);
|
|
|
|
|
--header-color: white;
|
|
|
|
|
--font-color: white;
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
.theme-frosted-blue {
|
|
|
|
|
--window-bg: rgba(0, 40, 80, 0.4);
|
|
|
|
|
--window-border: rgba(173, 216, 230, 0.2);
|
|
|
|
|
--window-blur: blur(25px);
|
|
|
|
|
--header-bg: rgba(10, 20, 50, 0.4);
|
|
|
|
|
--header-color: #e3f8ff;
|
|
|
|
|
--font-color: #e3f8ff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.theme-dreamy-pink {
|
|
|
|
|
--window-bg: rgba(80, 30, 60, 0.5);
|
|
|
|
|
--window-border: rgba(255, 182, 193, 0.3);
|
|
|
|
|
--window-blur: blur(16px);
|
|
|
|
|
--header-bg: rgba(100, 40, 80, 0.5);
|
|
|
|
|
--header-color: #ffddee;
|
|
|
|
|
--font-color: #ffe9f4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.theme-xp {
|
|
|
|
|
--window-bg: #ece9d8;
|
|
|
|
|
--window-border: #3a6ea5;
|
|
|
|
|
--window-blur: none;
|
|
|
|
|
--header-bg: linear-gradient(to bottom, #0a246a, #1c4dbd);
|
|
|
|
|
--header-color: white;
|
|
|
|
|
--font-color: black;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
body {
|
|
|
|
|
margin: 0;
|
|
|
|
|
font-family: 'Inter', sans-serif;
|
|
|
|
|
background: #121212;
|
|
|
|
|
color: white;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#app-bar {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
width: 200px;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
background: var(--window-bg);
|
|
|
|
|
backdrop-filter: var(--window-blur);
|
|
|
|
|
-webkit-backdrop-filter: var(--window-blur);
|
|
|
|
|
border: 1px solid var(--window-border);
|
|
|
|
|
padding: 10px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
z-index: 9999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app-btn {
|
|
|
|
|
display: block;
|
|
|
|
|
margin: 10px 0;
|
|
|
|
|
padding: 10px;
|
|
|
|
|
background: #292929;
|
|
|
|
|
border: none;
|
|
|
|
|
color: white;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
transition: background 0.3s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.app-btn:hover {
|
|
|
|
|
background: var(--accent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.window {
|
|
|
|
|
position: absolute;
|
|
|
|
|
width: 600px;
|
|
|
|
|
height: 400px;
|
|
|
|
|
background: var(--window-bg);
|
|
|
|
|
backdrop-filter: var(--window-blur);
|
|
|
|
|
-webkit-backdrop-filter: var(--window-blur);
|
|
|
|
|
border: 1px solid var(--window-border);
|
|
|
|
|
border-radius: 12px;
|
|
|
|
|
resize: both;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
z-index: 1;
|
|
|
|
|
transition: box-shadow 0.2s ease, backdrop-filter 0.3s ease;
|
|
|
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.window.active {
|
|
|
|
|
box-shadow: 0 0 20px var(--accent), 0 8px 24px rgba(0, 0, 0, 0.3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.window-header {
|
|
|
|
|
background: var(--header-bg);
|
|
|
|
|
color: var(--header-color);
|
|
|
|
|
padding: 6px 12px;
|
|
|
|
|
cursor: grab;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
backdrop-filter: var(--window-blur);
|
|
|
|
|
-webkit-backdrop-filter: var(--window-blur);
|
|
|
|
|
border-bottom: 1px solid var(--window-border);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.window-header:active {
|
|
|
|
|
cursor: grabbing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.window-header button {
|
|
|
|
|
background: transparent;
|
|
|
|
|
border: none;
|
|
|
|
|
color: inherit;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
font-size: 1.1em;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
user-select: none;
|
|
|
|
|
padding: 0 6px;
|
|
|
|
|
transition: color 0.2s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.window-header button:hover {
|
|
|
|
|
color: var(--header-hover);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.window-content {
|
|
|
|
|
background: var(--window-bg);
|
|
|
|
|
backdrop-filter: var(--window-blur);
|
|
|
|
|
-webkit-backdrop-filter: var(--window-blur);
|
|
|
|
|
border: 1px solid var(--window-border);
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: calc(100% - 32px);
|
|
|
|
|
border: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sparkle {
|
|
|
|
|
position: absolute;
|
|
|
|
|
width: 6px;
|
|
|
|
|
height: 6px;
|
|
|
|
|
background: radial-gradient(circle at center, var(--accent) 0%, transparent 70%);
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
filter: drop-shadow(0 0 6px var(--accent));
|
|
|
|
|
animation: sparkle 1.2s infinite ease-in-out;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
@keyframes sparkle {
|
|
|
|
|
|
|
|
|
|
0%,
|
|
|
|
|
100% {
|
|
|
|
|
opacity: 0;
|
|
|
|
|
transform: scale(0.5) rotate(0deg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
50% {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
transform: scale(1) rotate(45deg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#screensaver {
|
|
|
|
|
display: none;
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
width: 100vw;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
border: none;
|
|
|
|
|
z-index: 9999;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#drag-overlay {
|
|
|
|
|
position: fixed;
|
|
|
|
|
top: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
width: 100vw;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
z-index: 99999;
|
|
|
|
|
display: none;
|
|
|
|
|
cursor: grabbing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#fullscreen-toggle {
|
|
|
|
|
border: none;
|
|
|
|
|
background: transparent;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
backdrop-filter: blur(2px);
|
|
|
|
|
-webkit-backdrop-filter: blur(2px);
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#fullscreen-toggle:hover {
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.grayscale-bg {
|
|
|
|
|
filter: grayscale(100%) brightness(0.9);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.notification {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 0.25rem;
|
|
|
|
|
padding: 1rem 1.25rem;
|
|
|
|
|
border-radius: 1rem;
|
|
|
|
|
border: 2px solid transparent;
|
|
|
|
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
|
|
|
|
color: white;
|
|
|
|
|
min-width: 260px;
|
|
|
|
|
max-width: 340px;
|
|
|
|
|
pointer-events: auto;
|
|
|
|
|
animation: fadeInUp 0.3s ease-out;
|
|
|
|
|
position: relative;
|
|
|
|
|
transition: transform 0.2s ease, opacity 0.2s ease;
|
|
|
|
|
background: var(--window-bg);
|
|
|
|
|
backdrop-filter: var(--window-blur);
|
|
|
|
|
-webkit-backdrop-filter: var(--window-blur);
|
|
|
|
|
color: var(--accent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.notification-dismiss {
|
|
|
|
|
background: transparent;
|
|
|
|
|
border: none;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.notification.success {
|
|
|
|
|
border-color: #4caf50;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.notification.error {
|
|
|
|
|
border-color: #f44336;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.notification.info {
|
|
|
|
|
border-color: var(--accent, #00bcd4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes slideIn {
|
|
|
|
|
from {
|
|
|
|
|
transform: translateX(100%) scale(0.95);
|
|
|
|
|
opacity: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
to {
|
|
|
|
|
transform: translateX(0) scale(1);
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes fadeOut {
|
|
|
|
|
from {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
transform: translateX(0) scale(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
to {
|
|
|
|
|
opacity: 0;
|
|
|
|
|
transform: translateX(50%) scale(0.95);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
<script src="tailwind.es"></script>
|
|
|
|
|
</head>
|
|
|
|
|
|
|
|
|
|
<body class="bg-black">
|
|
|
|
|
<div id="appWrapper" class="transition-all duration-300">
|
|
|
|
|
<div id="bg-image" class="fixed inset-0 z-[-10] bg-cover bg-center transition-all duration-300"></div>
|
|
|
|
|
<div id="app-bar" class="bg-[#1e1e1e] fixed top-0 left-0 w-52 h-screen p-2.5 z-50">
|
|
|
|
|
<div class="flex flex-col h-full">
|
|
|
|
|
<div class="relative mb-4">
|
|
|
|
|
<button id="powerButton" class="group relative transition duration-300">
|
|
|
|
|
<img src="THOS.png" alt="Power" class="w-12 h-12 transition-all duration-300 ease-in-out
|
|
|
|
|
group-hover:brightness-125
|
|
|
|
|
group-hover:scale-110
|
|
|
|
|
group-hover:drop-shadow-[0_0_10px_rgba(255,200,255,0.6)]
|
|
|
|
|
group-hover:saturate-150
|
|
|
|
|
cursor-pointer select-none">
|
2025-06-05 08:52:31 +02:00
|
|
|
|
</button>
|
2025-06-05 13:44:25 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="grid grid-cols-3 gap-3 p-2 flex flex-col justify-between">
|
|
|
|
|
<button onclick="openApp('explorer.php', 'Explorer')" title="File Explorer"
|
|
|
|
|
class="p-2 rounded-lg bg-gray-800 hover:bg-gray-700 transition-all">
|
|
|
|
|
<svg class="w-6 h-6 text-blue-300 mx-auto" fill="none" stroke="currentColor" stroke-width="2"
|
|
|
|
|
viewBox="0 0 24 24">
|
|
|
|
|
<path d="M3 7l6-3 6 3 6-3v12l-6 3-6-3-6 3V7z" />
|
|
|
|
|
<path d="M9 4v12" />
|
|
|
|
|
<path d="M15 7v12" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<button onclick="openApp('tasks.php', 'Task Manager')" title="Task Manager"
|
|
|
|
|
class="p-2 rounded-lg bg-gray-800 hover:bg-gray-700 transition-all">
|
|
|
|
|
<svg class="w-6 h-6 text-red-300 mx-auto" fill="none" stroke="currentColor" stroke-width="2"
|
|
|
|
|
viewBox="0 0 24 24">
|
|
|
|
|
<rect x="3" y="3" width="7" height="7" rx="1" />
|
|
|
|
|
<rect x="14" y="3" width="7" height="7" rx="1" />
|
|
|
|
|
<rect x="3" y="14" width="7" height="7" rx="1" />
|
|
|
|
|
<rect x="14" y="14" width="7" height="7" rx="1" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<button onclick="openApp('https://surillya.com/thos/store', 'THOS Store')" title="THOS Store"
|
|
|
|
|
class="p-2 rounded-lg bg-gray-800 hover:bg-gray-700 transition-all">
|
|
|
|
|
<svg class="w-6 h-6 text-pink-300 mx-auto" fill="none" stroke="currentColor" stroke-width="2"
|
|
|
|
|
viewBox="0 0 24 24">
|
|
|
|
|
<path d="M4 9h16l-1.68 10.08a2 2 0 0 1-2 1.92H7.68a2 2 0 0 1-2-1.92L4 9Z" />
|
|
|
|
|
<path d="M8 11V8a4 4 0 0 1 8 0v3" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<button onclick="openApp('https://surillya.com/thos/search/thossearch.html', 'Internet', true)"
|
|
|
|
|
title="Internet" class="p-2 rounded-lg bg-gray-800 hover:bg-gray-700 transition-all">
|
|
|
|
|
<svg class="w-6 h-6 text-green-300 mx-auto" fill="none" stroke="currentColor" stroke-width="2"
|
|
|
|
|
viewBox="0 0 24 24">
|
|
|
|
|
<circle cx="12" cy="12" r="10" />
|
|
|
|
|
<path d="M2 12h20" />
|
|
|
|
|
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<button onclick="openApp('settings.php', 'Settings')" title="Settings"
|
|
|
|
|
class="p-2 rounded-lg bg-gray-800 hover:bg-gray-700 transition-all">
|
|
|
|
|
<svg class="w-6 h-6 text-yellow-300 mx-auto" fill="none" stroke="currentColor" stroke-width="2"
|
|
|
|
|
viewBox="0 0 24 24">
|
|
|
|
|
<path d="M12 15a3 3 0 100-6 3 3 0 000 6z" />
|
|
|
|
|
<path
|
|
|
|
|
d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1
|
|
|
|
|
1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0
|
|
|
|
|
010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65
|
|
|
|
|
1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0
|
|
|
|
|
012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65
|
|
|
|
|
0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="flex-grow"></div>
|
|
|
|
|
|
|
|
|
|
<div id="trdp"
|
|
|
|
|
class="grid grid-cols-2 gap-2 mt-4 p-2 bg-[#252525] rounded-lg overflow-y-auto p-2 justify-center items-center">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div id="version-display-container" class="w-full flex justify-center items-center mt-4">
|
|
|
|
|
<div id="version-display" class="flex items-center gap-2 text-sm text-gray-400 select-none">
|
|
|
|
|
<button id="fullscreen-toggle" title="Toggle fullscreen"
|
|
|
|
|
class="text-white hover:text-[var(--accent)] transition-colors duration-200 text-xs p-1 rounded bg-transparent hover:bg-[var(--glass-bg-light)]">
|
|
|
|
|
⛶
|
|
|
|
|
</button>
|
|
|
|
|
<span class="text-[var(--accent)]">THOS</span><span id="version-number"></span>
|
|
|
|
|
</div>
|
2025-06-05 08:52:31 +02:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
<div id="desktop" class="ml-52 h-screen relative">
|
|
|
|
|
<div id="notification-container" class="fixed top-4 right-4 z-50 flex flex-col gap-3 pointer-events-none"></div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<iframe id="screensaver" src="screensaver.html"></iframe>
|
|
|
|
|
|
|
|
|
|
<div id="drag-overlay"></div>
|
2025-06-05 08:52:31 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
<div id="powerDialog" class="fixed inset-0 z-50 hidden flex items-center justify-center">
|
|
|
|
|
<div class="bg-black/30 rounded-2xl shadow-2xl p-6 w-80 backdrop-blur-sm text-center text-white">
|
|
|
|
|
<h2 class="text-xl font-semibold mb-6 tracking-wide">Power Options</h2>
|
|
|
|
|
<div class="space-y-3">
|
|
|
|
|
<button id="shutdownBtn"
|
2025-06-09 00:56:27 +02:00
|
|
|
|
class="w-full py-2 px-4 border border-dotted rounded-xl transition-all duration-200 text-sm bg-black/40 hover:bg-black/50 backdrop-blur-sm border-red-400 text-red-300 hover:text-red-100 hover:border-red-300">Shutdown</button>
|
2025-06-05 13:44:25 +02:00
|
|
|
|
<button id="restartBtn"
|
2025-06-09 00:56:27 +02:00
|
|
|
|
class="w-full py-2 px-4 border border-dotted rounded-xl transition-all duration-200 text-sm bg-black/40 hover:bg-black/50 backdrop-blur-sm border-yellow-400 text-yellow-300 hover:text-yellow-100 hover:border-yellow-300">Restart</button>
|
2025-06-05 13:44:25 +02:00
|
|
|
|
<button id="reloadBtn"
|
2025-06-09 00:56:27 +02:00
|
|
|
|
class="w-full py-2 px-4 border border-dotted rounded-xl transition-all duration-200 text-sm bg-black/40 hover:bg-black/50 backdrop-blur-sm border-green-400 text-green-300 hover:text-green-100 hover:border-green-300">Reload</button>
|
2025-06-05 13:44:25 +02:00
|
|
|
|
<button id="cancelBtn"
|
2025-06-09 00:56:27 +02:00
|
|
|
|
class="w-full py-2 px-4 border border-dotted rounded-xl transition-all duration-200 text-sm bg-black/40 hover:bg-black/50 backdrop-blur-sm border-white/40 text-white hover:text-white hover:border-white/80">Cancel</button>
|
2025-06-05 13:44:25 +02:00
|
|
|
|
</div>
|
2025-06-05 08:52:31 +02:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
<script>
|
|
|
|
|
window.THOS = {
|
2025-06-05 14:15:25 +02:00
|
|
|
|
version: '6 Build-26'
|
2025-06-05 13:44:25 +02:00
|
|
|
|
};
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
let timeoutSeconds = 180;
|
|
|
|
|
|
|
|
|
|
const thosState = <?= json_encode($state ?? ['cookies' => '', 'localData' => '{}']) ?>;
|
|
|
|
|
|
|
|
|
|
thosState.cookies?.split('; ').forEach(cookie => document.cookie = cookie);
|
|
|
|
|
|
|
|
|
|
const localItems = JSON.parse(thosState.localData || '{}');
|
|
|
|
|
for (let key in localItems) {
|
|
|
|
|
localStorage.setItem(key, localItems[key]);
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
if (!localStorage.getItem('thos_done')) {
|
|
|
|
|
window.location.href = 'oobe.php';
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
class WindowManager {
|
|
|
|
|
constructor(container) {
|
|
|
|
|
this.container = container;
|
|
|
|
|
this.zIndexCounter = 1;
|
|
|
|
|
this.draggingWindow = null;
|
|
|
|
|
this.resizingWindow = null;
|
|
|
|
|
this.resizeDir = null;
|
|
|
|
|
this.offsetX = 0;
|
|
|
|
|
this.offsetY = 0;
|
|
|
|
|
this.startWidth = 0;
|
|
|
|
|
this.startHeight = 0;
|
|
|
|
|
this.startLeft = 0;
|
|
|
|
|
this.startTop = 0;
|
|
|
|
|
this.isMaximized = new WeakMap();
|
|
|
|
|
this.activeWindow = null;
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
this.dragOverlay = document.getElementById('drag-overlay');
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
document.addEventListener('mousemove', this.onMouseMove.bind(this));
|
|
|
|
|
document.addEventListener('mouseup', this.onMouseUp.bind(this));
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
this.makeWindowsDraggable();
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
makeWindowsDraggable() {
|
|
|
|
|
const windows = this.container.querySelectorAll('.window');
|
|
|
|
|
windows.forEach(win => {
|
|
|
|
|
const header = win.querySelector('.window-header');
|
|
|
|
|
this.makeDraggable(win, header);
|
|
|
|
|
this.makeResizable(win);
|
|
|
|
|
this.setupButtons(win);
|
|
|
|
|
this.setupFocus(win);
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
setupFocus(win) {
|
|
|
|
|
win.addEventListener('mousedown', () => {
|
|
|
|
|
this.bringToFront(win);
|
|
|
|
|
this.setActiveWindow(win);
|
|
|
|
|
});
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
win.addEventListener('focus', () => {
|
|
|
|
|
this.bringToFront(win);
|
|
|
|
|
this.setActiveWindow(win);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setActiveWindow(win) {
|
|
|
|
|
if (this.activeWindow && this.activeWindow !== win) {
|
|
|
|
|
this.activeWindow.classList.remove('active');
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
2025-06-05 13:44:25 +02:00
|
|
|
|
this.activeWindow = win;
|
|
|
|
|
win.classList.add('active');
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
makeDraggable(win, header) {
|
|
|
|
|
header.style.cursor = 'grab';
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
header.addEventListener('mousedown', (e) => {
|
|
|
|
|
if (e.target.closest('button')) return;
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
e.preventDefault();
|
|
|
|
|
this.bringToFront(win);
|
|
|
|
|
this.setActiveWindow(win);
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
if (this.isMaximized.get(win)) {
|
|
|
|
|
win.style.transition = "all 0.2s ease";
|
|
|
|
|
win.style.left = win.dataset.prevLeft;
|
|
|
|
|
win.style.top = win.dataset.prevTop;
|
|
|
|
|
win.style.width = win.dataset.prevWidth;
|
|
|
|
|
win.style.height = win.dataset.prevHeight;
|
|
|
|
|
this.isMaximized.set(win, false);
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
this.draggingWindow = win;
|
|
|
|
|
this.offsetX = e.clientX - win.offsetLeft;
|
|
|
|
|
this.offsetY = e.clientY - win.offsetTop;
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
header.style.cursor = 'grabbing';
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
if (this.dragOverlay) {
|
|
|
|
|
this.dragOverlay.style.display = 'block';
|
|
|
|
|
this.dragOverlay.style.cursor = 'grabbing';
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
document.body.style.userSelect = 'none';
|
|
|
|
|
});
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
onMouseMove(e) {
|
|
|
|
|
if (this.draggingWindow) {
|
|
|
|
|
let newLeft = e.clientX - this.offsetX;
|
|
|
|
|
let newTop = e.clientY - this.offsetY;
|
|
|
|
|
|
|
|
|
|
const containerRect = this.container.getBoundingClientRect();
|
|
|
|
|
const winRect = this.draggingWindow.getBoundingClientRect();
|
|
|
|
|
|
|
|
|
|
newLeft = Math.max(containerRect.left, Math.min(newLeft, containerRect.right - winRect.width));
|
|
|
|
|
newTop = Math.max(containerRect.top, Math.min(newTop, containerRect.bottom - winRect.height));
|
|
|
|
|
|
|
|
|
|
this.draggingWindow.style.left = newLeft + 'px';
|
|
|
|
|
this.draggingWindow.style.top = newTop + 'px';
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
onMouseUp() {
|
|
|
|
|
if (this.draggingWindow) {
|
|
|
|
|
const header = this.draggingWindow.querySelector('.window-header');
|
|
|
|
|
if (header) header.style.cursor = 'grab';
|
|
|
|
|
this.draggingWindow = null;
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
if (this.dragOverlay) {
|
|
|
|
|
this.dragOverlay.style.display = 'none';
|
|
|
|
|
this.dragOverlay.style.cursor = '';
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
document.body.style.userSelect = '';
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
if (this.resizingWindow) {
|
|
|
|
|
this.resizingWindow = null;
|
|
|
|
|
this.resizeDir = null;
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
2025-06-05 13:44:25 +02:00
|
|
|
|
|
|
|
|
|
bringToFront(win) {
|
|
|
|
|
win.style.zIndex = ++this.zIndexCounter;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toggleMaximize(win) {
|
|
|
|
|
if (!this.isMaximized.get(win)) {
|
|
|
|
|
win.dataset.prevLeft = win.style.left;
|
|
|
|
|
win.dataset.prevTop = win.style.top;
|
|
|
|
|
win.dataset.prevWidth = win.style.width;
|
|
|
|
|
win.dataset.prevHeight = win.style.height;
|
|
|
|
|
|
|
|
|
|
win.style.left = "0px";
|
|
|
|
|
win.style.top = "0px";
|
|
|
|
|
win.style.width = "100%";
|
|
|
|
|
win.style.height = "100%";
|
|
|
|
|
win.style.transition = "all 0.2s ease";
|
|
|
|
|
|
|
|
|
|
this.isMaximized.set(win, true);
|
|
|
|
|
} else {
|
|
|
|
|
win.style.transition = "all 0.2s ease";
|
|
|
|
|
win.style.left = win.dataset.prevLeft;
|
|
|
|
|
win.style.top = win.dataset.prevTop;
|
|
|
|
|
win.style.width = win.dataset.prevWidth;
|
|
|
|
|
win.style.height = win.dataset.prevHeight;
|
|
|
|
|
this.isMaximized.set(win, false);
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
2025-06-05 13:44:25 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
const tasks = [];
|
|
|
|
|
let z = 2;
|
|
|
|
|
const wm = new WindowManager(document.body);
|
|
|
|
|
const appBar = document.getElementById('trdp');
|
|
|
|
|
const contextMenu = document.createElement('div');
|
|
|
|
|
contextMenu.id = 'context-menu';
|
|
|
|
|
contextMenu.style.position = 'absolute';
|
|
|
|
|
contextMenu.style.display = 'none';
|
|
|
|
|
contextMenu.style.background = '#222';
|
|
|
|
|
contextMenu.style.border = '1px solid #444';
|
|
|
|
|
contextMenu.style.borderRadius = '6px';
|
|
|
|
|
contextMenu.style.padding = '6px';
|
|
|
|
|
contextMenu.style.zIndex = '9999';
|
|
|
|
|
document.body.appendChild(contextMenu);
|
|
|
|
|
|
|
|
|
|
document.addEventListener('click', () => contextMenu.style.display = 'none');
|
|
|
|
|
|
|
|
|
|
async function loadApps() {
|
|
|
|
|
const res = await fetch('apps.php');
|
|
|
|
|
const apps = await res.json();
|
|
|
|
|
console.log(apps);
|
|
|
|
|
|
|
|
|
|
appBar.innerHTML = '';
|
|
|
|
|
apps.forEach(app => {
|
|
|
|
|
const wrapper = document.createElement('div');
|
|
|
|
|
wrapper.classList.add('wrapper');
|
|
|
|
|
wrapper.style.alignItems = 'center';
|
|
|
|
|
wrapper.style.height = '50px';
|
|
|
|
|
wrapper.style.width = '50px';
|
|
|
|
|
wrapper.style.margin = '8px';
|
|
|
|
|
wrapper.style.transition = 'transform 0.3s ease';
|
|
|
|
|
|
|
|
|
|
const btn = document.createElement('img');
|
|
|
|
|
btn.src = app.icon;
|
|
|
|
|
btn.alt = app.name;
|
|
|
|
|
btn.title = app.name;
|
|
|
|
|
btn.style.height = '50px';
|
|
|
|
|
btn.style.width = '50px';
|
|
|
|
|
btn.style.borderRadius = '16px';
|
|
|
|
|
btn.style.border = '2px solid #ffffff22';
|
2025-06-05 08:52:31 +02:00
|
|
|
|
btn.style.boxShadow = '0 2px 10px rgba(255, 192, 203, 0.25)';
|
2025-06-05 13:44:25 +02:00
|
|
|
|
btn.style.background = 'linear-gradient(135deg, #222, #1a1a1a)';
|
|
|
|
|
btn.style.padding = '6px';
|
|
|
|
|
btn.style.transition = 'all 0.3s ease';
|
|
|
|
|
btn.style.cursor = 'pointer';
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
btn.onmouseenter = () => {
|
|
|
|
|
wrapper.style.transform = 'scale(1.08)';
|
|
|
|
|
btn.style.borderColor = 'var(--accent)';
|
|
|
|
|
btn.style.boxShadow = '0 4px 16px var(--accent), 0 0 8px var(--accent)';
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
for (let i = 0; i < 6; i++) {
|
|
|
|
|
const sparkle = document.createElement('div');
|
|
|
|
|
sparkle.classList.add('sparkle');
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
sparkle.style.top = `${Math.random() * 100}%`;
|
|
|
|
|
sparkle.style.left = `${Math.random() * 100}%`;
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
sparkle.style.animationDelay = `${Math.random() * 1.5}s`;
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
wrapper.appendChild(sparkle);
|
|
|
|
|
|
|
|
|
|
sparkle.addEventListener('animationiteration', () => sparkle.remove());
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
btn.onmouseleave = () => {
|
|
|
|
|
wrapper.style.transform = 'scale(1)';
|
|
|
|
|
btn.style.borderColor = '#ffffff22';
|
|
|
|
|
btn.style.boxShadow = '0 2px 10px rgba(255, 192, 203, 0.25)';
|
|
|
|
|
|
|
|
|
|
const sparkles = wrapper.querySelectorAll('.sparkle');
|
|
|
|
|
sparkles.forEach(s => s.remove());
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
btn.onclick = () => openApp(app.path, app.name);
|
|
|
|
|
|
|
|
|
|
wrapper.appendChild(btn);
|
|
|
|
|
|
|
|
|
|
wrapper.oncontextmenu = e => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
showContextMenu(e.pageX, e.pageY, app);
|
|
|
|
|
};
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
appBar.appendChild(wrapper);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function showContextMenu(x, y, app) {
|
|
|
|
|
contextMenu.innerHTML = `
|
2025-06-05 08:52:31 +02:00
|
|
|
|
<div onclick="uninstallApp('${app.id}')">🗑️ Uninstall</div>
|
|
|
|
|
`;
|
2025-06-05 13:44:25 +02:00
|
|
|
|
contextMenu.style.left = `${x}px`;
|
|
|
|
|
contextMenu.style.top = `${y}px`;
|
|
|
|
|
contextMenu.style.display = 'block';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function openApp(url, title, sandbox = false) {
|
|
|
|
|
const id = 'win_' + Date.now();
|
|
|
|
|
const win = document.createElement('div');
|
|
|
|
|
win.className = 'window';
|
|
|
|
|
win.style.top = Math.random() * 300 + 'px';
|
|
|
|
|
win.style.left = Math.random() * 400 + 'px';
|
|
|
|
|
win.style.zIndex = z++;
|
|
|
|
|
win.setAttribute('data-id', id);
|
|
|
|
|
|
|
|
|
|
win.innerHTML = `
|
2025-06-05 08:52:31 +02:00
|
|
|
|
<div class="window-header">
|
|
|
|
|
<span class="window-title">${title}</span>
|
|
|
|
|
<div class="window-controls">
|
|
|
|
|
<button class="max-btn" title="Maximize/Restore">🗖</button>
|
|
|
|
|
<button class="close-btn" title="Close">×</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
if (!sandbox) {
|
|
|
|
|
win.innerHTML += `<iframe src="${url}" class="window-content" allowtransparency="true"></iframe>`;
|
|
|
|
|
} else {
|
|
|
|
|
win.innerHTML += `<iframe src="${url}" sandbox="allow-scripts allow-forms allow-same-origin" class="window-content" allowtransparency="true"></iframe>`;
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
2025-06-05 13:44:25 +02:00
|
|
|
|
|
|
|
|
|
document.getElementById('desktop').appendChild(win);
|
|
|
|
|
tasks.push({ id, title, url });
|
|
|
|
|
let header = win.querySelector(".window-header");
|
|
|
|
|
wm.makeDraggable(win, header);
|
|
|
|
|
header.querySelector('.max-btn').addEventListener('click', (e) => {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
wm.toggleMaximize(win);
|
|
|
|
|
});
|
|
|
|
|
header.querySelector('.close-btn').addEventListener('click', (e) => {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
closeApp(id);
|
|
|
|
|
});
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
function closeApp(id) {
|
|
|
|
|
const win = document.querySelector(`.window[data-id='${id}']`);
|
|
|
|
|
if (win) win.remove();
|
|
|
|
|
const idx = tasks.findIndex(t => t.id === id);
|
|
|
|
|
if (idx !== -1) tasks.splice(idx, 1);
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
function focusApp(id) {
|
|
|
|
|
const win = document.querySelector(`.window[data-id='${id}']`);
|
|
|
|
|
if (win) win.style.zIndex = z++;
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
function uninstallApp(appId) {
|
|
|
|
|
if (!confirm("Are you sure you want to uninstall this app?")) return;
|
|
|
|
|
fetch(`uninstaller.php?id=${encodeURIComponent(appId)}`)
|
|
|
|
|
.then(res => res.text())
|
|
|
|
|
.then(() => loadApps());
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
loadApps();
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
window.addEventListener('message', async (event) => {
|
|
|
|
|
const { type, appId, packageFileUrl } = event.data || {};
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
if (type === 'THOS_INSTALL_APP') {
|
|
|
|
|
try {
|
|
|
|
|
const isValid = /^https:\/\/surillya\.com\//.test(packageFileUrl);
|
|
|
|
|
const fileName = packageFileUrl.split('/').pop().split('?')[0];
|
|
|
|
|
const outputPath = `/home/surillya/.temp/${fileName}`;
|
|
|
|
|
|
|
|
|
|
// I HATE UNDERSCORES!!!!
|
|
|
|
|
// underscore =/= dash
|
|
|
|
|
const url = `/install_app.php?url=${encodeURIComponent(packageFileUrl)}&output=${encodeURIComponent(outputPath)}&verified=${isValid ? 1 : 0}`;
|
|
|
|
|
const response = await fetch(url);
|
|
|
|
|
const result = await response.json();
|
|
|
|
|
|
|
|
|
|
if (!result.success) throw new Error(result.message);
|
|
|
|
|
|
|
|
|
|
const appUrl = `thp.php?q=${encodeURIComponent(result.filename)}&v=${result.verified}`;
|
|
|
|
|
window.openApp(appUrl, 'THOS Package Installer');
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.error('[THOS] App install failed:', err);
|
|
|
|
|
alert('Failed to install app.');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
window.getTaskList = () => JSON.parse(JSON.stringify(tasks));
|
|
|
|
|
window.focusApp = focusApp;
|
|
|
|
|
window.closeApp = closeApp;
|
|
|
|
|
window.openApp = openApp;
|
|
|
|
|
window.reloadApps = loadApps;
|
|
|
|
|
window.saveTHOSState = saveTHOSState;
|
|
|
|
|
|
|
|
|
|
function applySettings(settings) {
|
|
|
|
|
console.log(settings);
|
|
|
|
|
|
|
|
|
|
function applyTheme(theme) {
|
|
|
|
|
document.body.className = theme;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const savedTheme = settings.theme;
|
|
|
|
|
if (savedTheme) applyTheme(savedTheme);
|
|
|
|
|
|
|
|
|
|
document.documentElement.style.setProperty('--accent', settings.accentColor || '#ff69b4');
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
if (settings.wallpaper) {
|
|
|
|
|
document.getElementById('bg-image').style.background = `url('file.php?q=${settings.wallpaper}') center/cover`;
|
2025-06-05 08:52:31 +02:00
|
|
|
|
} else {
|
2025-06-05 13:44:25 +02:00
|
|
|
|
document.getElementById('bg-image').style.background = '';
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
2025-06-05 13:44:25 +02:00
|
|
|
|
|
|
|
|
|
const audio = document.getElementById('bgMusic');
|
|
|
|
|
if (audio) {
|
|
|
|
|
if (settings.musicEnabled === 'on') {
|
|
|
|
|
document.addEventListener("click", function () {
|
|
|
|
|
audio.src = `file.php?q=${settings.music}`;
|
|
|
|
|
audio.volume = parseFloat(settings.musicVolume || 0.3);
|
|
|
|
|
audio.play().catch(() => {
|
|
|
|
|
alert('An error occured while starting background music. Please turn background music off, or enable autoplay.');
|
|
|
|
|
});
|
|
|
|
|
}, { once: true });
|
|
|
|
|
} else {
|
|
|
|
|
audio.pause;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
timeoutSeconds = settings.screenTimeout ?? timeoutSeconds;
|
|
|
|
|
|
|
|
|
|
window.THOS = Object.assign(window.THOS || {}, {
|
|
|
|
|
getAllSettings() {
|
|
|
|
|
return settings;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
saveTHOSState();
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
const savedSettings = JSON.parse(localStorage.getItem('settings')) || {};
|
|
|
|
|
applySettings(savedSettings);
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
window.addEventListener('message', (event) => {
|
|
|
|
|
if (event.data?.type === 'applySettings') {
|
|
|
|
|
applySettings(event.data.settings);
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
function saveTHOSState() {
|
|
|
|
|
const cookies = document.cookie;
|
|
|
|
|
const localData = JSON.stringify(localStorage);
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
fetch('save_state.php', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
body: JSON.stringify({ cookies, localData })
|
|
|
|
|
});
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
setInterval(saveTHOSState, 60000);
|
|
|
|
|
window.addEventListener('beforeunload', saveTHOSState);
|
|
|
|
|
|
|
|
|
|
const powerButton = document.getElementById('powerButton');
|
|
|
|
|
const powerDialog = document.getElementById('powerDialog');
|
|
|
|
|
const cancelBtn = document.getElementById('cancelBtn');
|
|
|
|
|
const shutdownBtn = document.getElementById('shutdownBtn');
|
|
|
|
|
const restartBtn = document.getElementById('restartBtn');
|
|
|
|
|
const reloadBtn = document.getElementById('reloadBtn');
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
powerButton.addEventListener('click', () => {
|
|
|
|
|
document.getElementById("appWrapper").classList.add("grayscale-bg");
|
|
|
|
|
powerDialog.classList.remove('hidden');
|
2025-06-05 08:52:31 +02:00
|
|
|
|
});
|
2025-06-05 13:44:25 +02:00
|
|
|
|
|
|
|
|
|
cancelBtn.addEventListener('click', () => {
|
2025-06-05 08:52:31 +02:00
|
|
|
|
document.getElementById("appWrapper").classList.remove("grayscale-bg");
|
|
|
|
|
powerDialog.classList.add('hidden');
|
2025-06-05 13:44:25 +02:00
|
|
|
|
});
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
shutdownBtn.addEventListener('click', () => {
|
|
|
|
|
sendCommand('shutdown');
|
|
|
|
|
document.getElementById("appWrapper").classList.remove("grayscale-bg");
|
|
|
|
|
powerDialog.classList.add('hidden');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
restartBtn.addEventListener('click', () => {
|
|
|
|
|
sendCommand('reboot');
|
|
|
|
|
document.getElementById("appWrapper").classList.remove("grayscale-bg");
|
|
|
|
|
powerDialog.classList.add('hidden');
|
|
|
|
|
});
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
reloadBtn.addEventListener('click', () => {
|
|
|
|
|
location.reload();
|
|
|
|
|
});
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
powerDialog.addEventListener('click', (e) => {
|
|
|
|
|
if (e.target === powerDialog) {
|
|
|
|
|
document.getElementById("appWrapper").classList.remove("grayscale-bg");
|
|
|
|
|
powerDialog.classList.add('hidden');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function sendCommand(action) {
|
|
|
|
|
fetch(`server_command.php?action=${encodeURIComponent(action)}`)
|
|
|
|
|
.then(res => res.text())
|
|
|
|
|
.then(result => {
|
|
|
|
|
alert("Server response: " + result);
|
|
|
|
|
})
|
|
|
|
|
.catch(err => {
|
|
|
|
|
alert("Failed to send command: " + err);
|
|
|
|
|
});
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
document.getElementById('version-number').textContent = window.THOS.version;
|
|
|
|
|
|
|
|
|
|
document.getElementById("fullscreen-toggle").addEventListener("click", () => {
|
|
|
|
|
if (!document.fullscreenElement) {
|
|
|
|
|
document.documentElement.requestFullscreen().catch(err => {
|
|
|
|
|
console.error(`Failed to enter fullscreen: ${err.message}`);
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
document.exitFullscreen();
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
const screensaver = document.getElementById("screensaver");
|
|
|
|
|
let screensaverTimeout;
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
function showScreensaver() {
|
|
|
|
|
screensaver.style.display = "block";
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
function hideScreensaver() {
|
|
|
|
|
if (screensaver.style.display === "block") {
|
|
|
|
|
screensaver.contentWindow.postMessage({ type: 'screensaver_hidden' }, '*'); // optional
|
|
|
|
|
}
|
|
|
|
|
screensaver.style.display = "none";
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
function resetInactivityTimer() {
|
|
|
|
|
hideScreensaver();
|
|
|
|
|
clearTimeout(screensaverTimeout);
|
|
|
|
|
screensaverTimeout = setTimeout(showScreensaver, timeoutSeconds * 1000);
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
['mousemove', 'mousedown', 'keydown', 'scroll', 'touchstart', 'wheel'].forEach(event => {
|
|
|
|
|
document.addEventListener(event, resetInactivityTimer, { passive: true });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
window.addEventListener("message", event => {
|
|
|
|
|
if (event.data?.type === "user_active") {
|
|
|
|
|
resetInactivityTimer();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
resetInactivityTimer();
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
function notify(title, description = "", options = {}) {
|
|
|
|
|
const {
|
|
|
|
|
type = "info",
|
|
|
|
|
timeout = 5000,
|
|
|
|
|
icon = null,
|
|
|
|
|
id = `notif-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
|
|
|
|
|
} = options;
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
const container = document.getElementById("notification-container");
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
const notification = document.createElement("div");
|
|
|
|
|
notification.className = `notification ${type}`;
|
|
|
|
|
notification.id = id;
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
notification.innerHTML = `
|
2025-06-05 08:52:31 +02:00
|
|
|
|
${icon ? `<div class="text-xl">${icon}</div>` : ""}
|
|
|
|
|
<div class="flex-1">
|
2025-06-05 13:44:25 +02:00
|
|
|
|
<div class="notification-title font-semibold">${title}</div>
|
|
|
|
|
${description ? `<div class="notification-desc text-sm text-gray-400">${description}</div>` : ""}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
</div>
|
|
|
|
|
<button class="notification-dismiss absolute top-2 right-2 text-white/50 hover:text-white transition-colors text-sm" aria-label="Dismiss">×</button>
|
|
|
|
|
`;
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
const dismiss = () => {
|
|
|
|
|
notification.style.animation = "fadeOut 0.25s ease-out forwards";
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
if (container.contains(notification)) container.removeChild(notification);
|
|
|
|
|
}, 250);
|
|
|
|
|
};
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
notification.querySelector(".notification-dismiss").onclick = dismiss;
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
container.appendChild(notification);
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
if (timeout > 0) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
if (container.contains(notification)) dismiss();
|
|
|
|
|
}, timeout);
|
|
|
|
|
}
|
2025-06-05 08:52:31 +02:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
window.notify = notify;
|
|
|
|
|
</script>
|
|
|
|
|
</body>
|
2025-06-05 08:52:31 +02:00
|
|
|
|
|
2025-06-05 13:44:25 +02:00
|
|
|
|
</html>
|