Spaces:
Running
Running
You need to *uniformly follow all of the design instructions and apply them across the entire design. There should not be random big pictures of polenta, and this project should now be called "GRIDLAND." You also need to implement the Draggabilly library so that the windows can be draggable.
Browse files- components/window.js +59 -0
- index.html +16 -756
- script.js +23 -0
- style.css +9 -6
components/window.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class GridlandWindow extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
.window {
|
| 7 |
+
background: white;
|
| 8 |
+
box-shadow:
|
| 9 |
+
inset -1px -1px 0 0px #0a0a0a,
|
| 10 |
+
inset 1px 1px 0 0px #dfdfdf,
|
| 11 |
+
inset -2px -2px 0 0px #808080,
|
| 12 |
+
inset 2px 2px 0 0px #ffffff;
|
| 13 |
+
position: absolute;
|
| 14 |
+
z-index: 10;
|
| 15 |
+
cursor: move;
|
| 16 |
+
width: 300px;
|
| 17 |
+
}
|
| 18 |
+
.window-title {
|
| 19 |
+
background: linear-gradient(to right, #000080, #1084d0);
|
| 20 |
+
color: white;
|
| 21 |
+
padding: 4px 8px;
|
| 22 |
+
font-weight: bold;
|
| 23 |
+
display: flex;
|
| 24 |
+
justify-content: space-between;
|
| 25 |
+
align-items: center;
|
| 26 |
+
}
|
| 27 |
+
.window-content {
|
| 28 |
+
padding: 8px;
|
| 29 |
+
}
|
| 30 |
+
.close-btn {
|
| 31 |
+
background: white;
|
| 32 |
+
color: black;
|
| 33 |
+
border: 1px solid black;
|
| 34 |
+
width: 16px;
|
| 35 |
+
height: 16px;
|
| 36 |
+
display: flex;
|
| 37 |
+
align-items: center;
|
| 38 |
+
justify-content: center;
|
| 39 |
+
cursor: pointer;
|
| 40 |
+
}
|
| 41 |
+
</style>
|
| 42 |
+
<div class="window">
|
| 43 |
+
<div class="window-title">
|
| 44 |
+
<span>${this.getAttribute('title') || 'Window'}</span>
|
| 45 |
+
<button class="close-btn">×</button>
|
| 46 |
+
</div>
|
| 47 |
+
<div class="window-content">
|
| 48 |
+
<slot></slot>
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
`;
|
| 52 |
+
|
| 53 |
+
this.shadowRoot.querySelector('.close-btn').addEventListener('click', () => {
|
| 54 |
+
this.remove();
|
| 55 |
+
});
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
customElements.define('gridland-window', GridlandWindow);
|
index.html
CHANGED
|
@@ -1,763 +1,23 @@
|
|
| 1 |
-
|
| 2 |
<!DOCTYPE html>
|
| 3 |
<html lang="en">
|
| 4 |
<head>
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
<link rel="stylesheet" href="style.css">
|
| 10 |
-
<script src="https://cdn.tailwindcss.com"></script>
|
| 11 |
-
<style>
|
| 12 |
-
@font-face {
|
| 13 |
-
font-family: 'Chicago';
|
| 14 |
-
src: url('https://unpkg.com/system-font-css@2.0.1/fonts/ChicagoFLF.ttf') format('truetype');
|
| 15 |
-
}
|
| 16 |
-
@font-face {
|
| 17 |
-
font-family: 'Geneva';
|
| 18 |
-
src: url('https://unpkg.com/system-font-css@2.0.1/fonts/GenevaFLF.ttf') format('truetype');
|
| 19 |
-
}
|
| 20 |
-
|
| 21 |
-
body {
|
| 22 |
-
background-color: #008080;
|
| 23 |
-
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AkEEjIZJpQpWQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAJUlEQVQ4y2NgGAWjYBSMglEwCkbBKBgFgw0wDnQGRsEoGAWjYBSMglEAAKZQAf+J8jRZAAAAAElFTkSuQmCC');
|
| 24 |
-
font-family: 'Geneva', monospace;
|
| 25 |
-
font-size: 9pt;
|
| 26 |
-
color: #000;
|
| 27 |
-
margin: 0;
|
| 28 |
-
padding: 0;
|
| 29 |
-
height: 100vh;
|
| 30 |
-
overflow: hidden;
|
| 31 |
-
}
|
| 32 |
-
|
| 33 |
-
.window {
|
| 34 |
-
box-shadow: 4px 4px 0 #404040;
|
| 35 |
-
border: 2px solid #000;
|
| 36 |
-
background: #fff;
|
| 37 |
-
margin: 20px auto;
|
| 38 |
-
}
|
| 39 |
-
|
| 40 |
-
.title-bar {
|
| 41 |
-
background: linear-gradient(to right, #000 0%, #000 50%, #fff 50%, #fff 100%);
|
| 42 |
-
background-size: 16px 100%;
|
| 43 |
-
padding: 4px 6px;
|
| 44 |
-
border-bottom: 2px solid #000;
|
| 45 |
-
display: flex;
|
| 46 |
-
align-items: center;
|
| 47 |
-
}
|
| 48 |
-
|
| 49 |
-
.title {
|
| 50 |
-
font-family: 'Chicago', sans-serif;
|
| 51 |
-
font-size: 12pt;
|
| 52 |
-
margin: 0;
|
| 53 |
-
padding: 0 8px;
|
| 54 |
-
flex-grow: 1;
|
| 55 |
-
text-align: center;
|
| 56 |
-
color: #fff;
|
| 57 |
-
mix-blend-mode: difference;
|
| 58 |
-
white-space: nowrap;
|
| 59 |
-
overflow: hidden;
|
| 60 |
-
text-overflow: ellipsis;
|
| 61 |
-
}
|
| 62 |
-
|
| 63 |
-
.separator {
|
| 64 |
-
height: 2px;
|
| 65 |
-
background: #000;
|
| 66 |
-
}
|
| 67 |
-
|
| 68 |
-
.window-pane {
|
| 69 |
-
background: #fff;
|
| 70 |
-
padding: 12px;
|
| 71 |
-
}
|
| 72 |
-
|
| 73 |
-
.btn {
|
| 74 |
-
font-family: 'Geneva', monospace;
|
| 75 |
-
font-size: 9pt;
|
| 76 |
-
padding: 4px 12px;
|
| 77 |
-
border: 2px solid #000;
|
| 78 |
-
background: #fff;
|
| 79 |
-
cursor: pointer;
|
| 80 |
-
outline: none;
|
| 81 |
-
}
|
| 82 |
-
|
| 83 |
-
.btn:active, .btn:focus {
|
| 84 |
-
background: #000;
|
| 85 |
-
color: #fff;
|
| 86 |
-
}
|
| 87 |
-
|
| 88 |
-
.btn:disabled {
|
| 89 |
-
color: #808080;
|
| 90 |
-
background: #fff;
|
| 91 |
-
cursor: not-allowed;
|
| 92 |
-
border-style: dotted;
|
| 93 |
-
}
|
| 94 |
-
|
| 95 |
-
input[type="text"] {
|
| 96 |
-
font-family: 'Geneva', monospace;
|
| 97 |
-
font-size: 9pt;
|
| 98 |
-
padding: 4px;
|
| 99 |
-
border: 2px inset #c0c0c0;
|
| 100 |
-
outline: none;
|
| 101 |
-
}
|
| 102 |
-
|
| 103 |
-
input[type="text"]:focus {
|
| 104 |
-
border: 2px dotted #000;
|
| 105 |
-
}
|
| 106 |
-
|
| 107 |
-
.menu-bar {
|
| 108 |
-
background: #fff;
|
| 109 |
-
border-bottom: 2px solid #000;
|
| 110 |
-
padding: 4px 8px;
|
| 111 |
-
display: flex;
|
| 112 |
-
}
|
| 113 |
-
|
| 114 |
-
.menu-bar ul {
|
| 115 |
-
list-style: none;
|
| 116 |
-
margin: 0;
|
| 117 |
-
padding: 0;
|
| 118 |
-
display: flex;
|
| 119 |
-
}
|
| 120 |
-
|
| 121 |
-
.menu-bar li {
|
| 122 |
-
position: relative;
|
| 123 |
-
padding: 2px 12px;
|
| 124 |
-
cursor: default;
|
| 125 |
-
}
|
| 126 |
-
|
| 127 |
-
.menu-bar li:hover {
|
| 128 |
-
background: #000;
|
| 129 |
-
color: #fff;
|
| 130 |
-
}
|
| 131 |
-
|
| 132 |
-
.dropdown {
|
| 133 |
-
display: none;
|
| 134 |
-
position: absolute;
|
| 135 |
-
top: 100%;
|
| 136 |
-
left: 0;
|
| 137 |
-
background: #fff;
|
| 138 |
-
border: 2px solid #000;
|
| 139 |
-
min-width: 160px;
|
| 140 |
-
z-index: 100;
|
| 141 |
-
padding: 4px 0;
|
| 142 |
-
}
|
| 143 |
-
|
| 144 |
-
.menu-bar li:hover .dropdown {
|
| 145 |
-
display: block;
|
| 146 |
-
}
|
| 147 |
-
|
| 148 |
-
.dropdown li {
|
| 149 |
-
padding: 4px 12px;
|
| 150 |
-
white-space: nowrap;
|
| 151 |
-
}
|
| 152 |
-
|
| 153 |
-
.dropdown li.divider {
|
| 154 |
-
border-top: 1px solid #000;
|
| 155 |
-
margin-top: 4px;
|
| 156 |
-
padding-top: 8px;
|
| 157 |
-
}
|
| 158 |
-
|
| 159 |
-
.dropdown a {
|
| 160 |
-
color: inherit;
|
| 161 |
-
text-decoration: none;
|
| 162 |
-
display: block;
|
| 163 |
-
}
|
| 164 |
-
|
| 165 |
-
#results-output {
|
| 166 |
-
font-family: 'Geneva', monospace;
|
| 167 |
-
font-size: 9pt;
|
| 168 |
-
line-height: 1.4;
|
| 169 |
-
white-space: pre-wrap;
|
| 170 |
-
}
|
| 171 |
-
|
| 172 |
-
#results-output::-webkit-scrollbar {
|
| 173 |
-
width: 16px;
|
| 174 |
-
height: 16px;
|
| 175 |
-
}
|
| 176 |
-
|
| 177 |
-
#results-output::-webkit-scrollbar-track {
|
| 178 |
-
background: #fff;
|
| 179 |
-
border-left: 2px solid #000;
|
| 180 |
-
}
|
| 181 |
-
|
| 182 |
-
#results-output::-webkit-scrollbar-thumb {
|
| 183 |
-
background: #000;
|
| 184 |
-
border: 2px solid #fff;
|
| 185 |
-
}
|
| 186 |
-
|
| 187 |
-
#results-output::-webkit-scrollbar-button {
|
| 188 |
-
background: #fff;
|
| 189 |
-
border: 2px solid #000;
|
| 190 |
-
height: 16px;
|
| 191 |
-
width: 16px;
|
| 192 |
-
}
|
| 193 |
-
|
| 194 |
-
.field-row {
|
| 195 |
-
margin-bottom: 12px;
|
| 196 |
-
display: flex;
|
| 197 |
-
align-items: center;
|
| 198 |
-
}
|
| 199 |
-
|
| 200 |
-
.field-row label {
|
| 201 |
-
width: 120px;
|
| 202 |
-
display: inline-block;
|
| 203 |
-
}
|
| 204 |
-
|
| 205 |
-
#progress-bar {
|
| 206 |
-
height: 16px;
|
| 207 |
-
border: 1px solid #000;
|
| 208 |
-
background: #fff;
|
| 209 |
-
position: relative;
|
| 210 |
-
width: 280px;
|
| 211 |
-
}
|
| 212 |
-
|
| 213 |
-
#progress-bar-fill {
|
| 214 |
-
height: 100%;
|
| 215 |
-
background: #000;
|
| 216 |
-
width: 0%;
|
| 217 |
-
transition: width 0.3s;
|
| 218 |
-
}
|
| 219 |
-
|
| 220 |
-
#progress-text {
|
| 221 |
-
position: absolute;
|
| 222 |
-
top: 1px;
|
| 223 |
-
left: 4px;
|
| 224 |
-
font-family: 'Geneva', monospace;
|
| 225 |
-
font-size: 8pt;
|
| 226 |
-
color: #fff;
|
| 227 |
-
mix-blend-mode: difference;
|
| 228 |
-
}
|
| 229 |
-
|
| 230 |
-
.stream-window {
|
| 231 |
-
position: fixed;
|
| 232 |
-
top: 50px;
|
| 233 |
-
left: 50px;
|
| 234 |
-
z-index: 999;
|
| 235 |
-
}
|
| 236 |
-
|
| 237 |
-
@media (max-width: 768px) {
|
| 238 |
-
.window {
|
| 239 |
-
width: 95% !important;
|
| 240 |
-
margin: 10px auto;
|
| 241 |
-
}
|
| 242 |
-
|
| 243 |
-
.field-row {
|
| 244 |
-
flex-direction: column;
|
| 245 |
-
align-items: flex-start;
|
| 246 |
-
}
|
| 247 |
-
|
| 248 |
-
.field-row label {
|
| 249 |
-
margin-bottom: 4px;
|
| 250 |
-
width: auto;
|
| 251 |
-
}
|
| 252 |
-
|
| 253 |
-
#target-ip {
|
| 254 |
-
width: 100% !important;
|
| 255 |
-
}
|
| 256 |
-
|
| 257 |
-
#progress-bar {
|
| 258 |
-
width: 100% !important;
|
| 259 |
-
}
|
| 260 |
-
}
|
| 261 |
-
</style>
|
| 262 |
</head>
|
| 263 |
<body>
|
| 264 |
-
<
|
| 265 |
-
<
|
| 266 |
-
<
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
<li role="menuitem"><a href="#shodan">Search Shodan</a></li>
|
| 277 |
-
<li role="menuitem"><a href="#censys">Search Censys</a></li>
|
| 278 |
-
<li role="menuitem"><a href="#google">Google Dork</a></li>
|
| 279 |
-
</ul>
|
| 280 |
-
</li>
|
| 281 |
-
<li role="menuitem" tabindex="0">❓ Help
|
| 282 |
-
<ul role="menu" class="dropdown">
|
| 283 |
-
<li role="menuitem"><a href="#about">About CamXploit</a></li>
|
| 284 |
-
<li role="menuitem"><a href="#usage">Usage Guide</a></li>
|
| 285 |
-
</ul>
|
| 286 |
-
</li>
|
| 287 |
-
</ul>
|
| 288 |
-
</div>
|
| 289 |
-
|
| 290 |
-
<div class="window" style="width: 680px; margin: 20px auto;">
|
| 291 |
-
<div class="title-bar">
|
| 292 |
-
<button aria-label="Close" class="close"></button>
|
| 293 |
-
<h1 class="title">🎥 CamXploit Scanner v2.0.1</h1>
|
| 294 |
-
<button aria-label="Resize" class="resize"></button>
|
| 295 |
-
</div>
|
| 296 |
-
<div class="separator"></div>
|
| 297 |
-
<div class="window-pane">
|
| 298 |
-
<div class="field-row">
|
| 299 |
-
<label for="target-ip">🎯 Target IP:</label>
|
| 300 |
-
<input type="text" id="target-ip" placeholder="192.168.1.1 or 192.168.1.0/24" style="width: 280px;">
|
| 301 |
-
</div>
|
| 302 |
-
|
| 303 |
-
<div class="field-row" style="margin-top: 16px;">
|
| 304 |
-
<button class="btn" id="start-scan" style="margin-right: 8px;">🔍 Start Scan</button>
|
| 305 |
-
<button class="btn" id="stop-scan" disabled>⏹️ Stop Scan</button>
|
| 306 |
-
<button class="btn" id="clear-results" style="margin-left: 8px;">🗑️ Clear</button>
|
| 307 |
-
</div>
|
| 308 |
-
|
| 309 |
-
<div class="field-row" style="margin-top: 16px;">
|
| 310 |
-
<label>📊 Status:</label>
|
| 311 |
-
<span id="scan-status" style="font-weight: bold;">Ready to scan</span>
|
| 312 |
-
</div>
|
| 313 |
-
|
| 314 |
-
<div class="field-row" style="margin-top: 8px;">
|
| 315 |
-
<label>⏱️ Progress:</label>
|
| 316 |
-
<div id="progress-bar">
|
| 317 |
-
<div id="progress-bar-fill"></div>
|
| 318 |
-
<span id="progress-text">0%</span>
|
| 319 |
-
</div>
|
| 320 |
-
</div>
|
| 321 |
-
|
| 322 |
-
<div class="field-row" style="margin-top: 16px; flex-direction: column;">
|
| 323 |
-
<label style="font-weight: bold; margin-bottom: 4px;">📋 Scan Results:</label>
|
| 324 |
-
<div id="results-output" style="width: 100%; height: 320px; border: 2px inset #c0c0c0; background: #fff; overflow-y: auto; padding: 8px;" tabindex="0">
|
| 325 |
-
<!-- Scan results will appear here -->
|
| 326 |
-
</div>
|
| 327 |
-
</div>
|
| 328 |
-
</div>
|
| 329 |
-
</div>
|
| 330 |
-
|
| 331 |
-
<script>
|
| 332 |
-
// Global state management
|
| 333 |
-
let scanState = {
|
| 334 |
-
isScanning: false,
|
| 335 |
-
currentScan: null,
|
| 336 |
-
results: [],
|
| 337 |
-
totalFindings: 0
|
| 338 |
-
};
|
| 339 |
-
|
| 340 |
-
// DOM element references
|
| 341 |
-
const targetIpInput = document.getElementById('target-ip');
|
| 342 |
-
const startScanBtn = document.getElementById('start-scan');
|
| 343 |
-
const stopScanBtn = document.getElementById('stop-scan');
|
| 344 |
-
const clearResultsBtn = document.getElementById('clear-results');
|
| 345 |
-
const scanStatus = document.getElementById('scan-status');
|
| 346 |
-
const progressBarFill = document.getElementById('progress-bar-fill');
|
| 347 |
-
const progressText = document.getElementById('progress-text');
|
| 348 |
-
const resultsOutput = document.getElementById('results-output');
|
| 349 |
-
|
| 350 |
-
// System 6 style alert dialogs
|
| 351 |
-
function showSystemAlert(title, message, callback) {
|
| 352 |
-
const alertWindow = document.createElement('div');
|
| 353 |
-
alertWindow.className = 'window';
|
| 354 |
-
alertWindow.style.cssText = `
|
| 355 |
-
position: fixed;
|
| 356 |
-
top: 50%;
|
| 357 |
-
left: 50%;
|
| 358 |
-
transform: translate(-50%, -50%);
|
| 359 |
-
width: 400px;
|
| 360 |
-
z-index: 1000;
|
| 361 |
-
`;
|
| 362 |
-
|
| 363 |
-
alertWindow.innerHTML = `
|
| 364 |
-
<div class="title-bar">
|
| 365 |
-
<h1 class="title">⚠️ ${title}</h1>
|
| 366 |
-
</div>
|
| 367 |
-
<div class="separator"></div>
|
| 368 |
-
<div class="window-pane" style="padding: 16px; text-align: center;">
|
| 369 |
-
<p style="margin: 16px 0; font-family: Geneva; font-size: 9pt;">${message}</p>
|
| 370 |
-
<button class="btn" onclick="this.closest('.window').remove(); ${callback ? 'callback()' : ''}"
|
| 371 |
-
style="margin-top: 16px;">OK</button>
|
| 372 |
-
</div>
|
| 373 |
-
`;
|
| 374 |
-
|
| 375 |
-
document.body.appendChild(alertWindow);
|
| 376 |
-
}
|
| 377 |
-
|
| 378 |
-
// IP validation with System 6 feedback
|
| 379 |
-
function validateIP(ip) {
|
| 380 |
-
const ipPattern = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\/[0-9]{1,2})?$/;
|
| 381 |
-
return ipPattern.test(ip.trim());
|
| 382 |
-
}
|
| 383 |
-
|
| 384 |
-
// Format scan results with System 6 styling
|
| 385 |
-
function formatScanResult(event) {
|
| 386 |
-
const timestamp = new Date().toLocaleTimeString();
|
| 387 |
-
let formattedResult = '';
|
| 388 |
-
|
| 389 |
-
switch(event.type) {
|
| 390 |
-
case 'status':
|
| 391 |
-
formattedResult = `[${timestamp}] 📊 ${event.message}\n`;
|
| 392 |
-
break;
|
| 393 |
-
case 'finding':
|
| 394 |
-
scanState.totalFindings++;
|
| 395 |
-
if (event.url) {
|
| 396 |
-
formattedResult = `[${timestamp}] ✅ FOUND: ${event.message}\n`;
|
| 397 |
-
formattedResult += ` 🔗 URL: ${event.url}\n`;
|
| 398 |
-
|
| 399 |
-
// Create clickable link for streams
|
| 400 |
-
if (event.url.includes('rtsp://') || event.url.includes('http')) {
|
| 401 |
-
const linkElement = document.createElement('a');
|
| 402 |
-
linkElement.href = '#';
|
| 403 |
-
linkElement.textContent = '👁️ View Stream';
|
| 404 |
-
linkElement.style.cssText = 'color: #000; text-decoration: underline; cursor: pointer;';
|
| 405 |
-
linkElement.onclick = () => openStreamViewer(event.url);
|
| 406 |
-
resultsOutput.appendChild(linkElement);
|
| 407 |
-
resultsOutput.appendChild(document.createTextNode('\n'));
|
| 408 |
-
}
|
| 409 |
-
} else {
|
| 410 |
-
formattedResult = `[${timestamp}] 🔍 ${event.message}\n`;
|
| 411 |
-
}
|
| 412 |
-
break;
|
| 413 |
-
case 'error':
|
| 414 |
-
formattedResult = `[${timestamp}] ❌ ERROR: ${event.message}\n`;
|
| 415 |
-
break;
|
| 416 |
-
case 'complete':
|
| 417 |
-
formattedResult = `[${timestamp}] ✅ SCAN COMPLETE\n`;
|
| 418 |
-
formattedResult += ` 📊 Total findings: ${scanState.totalFindings}\n`;
|
| 419 |
-
break;
|
| 420 |
-
}
|
| 421 |
-
|
| 422 |
-
return formattedResult;
|
| 423 |
-
}
|
| 424 |
-
|
| 425 |
-
// Stream viewer in System 6 style window
|
| 426 |
-
function openStreamViewer(streamUrl) {
|
| 427 |
-
const streamWindow = document.createElement('div');
|
| 428 |
-
streamWindow.className = 'window stream-window';
|
| 429 |
-
streamWindow.style.width = '640px';
|
| 430 |
-
|
| 431 |
-
const b64Url = btoa(streamUrl).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
| 432 |
-
|
| 433 |
-
streamWindow.innerHTML = `
|
| 434 |
-
<div class="title-bar">
|
| 435 |
-
<button aria-label="Close" class="close" onclick="this.closest('.window').remove()"></button>
|
| 436 |
-
<h1 class="title">📺 Stream Viewer</h1>
|
| 437 |
-
</div>
|
| 438 |
-
<div class="separator"></div>
|
| 439 |
-
<div class="window-pane" style="padding: 8px; height: calc(100% - 40px);">
|
| 440 |
-
<video controls autoplay style="width: 100%; height: calc(100% - 60px); background: #000;">
|
| 441 |
-
<source src="/stream/${b64Url}" type="application/vnd.apple.mpegurl">
|
| 442 |
-
Your browser does not support HLS streams.
|
| 443 |
-
</video>
|
| 444 |
-
<div style="margin-top: 8px; font-family: Geneva; font-size: 8pt;">
|
| 445 |
-
🔗 ${streamUrl}
|
| 446 |
-
</div>
|
| 447 |
-
</div>
|
| 448 |
-
`;
|
| 449 |
-
|
| 450 |
-
document.body.appendChild(streamWindow);
|
| 451 |
-
}
|
| 452 |
-
|
| 453 |
-
// Main scan function with backend integration
|
| 454 |
-
async function startScan() {
|
| 455 |
-
const targetIP = targetIpInput.value.trim();
|
| 456 |
-
|
| 457 |
-
if (!targetIP) {
|
| 458 |
-
showSystemAlert('Input Error', 'Please enter a target IP address.');
|
| 459 |
-
return;
|
| 460 |
-
}
|
| 461 |
-
|
| 462 |
-
if (!validateIP(targetIP)) {
|
| 463 |
-
showSystemAlert('Input Error', 'Please enter a valid IP address or CIDR range.');
|
| 464 |
-
return;
|
| 465 |
-
}
|
| 466 |
-
|
| 467 |
-
// Update UI state
|
| 468 |
-
scanState.isScanning = true;
|
| 469 |
-
scanState.totalFindings = 0;
|
| 470 |
-
startScanBtn.disabled = true;
|
| 471 |
-
stopScanBtn.disabled = false;
|
| 472 |
-
targetIpInput.disabled = true;
|
| 473 |
-
|
| 474 |
-
// Clear previous results
|
| 475 |
-
resultsOutput.textContent = '';
|
| 476 |
-
updateProgress(0, 'Initializing scan...');
|
| 477 |
-
|
| 478 |
-
try {
|
| 479 |
-
const response = await fetch('/scan', {
|
| 480 |
-
method: 'POST',
|
| 481 |
-
headers: { 'Content-Type': 'application/json' },
|
| 482 |
-
body: JSON.stringify({ ip: targetIP })
|
| 483 |
-
});
|
| 484 |
-
|
| 485 |
-
if (!response.ok) {
|
| 486 |
-
const errorData = await response.json().catch(() => ({}));
|
| 487 |
-
throw new Error(errorData.error || 'Server error');
|
| 488 |
-
}
|
| 489 |
-
|
| 490 |
-
// Process streaming response
|
| 491 |
-
const reader = response.body.getReader();
|
| 492 |
-
const decoder = new TextDecoder();
|
| 493 |
-
let buffer = '';
|
| 494 |
-
let progress = 0;
|
| 495 |
-
|
| 496 |
-
while (scanState.isScanning) {
|
| 497 |
-
const { value, done } = await reader.read();
|
| 498 |
-
if (done) break;
|
| 499 |
-
|
| 500 |
-
buffer += decoder.decode(value, { stream: true });
|
| 501 |
-
let lines = buffer.split('\n');
|
| 502 |
-
buffer = lines.pop();
|
| 503 |
-
|
| 504 |
-
for (const line of lines) {
|
| 505 |
-
if (!line.trim()) continue;
|
| 506 |
-
|
| 507 |
-
let event;
|
| 508 |
-
try {
|
| 509 |
-
event = JSON.parse(line);
|
| 510 |
-
} catch (e) {
|
| 511 |
-
continue;
|
| 512 |
-
}
|
| 513 |
-
|
| 514 |
-
// Update progress
|
| 515 |
-
progress = Math.min(progress + 2, 95);
|
| 516 |
-
updateProgress(progress, event.message || 'Scanning...');
|
| 517 |
-
|
| 518 |
-
// Format and display result
|
| 519 |
-
const formattedResult = formatScanResult(event);
|
| 520 |
-
resultsOutput.textContent += formattedResult;
|
| 521 |
-
resultsOutput.scrollTop = resultsOutput.scrollHeight;
|
| 522 |
-
|
| 523 |
-
// Handle scan completion
|
| 524 |
-
if (event.type === 'end' || event.complete) {
|
| 525 |
-
finalizeScan();
|
| 526 |
-
break;
|
| 527 |
-
}
|
| 528 |
-
}
|
| 529 |
-
}
|
| 530 |
-
|
| 531 |
-
} catch (error) {
|
| 532 |
-
showSystemAlert('Connection Error', `Failed to connect to scanner: ${error.message}`);
|
| 533 |
-
finalizeScan();
|
| 534 |
-
}
|
| 535 |
-
}
|
| 536 |
-
|
| 537 |
-
// Progress update with System 6 styling
|
| 538 |
-
function updateProgress(percentage, message) {
|
| 539 |
-
progressBarFill.style.width = `${percentage}%`;
|
| 540 |
-
progressText.textContent = `${Math.round(percentage)}%`;
|
| 541 |
-
scanStatus.textContent = message;
|
| 542 |
-
}
|
| 543 |
-
|
| 544 |
-
// Finalize scan state
|
| 545 |
-
function finalizeScan() {
|
| 546 |
-
scanState.isScanning = false;
|
| 547 |
-
startScanBtn.disabled = false;
|
| 548 |
-
stopScanBtn.disabled = true;
|
| 549 |
-
targetIpInput.disabled = false;
|
| 550 |
-
updateProgress(100, `Scan completed - ${scanState.totalFindings} findings`);
|
| 551 |
-
}
|
| 552 |
-
|
| 553 |
-
// Stop scan function
|
| 554 |
-
function stopScan() {
|
| 555 |
-
if (scanState.isScanning) {
|
| 556 |
-
scanState.isScanning = false;
|
| 557 |
-
showSystemAlert('Scan Stopped', 'Scan has been manually stopped.');
|
| 558 |
-
finalizeScan();
|
| 559 |
-
}
|
| 560 |
-
}
|
| 561 |
-
|
| 562 |
-
// Clear results function
|
| 563 |
-
function clearResults() {
|
| 564 |
-
resultsOutput.textContent = '';
|
| 565 |
-
scanState.results = [];
|
| 566 |
-
scanState.totalFindings = 0;
|
| 567 |
-
updateProgress(0, 'Ready to scan');
|
| 568 |
-
}
|
| 569 |
-
|
| 570 |
-
// Menu functions
|
| 571 |
-
function openSearchUrl(service, ip) {
|
| 572 |
-
const urls = {
|
| 573 |
-
shodan: `https://www.shodan.io/search?query=${ip}`,
|
| 574 |
-
censys: `https://search.censys.io/hosts/${ip}`,
|
| 575 |
-
google: `https://www.google.com/search?q=site:${ip}+inurl:view/view.shtml+OR+inurl:admin.html`
|
| 576 |
-
};
|
| 577 |
-
|
| 578 |
-
if (urls[service]) {
|
| 579 |
-
window.open(urls[service], '_blank');
|
| 580 |
-
}
|
| 581 |
-
}
|
| 582 |
-
|
| 583 |
-
// Event listeners
|
| 584 |
-
startScanBtn.addEventListener('click', startScan);
|
| 585 |
-
stopScanBtn.addEventListener('click', stopScan);
|
| 586 |
-
clearResultsBtn.addEventListener('click', clearResults);
|
| 587 |
-
|
| 588 |
-
// Enter key support for IP input
|
| 589 |
-
targetIpInput.addEventListener('keypress', (e) => {
|
| 590 |
-
if (e.key === 'Enter' && !scanState.isScanning) {
|
| 591 |
-
startScan();
|
| 592 |
-
}
|
| 593 |
-
});
|
| 594 |
-
|
| 595 |
-
// Menu item handlers
|
| 596 |
-
document.querySelectorAll('[role="menuitem"] a').forEach(link => {
|
| 597 |
-
link.addEventListener('click', (e) => {
|
| 598 |
-
e.preventDefault();
|
| 599 |
-
const action = e.target.getAttribute('href').substring(1);
|
| 600 |
-
|
| 601 |
-
switch(action) {
|
| 602 |
-
case 'new':
|
| 603 |
-
clearResults();
|
| 604 |
-
targetIpInput.focus();
|
| 605 |
-
break;
|
| 606 |
-
case 'export':
|
| 607 |
-
exportResults();
|
| 608 |
-
break;
|
| 609 |
-
case 'shodan':
|
| 610 |
-
case 'censys':
|
| 611 |
-
case 'google':
|
| 612 |
-
const ip = targetIpInput.value.trim();
|
| 613 |
-
if (ip) {
|
| 614 |
-
openSearchUrl(action, ip);
|
| 615 |
-
} else {
|
| 616 |
-
showSystemAlert('No Target', 'Please enter an IP address first.');
|
| 617 |
-
}
|
| 618 |
-
break;
|
| 619 |
-
case 'about':
|
| 620 |
-
showAboutDialog();
|
| 621 |
-
break;
|
| 622 |
-
case 'usage':
|
| 623 |
-
showUsageDialog();
|
| 624 |
-
break;
|
| 625 |
-
case 'quit':
|
| 626 |
-
showSystemAlert('Quit', 'This would close the application in a real environment.');
|
| 627 |
-
break;
|
| 628 |
-
}
|
| 629 |
-
});
|
| 630 |
-
});
|
| 631 |
-
|
| 632 |
-
// Export results function
|
| 633 |
-
function exportResults() {
|
| 634 |
-
const results = resultsOutput.textContent;
|
| 635 |
-
if (!results.trim()) {
|
| 636 |
-
showSystemAlert('No Results', 'No scan results to export.');
|
| 637 |
-
return;
|
| 638 |
-
}
|
| 639 |
-
|
| 640 |
-
const blob = new Blob([results], { type: 'text/plain' });
|
| 641 |
-
const url = URL.createObjectURL(blob);
|
| 642 |
-
const a = document.createElement('a');
|
| 643 |
-
a.href = url;
|
| 644 |
-
a.download = `camxploit-scan-${new Date().toISOString().slice(0,19).replace(/:/g,'-')}.txt`;
|
| 645 |
-
a.click();
|
| 646 |
-
URL.revokeObjectURL(url);
|
| 647 |
-
}
|
| 648 |
-
|
| 649 |
-
// About dialog
|
| 650 |
-
function showAboutDialog() {
|
| 651 |
-
const aboutWindow = document.createElement('div');
|
| 652 |
-
aboutWindow.className = 'window';
|
| 653 |
-
aboutWindow.style.cssText = `
|
| 654 |
-
position: fixed;
|
| 655 |
-
top: 30%;
|
| 656 |
-
left: 50%;
|
| 657 |
-
transform: translate(-50%, -50%);
|
| 658 |
-
width: 500px;
|
| 659 |
-
z-index: 1000;
|
| 660 |
-
`;
|
| 661 |
-
|
| 662 |
-
aboutWindow.innerHTML = `
|
| 663 |
-
<div class="title-bar">
|
| 664 |
-
<button aria-label="Close" class="close" onclick="this.closest('.window').remove()"></button>
|
| 665 |
-
<h1 class="title">About CamXploit</h1>
|
| 666 |
-
</div>
|
| 667 |
-
<div class="separator"></div>
|
| 668 |
-
<div class="window-pane" style="padding: 16px;">
|
| 669 |
-
<div style="text-align: center; margin-bottom: 16px;">
|
| 670 |
-
<div style="font-size: 48px; margin-bottom: 8px;">🎥</div>
|
| 671 |
-
<div style="font-family: Chicago; font-size: 12pt; font-weight: bold;">CamXploit Scanner</div>
|
| 672 |
-
<div style="font-family: Geneva; font-size: 9pt;">Version 2.0.1</div>
|
| 673 |
-
</div>
|
| 674 |
-
<div style="font-family: Geneva; font-size: 9pt; line-height: 1.4;">
|
| 675 |
-
<p>CamXploit is a reconnaissance tool for discovering exposed CCTV cameras and analyzing their security configurations.</p>
|
| 676 |
-
<p><strong>Features:</strong></p>
|
| 677 |
-
<ul style="margin-left: 20px;">
|
| 678 |
-
<li>Comprehensive port scanning</li>
|
| 679 |
-
<li>Camera brand detection</li>
|
| 680 |
-
<li>Default credential testing</li>
|
| 681 |
-
<li>Live stream discovery</li>
|
| 682 |
-
<li>Vulnerability identification</li>
|
| 683 |
-
</ul>
|
| 684 |
-
<p><em>⚠️ For educational and authorized security testing only.</em></p>
|
| 685 |
-
</div>
|
| 686 |
-
<div style="text-align: center; margin-top: 16px;">
|
| 687 |
-
<button class="btn" onclick="this.closest('.window').remove()">OK</button>
|
| 688 |
-
</div>
|
| 689 |
-
</div>
|
| 690 |
-
`;
|
| 691 |
-
|
| 692 |
-
document.body.appendChild(aboutWindow);
|
| 693 |
-
}
|
| 694 |
-
|
| 695 |
-
// Usage dialog
|
| 696 |
-
function showUsageDialog() {
|
| 697 |
-
const usageWindow = document.createElement('div');
|
| 698 |
-
usageWindow.className = 'window';
|
| 699 |
-
usageWindow.style.cssText = `
|
| 700 |
-
position: fixed;
|
| 701 |
-
top: 20%;
|
| 702 |
-
left: 50%;
|
| 703 |
-
transform: translate(-50%, -50%);
|
| 704 |
-
width: 500px;
|
| 705 |
-
z-index: 1000;
|
| 706 |
-
`;
|
| 707 |
-
|
| 708 |
-
usageWindow.innerHTML = `
|
| 709 |
-
<div class="title-bar">
|
| 710 |
-
<button aria-label="Close" class="close" onclick="this.closest('.window').remove()"></button>
|
| 711 |
-
<h1 class="title">Usage Guide</h1>
|
| 712 |
-
</div>
|
| 713 |
-
<div class="separator"></div>
|
| 714 |
-
<div class="window-pane" style="padding: 16px;">
|
| 715 |
-
<div style="font-family: Geneva; font-size: 9pt; line-height: 1.4;">
|
| 716 |
-
<p><strong>How to use CamXploit Scanner:</strong></p>
|
| 717 |
-
<ol style="margin-left: 20px;">
|
| 718 |
-
<li>Enter a target IP address or CIDR range (e.g., 192.168.1.1 or 192.168.1.0/24)</li>
|
| 719 |
-
<li>Click "Start Scan" or press Enter</li>
|
| 720 |
-
<li>Monitor progress in the status area</li>
|
| 721 |
-
<li>View discovered cameras in the results panel</li>
|
| 722 |
-
<li>Click "View Stream" links to open live feeds</li>
|
| 723 |
-
<li>Use "Export Results" to save your findings</li>
|
| 724 |
-
</ol>
|
| 725 |
-
<p><strong>Keyboard Shortcuts:</strong></p>
|
| 726 |
-
<ul style="margin-left: 20px;">
|
| 727 |
-
<li>Enter: Start scan</li>
|
| 728 |
-
<li>Escape: Stop scan</li>
|
| 729 |
-
<li>Cmd/Ctrl+E: Export results</li>
|
| 730 |
-
</ul>
|
| 731 |
-
<p><em>Note: Always ensure you have permission to scan target networks.</em></p>
|
| 732 |
-
</div>
|
| 733 |
-
<div style="text-align: center; margin-top: 16px;">
|
| 734 |
-
<button class="btn" onclick="this.closest('.window').remove()">OK</button>
|
| 735 |
-
</div>
|
| 736 |
-
</div>
|
| 737 |
-
`;
|
| 738 |
-
|
| 739 |
-
document.body.appendChild(usageWindow);
|
| 740 |
-
}
|
| 741 |
-
|
| 742 |
-
// Initialize application
|
| 743 |
-
document.addEventListener('DOMContentLoaded', () => {
|
| 744 |
-
updateProgress(0, 'Ready to scan');
|
| 745 |
-
targetIpInput.focus();
|
| 746 |
-
|
| 747 |
-
// Add keyboard shortcuts
|
| 748 |
-
document.addEventListener('keydown', (e) => {
|
| 749 |
-
if (e.key === 'Escape' && scanState.isScanning) {
|
| 750 |
-
stopScan();
|
| 751 |
-
}
|
| 752 |
-
|
| 753 |
-
if ((e.ctrlKey || e.metaKey) && e.key === 'e') {
|
| 754 |
-
exportResults();
|
| 755 |
-
e.preventDefault();
|
| 756 |
-
}
|
| 757 |
-
});
|
| 758 |
-
});
|
| 759 |
-
</script>
|
| 760 |
-
<script src="components/navbar.js"></script>
|
| 761 |
-
<script src="components/footer.js"></script>
|
| 762 |
</body>
|
| 763 |
</html>
|
|
|
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>GRIDLAND</title>
|
| 7 |
+
<link rel="stylesheet" href="style.css">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
</head>
|
| 9 |
<body>
|
| 10 |
+
<div class="container">
|
| 11 |
+
<gridland-window title="Welcome">
|
| 12 |
+
<p>Welcome to GRIDLAND. Drag windows around!</p>
|
| 13 |
+
</gridland-window>
|
| 14 |
+
|
| 15 |
+
<gridland-window title="Info" style="top: 100px; left: 100px;">
|
| 16 |
+
<p>This is a draggable window demo.</p>
|
| 17 |
+
</gridland-window>
|
| 18 |
+
</div>
|
| 19 |
+
|
| 20 |
+
<script src="components/window.js"></script>
|
| 21 |
+
<script src="script.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
</body>
|
| 23 |
</html>
|
script.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Import Draggabilly from CDN
|
| 2 |
+
(function() {
|
| 3 |
+
function loadScript(url, callback) {
|
| 4 |
+
const script = document.createElement('script');
|
| 5 |
+
script.src = url;
|
| 6 |
+
script.onload = callback;
|
| 7 |
+
document.body.appendChild(script);
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 11 |
+
// Load Draggabilly
|
| 12 |
+
loadScript('https://unpkg.com/draggabilly@3.0.0/dist/draggabilly.pkgd.min.js', () => {
|
| 13 |
+
// Make all windows draggable
|
| 14 |
+
const windows = document.querySelectorAll('.window');
|
| 15 |
+
windows.forEach(window => {
|
| 16 |
+
new Draggabilly(window, {
|
| 17 |
+
containment: '.container',
|
| 18 |
+
handle: '.window-title'
|
| 19 |
+
});
|
| 20 |
+
});
|
| 21 |
+
});
|
| 22 |
+
});
|
| 23 |
+
})();
|
style.css
CHANGED
|
@@ -5,9 +5,9 @@ body {
|
|
| 5 |
margin: 0;
|
| 6 |
padding: 0;
|
| 7 |
color: #000;
|
| 8 |
-
background-color: #
|
| 9 |
-
background-image:
|
| 10 |
-
|
| 11 |
background-attachment: fixed;
|
| 12 |
}
|
| 13 |
/* Layout */
|
|
@@ -16,18 +16,21 @@ body {
|
|
| 16 |
max-width: 800px;
|
| 17 |
margin: 2rem auto;
|
| 18 |
padding: 2px;
|
| 19 |
-
|
|
|
|
| 20 |
box-shadow:
|
| 21 |
inset -1px -1px 0 0px #0a0a0a,
|
| 22 |
inset 1px 1px 0 0px #dfdfdf,
|
| 23 |
inset -2px -2px 0 0px #808080,
|
| 24 |
inset 2px 2px 0 0px #ffffff;
|
| 25 |
}
|
| 26 |
-
|
| 27 |
.window {
|
| 28 |
margin: 1rem 0;
|
| 29 |
background: white;
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
| 31 |
inset -1px -1px 0 0px #0a0a0a,
|
| 32 |
inset 1px 1px 0 0px #dfdfdf,
|
| 33 |
inset -2px -2px 0 0px #808080,
|
|
|
|
| 5 |
margin: 0;
|
| 6 |
padding: 0;
|
| 7 |
color: #000;
|
| 8 |
+
background-color: #008080;
|
| 9 |
+
background-image: repeating-linear-gradient(45deg, rgba(255,255,255,0.1) 0px, rgba(255,255,255,0.1) 10px, transparent 10px, transparent 20px);
|
| 10 |
+
background-size: cover;
|
| 11 |
background-attachment: fixed;
|
| 12 |
}
|
| 13 |
/* Layout */
|
|
|
|
| 16 |
max-width: 800px;
|
| 17 |
margin: 2rem auto;
|
| 18 |
padding: 2px;
|
| 19 |
+
position: relative;
|
| 20 |
+
background: white;
|
| 21 |
box-shadow:
|
| 22 |
inset -1px -1px 0 0px #0a0a0a,
|
| 23 |
inset 1px 1px 0 0px #dfdfdf,
|
| 24 |
inset -2px -2px 0 0px #808080,
|
| 25 |
inset 2px 2px 0 0px #ffffff;
|
| 26 |
}
|
|
|
|
| 27 |
.window {
|
| 28 |
margin: 1rem 0;
|
| 29 |
background: white;
|
| 30 |
+
position: absolute;
|
| 31 |
+
cursor: move;
|
| 32 |
+
z-index: 10;
|
| 33 |
+
box-shadow:
|
| 34 |
inset -1px -1px 0 0px #0a0a0a,
|
| 35 |
inset 1px 1px 0 0px #dfdfdf,
|
| 36 |
inset -2px -2px 0 0px #808080,
|