├── .gitignore ├── sfx ├── boot.mp3 ├── blast.mp3 ├── error.mp3 └── grand.mp3 ├── background.png ├── icons ├── chat.png ├── code.png ├── file.png ├── gear.png ├── paint.png ├── snake.png ├── browser.png ├── notepad.png ├── picture.png └── calculator.png ├── browserbanner.png ├── background-ver2.png ├── package.json ├── LICENSE ├── index.html ├── server.js ├── .github └── ISSUE_TEMPLATE │ ├── feature.yaml │ └── bug.yaml ├── README.md ├── favicon.svg ├── styles.css └── script.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json -------------------------------------------------------------------------------- /sfx/boot.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/sfx/boot.mp3 -------------------------------------------------------------------------------- /background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/background.png -------------------------------------------------------------------------------- /icons/chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/icons/chat.png -------------------------------------------------------------------------------- /icons/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/icons/code.png -------------------------------------------------------------------------------- /icons/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/icons/file.png -------------------------------------------------------------------------------- /icons/gear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/icons/gear.png -------------------------------------------------------------------------------- /icons/paint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/icons/paint.png -------------------------------------------------------------------------------- /icons/snake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/icons/snake.png -------------------------------------------------------------------------------- /sfx/blast.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/sfx/blast.mp3 -------------------------------------------------------------------------------- /sfx/error.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/sfx/error.mp3 -------------------------------------------------------------------------------- /sfx/grand.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/sfx/grand.mp3 -------------------------------------------------------------------------------- /browserbanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/browserbanner.png -------------------------------------------------------------------------------- /icons/browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/icons/browser.png -------------------------------------------------------------------------------- /icons/notepad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/icons/notepad.png -------------------------------------------------------------------------------- /icons/picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/icons/picture.png -------------------------------------------------------------------------------- /background-ver2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/background-ver2.png -------------------------------------------------------------------------------- /icons/calculator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingkatty/y2kos/HEAD/icons/calculator.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "y2kos", 3 | "version": "1.0.0", 4 | "description": "A 90s themed webOS.", 5 | "main": "script.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "ws": "^8.18.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Lim Xin Ying 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Y2K WebOS 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 |
18 | Y2K WebOS v1.0 19 | 20 | Booting up... 21 | Doing stuff a 90s computer do idk... 22 | Memory Test... OK 23 | Made with ♡ by @themeowmews (Codédex) 24 | 25 | Loading system files... 26 | SYSTEM.DAT........OK 27 | COMMAND.COM.......OK 28 | 29 | Initializing GUI... 30 |
31 | 32 |
33 | 34 |
35 | 36 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const WebSocket = require('ws'); 2 | const http = require('http'); 3 | const PORT = process.env.PORT || 8080; 4 | 5 | const server = http.createServer((req, res) => { 6 | if (req.url === '/health') { 7 | res.writeHead(200, { 'Content-Type': 'application/json' }); 8 | res.end(JSON.stringify({ status: 'healthy', timestamp: new Date().toISOString() })); 9 | } else { 10 | res.writeHead(404); 11 | res.end(); 12 | } 13 | }); 14 | 15 | const wss = new WebSocket.Server({ server }); 16 | 17 | wss.on('connection', (ws) => { 18 | console.log('Client connected'); 19 | 20 | ws.on('message', (data) => { 21 | try { 22 | const messageStr = data.toString(); 23 | const message = JSON.parse(messageStr); 24 | console.log('Received:', message); 25 | 26 | wss.clients.forEach((client) => { 27 | if (client.readyState === WebSocket.OPEN) { 28 | client.send(JSON.stringify(message)); 29 | } 30 | }); 31 | } catch (error) { 32 | console.error('Error processing message:', error); 33 | } 34 | }); 35 | 36 | ws.on('close', () => { 37 | console.log('Client disconnected'); 38 | }); 39 | 40 | ws.on('error', (error) => { 41 | console.error('WebSocket error:', error); 42 | }); 43 | }); 44 | 45 | server.listen(PORT, () => { 46 | console.log(`Server started on port ${PORT}`); 47 | }); -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yaml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | title: "[FEATURE] " 4 | labels: ["enhancement"] 5 | assignees: [] 6 | 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for suggesting a feature! Please provide as much detail as possible to help us understand your idea. 12 | 13 | - type: input 14 | id: title 15 | attributes: 16 | label: Feature Title 17 | description: A brief title for the feature request 18 | placeholder: "Enter a brief title for the feature" 19 | validations: 20 | required: true 21 | 22 | - type: textarea 23 | id: description 24 | attributes: 25 | label: Description 26 | description: A detailed description of the feature request 27 | placeholder: "Describe the feature in detail" 28 | validations: 29 | required: true 30 | 31 | - type: textarea 32 | id: motivation 33 | attributes: 34 | label: Motivation 35 | description: | 36 | Explain why this feature should be added: 37 | - What problem does it solve? 38 | - How will it improve the project? 39 | placeholder: "Explain the motivation behind this feature" 40 | validations: 41 | required: true 42 | 43 | - type: textarea 44 | id: alternatives 45 | attributes: 46 | label: Alternatives 47 | description: | 48 | Describe any alternative solutions or features you've considered. 49 | placeholder: "Describe any alternative solutions or features" 50 | validations: 51 | required: false 52 | 53 | - type: textarea 54 | id: additional 55 | attributes: 56 | label: Additional Context 57 | description: Add any other context or screenshots about the feature request here 58 | placeholder: "Add any other context or information" 59 | validations: 60 | required: false -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Y2K OS 2 | Hey everyone, I'm Candy, also [@themeowmews](https://www.codedex.io/@themeowmews) on Codédex. Here, I proudly present to you my masterpiece, the Y2K OS. It's a webOS ispired by this [hackclub tutorial](https://jams.hackclub.com/batch/webOS). 3 | 4 | ![banner](https://i.imgur.com/WYOi9zK.png) 5 | 6 | ## How to Use 7 | Y2K OS will have a loading screen, and after that, it will start the whole thing. A welcome.txt will appear. It can be later accesed via the 'my files' app. To open an app, you could just click on one and it'll open. And to open files you can double click them in the file explorer. 8 | 9 | ### Easter Egg 10 | If you switch your settings of year to 2000, you'll see some errors and then you'll be greeted with fireworks and an end screen. The idea of this is from the Y2K bug, also the name of this webOS. 11 | 12 | ### Bugs you may Encounter 13 | So, the I hosted the chat thing on Render, so it may get inactive and won't work sometimes, but get patient and the server would probably work after some time. 14 | 15 | --- 16 | 17 | ## Project Desc 18 | **Y2K OS** is a cool webOS I made. Made for Codédex Holiday Hackathon, I've chosen track 2 and a more... aesthetic retro theme? Well, the colors a customizable in the settings and it is pretty nice tbh. Note that there are still some bugs though. 19 | 20 | ### Cool Facts 21 | - i took the background picture in my school field 22 | - Intentional lag 23 | - Retrooo theme 24 | 25 | ### Tech Stack 26 | ![HTML](https://img.shields.io/badge/HTML-orange?style=for-the-badge&logo=html5&logoColor=white) 27 | ![CSS](https://img.shields.io/badge/CSS-blue?style=for-the-badge&logo=css3&logoColor=white) 28 | ![JavaScript](https://img.shields.io/badge/JavaScript-yellow?style=for-the-badge&logo=javascript&logoColor=white) 29 | ![Node.js](https://img.shields.io/badge/Node.js-green?style=for-the-badge&logo=node.js&logoColor=white) 30 | 31 | ## Find a bug? 32 | [Oh no D:, a wild bug](https://github.com/codingkatty/y2kos/issues/new?template=bug.yaml) | [I have a great idea](https://github.com/codingkatty/y2kos/issues/new?template=feature.yaml) 33 | 34 | ## Acknoledgements 35 | Used AI & music/sfx are from Pixabay -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yaml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report to help us improve 3 | title: "[BUG] " 4 | labels: ["bug"] 5 | assignees: [] 6 | 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for taking the time to fill out this bug report! Please provide as much detail as possible to help us resolve the issue. 12 | 13 | - type: input 14 | id: title 15 | attributes: 16 | label: Bug Title 17 | description: A brief description of the bug 18 | placeholder: "Enter a brief title for the bug" 19 | validations: 20 | required: true 21 | 22 | - type: textarea 23 | id: description 24 | attributes: 25 | label: Description 26 | description: A detailed description of the bug 27 | placeholder: "Describe the bug in detail" 28 | validations: 29 | required: true 30 | 31 | - type: textarea 32 | id: steps 33 | attributes: 34 | label: Steps to Reproduce 35 | description: | 36 | Steps to reproduce the behavior: 37 | 1. Go to '...' 38 | 2. Click on '...' 39 | 3. Scroll down to '...' 40 | 4. See error 41 | placeholder: "List the steps to reproduce the bug" 42 | validations: 43 | required: true 44 | 45 | - type: textarea 46 | id: expected 47 | attributes: 48 | label: Expected Behavior 49 | description: Describe what you expected to happen 50 | placeholder: "Describe the expected behavior" 51 | validations: 52 | required: true 53 | 54 | - type: textarea 55 | id: actual 56 | attributes: 57 | label: Actual Behavior 58 | description: Describe what actually happened 59 | placeholder: "Describe the actual behavior" 60 | validations: 61 | required: true 62 | 63 | - type: textarea 64 | id: screenshots 65 | attributes: 66 | label: Screenshots 67 | description: | 68 | If applicable, add screenshots to help explain your problem. 69 | placeholder: "Add any screenshots here" 70 | 71 | - type: input 72 | id: environment 73 | attributes: 74 | label: Environment 75 | description: | 76 | Provide details about your environment: 77 | - OS: [e.g. Windows, Mac, Linux] 78 | - Browser: [e.g. Chrome, Safari, Firefox] 79 | - Version: [e.g. 22] 80 | placeholder: "Enter details about your environment" 81 | validations: 82 | required: true 83 | 84 | - type: textarea 85 | id: additional 86 | attributes: 87 | label: Additional Context 88 | description: Add any other context about the problem here 89 | placeholder: "Add any other context or information" -------------------------------------------------------------------------------- /favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --button-color: #c0c0c0; 3 | --taskbar-color: #d2a4a4; 4 | --window-title-color: #f2426b; 5 | --close-btn-color: #eeeb12; 6 | --min-btn-color: #f9f4c9; 7 | } 8 | 9 | body { 10 | margin: 0; 11 | padding: 0; 12 | height: 100vh; 13 | background: #c0f4f4; 14 | background-image: url(background-ver2.png); 15 | background-size: cover; 16 | background-position-y: -40px; 17 | font-family: 'Tiny5', sans-serif; 18 | font-size: 16px; 19 | overflow-x: hidden; 20 | overflow-y: hidden; 21 | } 22 | 23 | #desktop { 24 | height: calc(100vh - 40px); 25 | position: relative; 26 | } 27 | 28 | #taskbar { 29 | height: 40px; 30 | background: var(--taskbar-color); 31 | border-top: 1px solid #fff; 32 | box-shadow: 0 -1px #808080; 33 | position: fixed; 34 | bottom: 0; 35 | width: 100%; 36 | display: flex; 37 | align-items: center; 38 | } 39 | 40 | .icon { 41 | width: 75px; 42 | height: 90px; 43 | background: transparent; 44 | margin: 10px; 45 | padding: 5px; 46 | text-align: center; 47 | color: rgb(38, 32, 32); 48 | cursor: pointer; 49 | display: flex; 50 | flex-direction: column; 51 | align-items: center; 52 | gap: 5px; 53 | } 54 | 55 | .icon-image { 56 | width: 40px; 57 | height: 40px; 58 | background-size: contain; 59 | background-repeat: no-repeat; 60 | background-position: center; 61 | margin-bottom: 5px; 62 | } 63 | 64 | .icon::before { 65 | display: none; 66 | } 67 | 68 | #boot-screen { 69 | position: fixed; 70 | top: 0; 71 | left: 0; 72 | width: 100%; 73 | height: 100%; 74 | background: #000; 75 | color: #0f0; 76 | font-family: monospace; 77 | padding: 20px; 78 | z-index: 1000; 79 | white-space: pre-line; 80 | overflow: hidden; 81 | } 82 | 83 | .fade-out { 84 | animation: fadeOut 1s forwards; 85 | } 86 | 87 | @keyframes fadeOut { 88 | to { 89 | opacity: 0; 90 | visibility: hidden; 91 | } 92 | } 93 | 94 | .window { 95 | position: absolute; 96 | background: #c0c0c0; 97 | border: 2px solid; 98 | border-color: #fff #808080 #808080 #fff; 99 | min-width: 300px; 100 | min-height: 200px; 101 | box-shadow: 2px 2px #000; 102 | display: flex; 103 | flex-direction: column; 104 | resize: both; 105 | overflow: auto; 106 | } 107 | 108 | .window-title { 109 | background: var(--window-title-color); 110 | color: white; 111 | font-weight: bold; 112 | padding: 2px 4px; 113 | height: 18px; 114 | cursor: move; 115 | display: flex; 116 | justify-content: space-between; 117 | } 118 | 119 | .window-content { 120 | padding: 10px; 121 | flex-grow: 1; 122 | border: 2px solid; 123 | border-color: #808080 #fff #fff #808080; 124 | background: #fff; 125 | } 126 | 127 | .window-controls { 128 | display: flex; 129 | gap: 2px; 130 | margin-top: 2px; 131 | } 132 | 133 | .window-button { 134 | width: 14px; 135 | height: 14px; 136 | border: 1px solid; 137 | border-color: #fff #808080 #808080 #fff; 138 | border-radius: 50%; 139 | cursor: pointer; 140 | background: #c0c0c0; 141 | } 142 | 143 | .window-button:active { 144 | border-color: #808080 #fff #fff #808080; 145 | } 146 | 147 | .close-button { 148 | background: var(--close-btn-color); 149 | } 150 | 151 | .minimize-button { 152 | background: var(--min-btn-color); 153 | } 154 | 155 | .taskbar-right { 156 | margin-left: auto; 157 | margin-right: 20px; 158 | background: #dec9c9; 159 | border: 1px solid; 160 | border-color: #808080 #fff #fff #808080; 161 | padding: 2px 8px; 162 | color: #000; 163 | display: flex; 164 | align-items: center; 165 | gap: 20px; 166 | } 167 | 168 | .app-content { 169 | width: 100%; 170 | height: 100%; 171 | min-height: 200px; 172 | } 173 | 174 | .notepad-content { 175 | width: 100%; 176 | height: 100%; 177 | resize: none; 178 | border: none; 179 | padding: 5px; 180 | } 181 | 182 | input[type="color"] { 183 | appearance: none; 184 | -webkit-appearance: none; 185 | width: 50px; 186 | height: 25px; 187 | padding: 2px; 188 | background: #c0c0c0; 189 | border: 2px solid; 190 | border-color: #808080 #fff #fff #808080; 191 | cursor: pointer; 192 | } 193 | 194 | input[type="color"]::-webkit-color-swatch-wrapper { 195 | padding: 0; 196 | } 197 | 198 | input[type="color"]::-webkit-color-swatch { 199 | border: 2px solid; 200 | border-color: #fff #808080 #808080 #fff; 201 | } 202 | 203 | input[type="range"] { 204 | appearance: none; 205 | -webkit-appearance: none; 206 | width: 100%; 207 | height: 20px; 208 | background: #c0c0c0; 209 | border: 2px solid; 210 | border-color: #808080 #fff #fff #808080; 211 | cursor: pointer; 212 | } 213 | 214 | input[type="range"]::-webkit-slider-thumb { 215 | -webkit-appearance: none; 216 | width: 20px; 217 | height: 20px; 218 | background: linear-gradient(135deg, #fff, #c0c0c0); 219 | border: 2px solid; 220 | border-color: #fff #808080 #808080 #fff; 221 | cursor: pointer; 222 | } 223 | 224 | button { 225 | background: var(--button-color); 226 | border: 2px solid; 227 | border-color: #fff #808080 #808080 #fff; 228 | padding: 4px 12px; 229 | font-size: 12px; 230 | color: #000; 231 | cursor: pointer; 232 | box-shadow: 1px 1px 0 #000; 233 | } 234 | 235 | button:active { 236 | border-color: #808080 #fff #fff #808080; 237 | padding: 5px 11px 3px 13px; 238 | box-shadow: none; 239 | } 240 | 241 | .paint-canvas { 242 | border: #000 2px solid; 243 | } 244 | 245 | .paint-tools input[type="color"] { 246 | width: 40px; 247 | height: 25px; 248 | } 249 | 250 | .paint-tools input[type="range"] { 251 | width: 150px; 252 | } 253 | 254 | input[type="text"], 255 | textarea { 256 | background: #fff; 257 | border: 2px solid; 258 | border-color: #808080 #fff #fff #808080; 259 | font-size: 12px; 260 | padding: 3px 5px; 261 | color: #000; 262 | } 263 | 264 | input[type="text"]:focus, 265 | textarea:focus { 266 | outline: 1px solid #000080; 267 | outline-offset: -1px; 268 | } 269 | 270 | .calc-display { 271 | background: #e8f4e8 !important; 272 | font-family: monospace !important; 273 | font-size: 14px !important; 274 | text-align: right; 275 | letter-spacing: 1px; 276 | padding: 5px 8px !important; 277 | } 278 | 279 | .notepad-content { 280 | background: #fff; 281 | border: 2px solid; 282 | border-color: #808080 #fff #fff #808080; 283 | font-size: 12px; 284 | padding: 5px; 285 | resize: none; 286 | width: calc(100% - 14px); 287 | height: 100px; 288 | } 289 | 290 | .file-explorer { 291 | height: 100%; 292 | display: flex; 293 | flex-direction: column; 294 | padding: 15px; 295 | overflow-y: auto; 296 | } 297 | 298 | .file-toolbar { 299 | padding: 5px; 300 | border-bottom: 2px solid; 301 | border-color: #808080 #fff #fff #808080; 302 | } 303 | 304 | .file-list { 305 | flex: 1; 306 | padding: 10px; 307 | display: flex; 308 | flex-wrap: wrap; 309 | align-content: flex-start; 310 | gap: 10px; 311 | } 312 | 313 | .file-item { 314 | width: 80px; 315 | text-align: center; 316 | cursor: pointer; 317 | padding: 5px; 318 | display: flex; 319 | flex-direction: column; 320 | align-items: center; 321 | } 322 | 323 | .file-item:hover { 324 | background: rgba(255, 255, 255, 0.3); 325 | } 326 | 327 | .context-menu { 328 | position: fixed; 329 | background: #c0c0c0; 330 | border: 2px solid; 331 | border-color: #fff #808080 #808080 #fff; 332 | padding: 2px; 333 | } 334 | 335 | .menu-item { 336 | padding: 3px 20px; 337 | cursor: pointer; 338 | } 339 | 340 | .menu-item:hover { 341 | background: #000080; 342 | color: white; 343 | } 344 | 345 | .python-editor { 346 | display: flex; 347 | flex-direction: column; 348 | height: 100%; 349 | } 350 | 351 | .editor-content { 352 | flex-grow: 1; 353 | font-size: 14px; 354 | padding: 10px; 355 | resize: none; 356 | background: #f8f8f8; 357 | border: none; 358 | outline: none; 359 | } 360 | 361 | .output { 362 | height: 100px; 363 | background: #000; 364 | color: #0f0; 365 | font-family: monospace; 366 | padding: 10px; 367 | overflow-y: auto; 368 | } 369 | 370 | .toolbar { 371 | padding: 5px; 372 | border-bottom: 2px solid; 373 | border-color: #808080 #fff #fff #808080; 374 | display: flex; 375 | gap: 10px; 376 | } 377 | 378 | .keyword { 379 | color: #00f; 380 | } 381 | 382 | .folder { 383 | margin-bottom: 20px; 384 | } 385 | 386 | .folder-header { 387 | display: flex; 388 | align-items: center; 389 | gap: 10px; 390 | padding: 5px; 391 | background: rgba(255, 255, 255, 0.1); 392 | } 393 | 394 | .folder-content { 395 | padding: 10px; 396 | padding-left: 30px; 397 | display: flex; 398 | flex-wrap: wrap; 399 | gap: 15px; 400 | } 401 | 402 | .modal { 403 | display: none; 404 | position: fixed; 405 | z-index: 1000; 406 | left: 0; 407 | top: 0; 408 | width: 100%; 409 | height: 100%; 410 | background-color: rgba(0, 0, 0, 0.7); 411 | } 412 | 413 | .modal-content { 414 | background-color: #c0c0c0; 415 | margin: 10% auto; 416 | padding: 2px; 417 | width: 300px; 418 | border: 2px solid; 419 | border-color: #fff #808080 #808080 #fff; 420 | box-shadow: 2px 2px #000; 421 | } 422 | 423 | .modal-header { 424 | background: var(--window-title-color); 425 | color: white; 426 | font-weight: bold; 427 | padding: 2px 4px; 428 | height: 18px; 429 | display: flex; 430 | justify-content: space-between; 431 | align-items: center; 432 | } 433 | 434 | .modal-body { 435 | padding: 10px; 436 | background: #c0c0c0; 437 | border: 2px solid; 438 | border-color: #808080 #fff #fff #808080; 439 | } 440 | 441 | .modal-close { 442 | width: 14px; 443 | height: 14px; 444 | background: var(--close-btn-color); 445 | border: 1px solid; 446 | border-color: #fff #808080 #808080 #fff; 447 | border-radius: 50%; 448 | cursor: pointer; 449 | display: flex; 450 | align-items: center; 451 | justify-content: center; 452 | font-size: 12px; 453 | } 454 | 455 | .modal-close:active { 456 | border-color: #808080 #fff #fff #808080; 457 | } 458 | 459 | .game-container { 460 | display: flex; 461 | flex-direction: column; 462 | align-items: center; 463 | justify-content: center; 464 | background-color: #222; 465 | width: 100%; 466 | height: 100%; 467 | } 468 | 469 | .game-instructions { 470 | color: #0f0; 471 | margin-bottom: 10px; 472 | text-align: center; 473 | } 474 | 475 | #snake { 476 | border: 2px solid #0f0; 477 | background-color: #000; 478 | } 479 | 480 | .settings { 481 | padding: 20px; 482 | } 483 | 484 | .color-grid { 485 | display: grid; 486 | grid-template-columns: repeat(2, 1fr); 487 | gap: 15px; 488 | margin: 15px 0; 489 | } 490 | 491 | .color-option { 492 | display: flex; 493 | flex-direction: column; 494 | gap: 5px; 495 | } 496 | 497 | .date-settings { 498 | display: flex; 499 | flex-direction: column; 500 | gap: 10px; 501 | margin: 15px 0; 502 | } 503 | 504 | .y2k-celebration { 505 | position: fixed; 506 | top: 0; 507 | left: 0; 508 | width: 100vw; 509 | height: 100vh; 510 | background: #000; 511 | color: #fff; 512 | display: flex; 513 | flex-direction: column; 514 | align-items: center; 515 | justify-content: center; 516 | text-align: center; 517 | } 518 | 519 | .fireworks { 520 | position: absolute; 521 | top: 0; 522 | left: 0; 523 | width: 100%; 524 | height: 100%; 525 | pointer-events: none; 526 | } 527 | 528 | .retro-browser { 529 | display: flex; 530 | flex-direction: column; 531 | height: 100%; 532 | background: #dedbdb; 533 | } 534 | 535 | .browser-banner { 536 | width: 90%; 537 | border-bottom: 2px solid; 538 | border-color: #808080 #fff #fff #808080; 539 | margin-bottom: 10px; 540 | } 541 | 542 | .browser-toolbar { 543 | display: flex; 544 | gap: 10px; 545 | padding: 10px; 546 | background: #dedbdb; 547 | border: 2px solid; 548 | border-color: #808080 #fff #fff #808080; 549 | } 550 | 551 | .search-bar { 552 | flex-grow: 1; 553 | padding: 5px 10px; 554 | font-size: 14px; 555 | background: #fff; 556 | border: 2px inset #808080; 557 | color: #000; 558 | } 559 | 560 | .search-bar:focus { 561 | outline: 1px solid #000080; 562 | outline-offset: -1px; 563 | } 564 | 565 | .search-btn { 566 | min-width: 80px; 567 | height: 28px; 568 | background: linear-gradient(to bottom, #fff, #c0c0c0); 569 | border: 2px solid; 570 | border-color: #fff #808080 #808080 #fff; 571 | font-family: 'Tiny5'; 572 | font-weight: bold; 573 | text-transform: uppercase; 574 | font-size: 12px; 575 | cursor: pointer; 576 | } 577 | 578 | .search-btn:active { 579 | border-color: #808080 #fff #fff #808080; 580 | padding-top: 2px; 581 | } -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | class FileSystem { 2 | constructor() { 3 | if (FileSystem.instance) { 4 | return FileSystem.instance; 5 | } 6 | 7 | this.fs = { 8 | notes: { 9 | type: "folder", 10 | content: { 11 | "welcome.txt": { 12 | type: "file", 13 | content: 14 | "Welcome to Y2K WebOS! (Psst.. Easter egg when you set your year to 2000 in settings!)", 15 | modified: new Date().toISOString(), 16 | }, 17 | }, 18 | }, 19 | art: { 20 | type: "folder", 21 | content: {}, 22 | }, 23 | }; 24 | 25 | FileSystem.instance = this; 26 | } 27 | 28 | saveFile(path, content) { 29 | const [folder, filename] = path.split("/"); 30 | 31 | if (!this.fs[folder]) { 32 | alert("Please save in notes/ or art/ folder"); 33 | return false; 34 | } 35 | 36 | if (!filename) { 37 | alert("Please provide a valid filename."); 38 | return false; 39 | } 40 | 41 | this.fs[folder].content[filename] = { 42 | type: "file", 43 | content: content, 44 | modified: new Date().toISOString(), 45 | }; 46 | 47 | return true; 48 | } 49 | 50 | loadFile(path) { 51 | const [folder, filename] = path.split("/"); 52 | if (!filename) { 53 | alert("Invalid file path."); 54 | return ""; 55 | } 56 | return this.fs[folder]?.content[filename]?.content || ""; 57 | } 58 | 59 | getStructure() { 60 | return this.fs; 61 | } 62 | } 63 | 64 | const fileSystem = new FileSystem(); 65 | 66 | class AppManager { 67 | static apps = { 68 | Notepad: { 69 | create: () => { 70 | return ` 71 |
72 |
73 | 74 |
75 | 76 |
`; 77 | }, 78 | init: (window, readOnly = false) => { 79 | const textarea = window.querySelector(".notepad-content"); 80 | const saveBtn = window.querySelector(".save-btn"); 81 | const defaultFolder = "notes"; 82 | 83 | if (readOnly) { 84 | textarea.disabled = true; 85 | saveBtn.style.display = "none"; 86 | } 87 | 88 | saveBtn.onclick = () => { 89 | showModal(` 90 |

Save File

91 | 92 | 93 | `); 94 | 95 | document.getElementById("modal-save-btn").onclick = () => { 96 | const filename = document 97 | .getElementById("modal-filename") 98 | .value.trim(); 99 | if (!filename) { 100 | showModal(` 101 |

Error

102 |

Please enter a filename.

103 | 104 | `); 105 | document.getElementById("modal-close-btn").onclick = hideModal; 106 | return; 107 | } 108 | 109 | const path = `${defaultFolder}/${filename}`; 110 | const content = textarea.value; 111 | 112 | if (fileSystem.saveFile(path, content)) { 113 | showModal(` 114 |

Success

115 |

File saved successfully!

116 | 117 | `); 118 | document.getElementById("modal-close-btn").onclick = hideModal; 119 | windowManager.refreshAppWindow("My Files"); 120 | } else { 121 | showModal(` 122 |

Error

123 |

Failed to save file. Ensure you are saving in the correct folder.

124 | 125 | `); 126 | document.getElementById("modal-close-btn").onclick = hideModal; 127 | } 128 | }; 129 | }; 130 | }, 131 | }, 132 | Paint: { 133 | create: () => { 134 | return ` 135 |
136 |
137 | 138 | 139 | 140 | 141 |

142 | 143 |
`; 144 | }, 145 | init: (window) => { 146 | const canvas = window.querySelector(".paint-canvas"); 147 | const ctx = canvas.getContext("2d"); 148 | let painting = false; 149 | let lastX, lastY; 150 | 151 | canvas.width = window.clientWidth - 40; 152 | canvas.height = window.clientHeight - 100; 153 | 154 | function draw(e) { 155 | if (!painting) return; 156 | const color = window.querySelector('input[type="color"]').value; 157 | const size = window.querySelector('input[type="range"]').value; 158 | 159 | ctx.lineWidth = size; 160 | ctx.lineCap = "round"; 161 | ctx.strokeStyle = color; 162 | 163 | const rect = canvas.getBoundingClientRect(); 164 | const x = e.clientX - rect.left; 165 | const y = e.clientY - rect.top; 166 | 167 | ctx.beginPath(); 168 | ctx.moveTo(lastX, lastY); 169 | ctx.lineTo(x, y); 170 | ctx.stroke(); 171 | 172 | [lastX, lastY] = [x, y]; 173 | } 174 | 175 | canvas.onmousedown = (e) => { 176 | painting = true; 177 | const rect = canvas.getBoundingClientRect(); 178 | [lastX, lastY] = [e.clientX - rect.left, e.clientY - rect.top]; 179 | }; 180 | 181 | canvas.onmouseup = () => (painting = false); 182 | canvas.onmousemove = draw; 183 | canvas.onmouseleave = () => (painting = false); 184 | 185 | window.querySelector(".clear-btn").onclick = () => { 186 | ctx.clearRect(0, 0, canvas.width, canvas.height); 187 | }; 188 | 189 | window.querySelector(".save-btn").onclick = () => { 190 | showModal(` 191 |

Save Drawing

192 | 193 | 194 | `); 195 | 196 | document.getElementById("modal-save-btn").onclick = () => { 197 | const filename = document 198 | .getElementById("modal-filename") 199 | .value.trim(); 200 | if (!filename) { 201 | showModal(` 202 |

Error

203 |

Please enter a filename.

204 | 205 | `); 206 | document.getElementById("modal-close-btn").onclick = hideModal; 207 | return; 208 | } 209 | 210 | const defaultFolder = "art"; 211 | const imageData = canvas.toDataURL("image/png"); 212 | const path = `${defaultFolder}/${filename}`; 213 | 214 | if (fileSystem.saveFile(path, imageData)) { 215 | showModal(` 216 |

Success

217 |

Image saved successfully!

218 | 219 | `); 220 | document.getElementById("modal-close-btn").onclick = hideModal; 221 | windowManager.refreshAppWindow("My Files"); 222 | } else { 223 | showModal(` 224 |

Error

225 |

Failed to save image. Ensure you are saving in the correct folder.

226 | 227 | `); 228 | document.getElementById("modal-close-btn").onclick = hideModal; 229 | } 230 | }; 231 | }; 232 | }, 233 | }, 234 | Calculator: { 235 | create: () => { 236 | return ` 237 |
238 | 239 |
240 | ${[7, 8, 9, "+"] 241 | .map( 242 | (btn) => 243 | `` 244 | ) 245 | .join("")} 246 |
247 | ${[4, 5, 6, "-"] 248 | .map( 249 | (btn) => 250 | `` 251 | ) 252 | .join("")} 253 |
254 | ${[1, 2, 3, "*"] 255 | .map( 256 | (btn) => 257 | `` 258 | ) 259 | .join("")} 260 |
261 | ${[0, "C", "=", "/"] 262 | .map( 263 | (btn) => 264 | `` 265 | ) 266 | .join("")} 267 |
268 |
`; 269 | }, 270 | init: (window) => { 271 | const display = window.querySelector(".calc-display"); 272 | const calculator = new Calculator(); 273 | 274 | window.querySelectorAll(".calc-btn").forEach((btn) => { 275 | btn.addEventListener("click", () => { 276 | const value = btn.dataset.value; 277 | display.value = calculator.handleInput(value); 278 | }); 279 | }); 280 | }, 281 | }, 282 | Browser: { 283 | create: () => { 284 | return ` 285 |
286 |
287 | Browser Banner 288 |
289 |
290 | 291 | 292 |
293 |
294 | `; 295 | }, 296 | init: (appWindow) => { 297 | const searchBtn = appWindow.querySelector(".search-btn"); 298 | const searchBar = appWindow.querySelector(".search-bar"); 299 | 300 | searchBtn.onclick = () => { 301 | const query = searchBar.value.trim(); 302 | if (query === "") { 303 | showModal(` 304 |

Error

305 |

Please enter a search query.

306 | 307 | `); 308 | document.getElementById("modal-close-btn").onclick = hideModal; 309 | return; 310 | } 311 | 312 | const url = 313 | "https://www.google.com/search?q=" + encodeURIComponent(query); 314 | window.open(url, "_blank"); 315 | }; 316 | }, 317 | }, 318 | "My Files": { 319 | create: () => { 320 | const structure = fileSystem.getStructure(); 321 | 322 | return ` 323 |
324 | ${Object.entries(structure) 325 | .map( 326 | ([folder, data]) => ` 327 |
328 |
329 | 330 | ${folder} 331 |
332 |
333 | ${Object.entries(data.content) 334 | .map( 335 | ([filename, file]) => ` 336 |
337 | 343 | ${filename} 344 |
345 | ` 346 | ) 347 | .join("")} 348 |
349 |
350 | ` 351 | ) 352 | .join("")} 353 |
`; 354 | }, 355 | init: (window) => { 356 | const fileList = window.querySelector(".file-explorer"); 357 | 358 | fileList.addEventListener("dblclick", (e) => { 359 | const fileItem = e.target.closest(".file-item"); 360 | if (fileItem) { 361 | const path = fileItem.dataset.path; 362 | const [folder, ...rest] = path.split("/"); 363 | const filename = rest.join("/"); 364 | 365 | const extension = filename.split(".").pop().toLowerCase(); 366 | const imageExtensions = ["png", "jpg", "jpeg", "gif", "bmp"]; 367 | const textExtensions = ["txt", "md", "js", "html", "css"]; 368 | 369 | if (imageExtensions.includes(extension)) { 370 | const imageSrc = fileSystem.loadFile(path); 371 | const imageViewerContent = 372 | AppManager.apps["Image Viewer"].create(imageSrc); 373 | windowManager.createWindow(filename, imageViewerContent); 374 | } else if (textExtensions.includes(extension)) { 375 | const notepadContent = AppManager.apps.Notepad.create(); 376 | const notepadWindow = windowManager.createWindow( 377 | filename, 378 | notepadContent 379 | ); 380 | AppManager.apps.Notepad.init(notepadWindow, true); 381 | const textarea = notepadWindow.querySelector(".notepad-content"); 382 | textarea.value = fileSystem.loadFile(path); 383 | } 384 | } 385 | }); 386 | }, 387 | }, 388 | "Image Viewer": { 389 | create: (imageSrc) => { 390 | return ` 391 |
392 | No file opened 393 |
`; 394 | }, 395 | }, 396 | Codédex: { 397 | create: () => { 398 | window.open("https://www.codedex.io/community/hackathon/mMIVccDtlC5hkuWWrJsJ", "_blank"); 399 | return `
Redirecting to Codédex...
`; 400 | }, 401 | }, 402 | Game: { 403 | create: () => { 404 | return ` 405 |
406 |
Press SPACE to start/pause. Use arrow keys to move.
407 | 408 |
`; 409 | }, 410 | init: (window) => { 411 | const canvas = window.querySelector("#snake"); 412 | if (!canvas) { 413 | console.error("Canvas with id 'snake' not found."); 414 | return; 415 | } 416 | const ctx = canvas.getContext("2d"); 417 | const gridSize = 20; 418 | let snake = [{ x: 10, y: 10 }]; 419 | let food = { x: 15, y: 15 }; 420 | let direction = "right"; 421 | let score = 0; 422 | let gameRunning = false; 423 | let gameLoop; 424 | 425 | canvas.width = 400; 426 | canvas.height = 400; 427 | 428 | document.addEventListener("keydown", (e) => { 429 | if (e.code === "Space" && window.style.zIndex === (windowManager.zIndex - 1).toString()) { 430 | e.preventDefault(); 431 | gameRunning = !gameRunning; 432 | if (gameRunning) { 433 | gameLoop = setInterval(() => { 434 | updateGame(); 435 | drawGame(); 436 | }, 100); 437 | } else { 438 | clearInterval(gameLoop); 439 | } 440 | } 441 | if (gameRunning) { 442 | switch (e.key) { 443 | case "ArrowUp": 444 | if (direction !== "down") direction = "up"; 445 | break; 446 | case "ArrowDown": 447 | if (direction !== "up") direction = "down"; 448 | break; 449 | case "ArrowLeft": 450 | if (direction !== "right") direction = "left"; 451 | break; 452 | case "ArrowRight": 453 | if (direction !== "left") direction = "right"; 454 | break; 455 | } 456 | } 457 | }); 458 | 459 | function drawGame() { 460 | ctx.fillStyle = "black"; 461 | ctx.fillRect(0, 0, canvas.width, canvas.height); 462 | 463 | ctx.fillStyle = "lime"; 464 | snake.forEach((segment) => { 465 | ctx.fillRect( 466 | segment.x * gridSize, 467 | segment.y * gridSize, 468 | gridSize - 2, 469 | gridSize - 2 470 | ); 471 | }); 472 | 473 | ctx.fillStyle = "red"; 474 | ctx.fillRect( 475 | food.x * gridSize, 476 | food.y * gridSize, 477 | gridSize - 2, 478 | gridSize - 2 479 | ); 480 | 481 | ctx.fillStyle = "white"; 482 | ctx.font = "20px Arial"; 483 | ctx.fillText(`Score: ${score}`, 10, 30); 484 | } 485 | 486 | function updateGame() { 487 | const head = { ...snake[0] }; 488 | 489 | switch (direction) { 490 | case "up": 491 | head.y--; 492 | break; 493 | case "down": 494 | head.y++; 495 | break; 496 | case "left": 497 | head.x--; 498 | break; 499 | case "right": 500 | head.x++; 501 | break; 502 | } 503 | 504 | if ( 505 | head.x < 0 || 506 | head.x >= canvas.width / gridSize || 507 | head.y < 0 || 508 | head.y >= canvas.height / gridSize 509 | ) { 510 | return gameOver(); 511 | } 512 | 513 | if ( 514 | snake.some( 515 | (segment) => segment.x === head.x && segment.y === head.y 516 | ) 517 | ) { 518 | return gameOver(); 519 | } 520 | 521 | snake.unshift(head); 522 | 523 | if (head.x === food.x && head.y === food.y) { 524 | score += 10; 525 | spawnFood(); 526 | } else { 527 | snake.pop(); 528 | } 529 | } 530 | 531 | function spawnFood() { 532 | food = { 533 | x: Math.floor(Math.random() * (canvas.width / gridSize)), 534 | y: Math.floor(Math.random() * (canvas.height / gridSize)), 535 | }; 536 | 537 | if ( 538 | snake.some( 539 | (segment) => segment.x === food.x && segment.y === food.y 540 | ) 541 | ) { 542 | spawnFood(); 543 | } 544 | } 545 | 546 | function gameOver() { 547 | clearInterval(gameLoop); 548 | showModal(` 549 |

Game Over!

550 |

Your Score: ${score}

551 | 552 | `); 553 | document.getElementById("modal-close-btn").onclick = () => { 554 | hideModal(); 555 | snake = [{ x: 10, y: 10 }]; 556 | direction = "right"; 557 | score = 0; 558 | spawnFood(); 559 | drawGame(); 560 | gameRunning = false; 561 | }; 562 | } 563 | 564 | drawGame(); 565 | }, 566 | }, 567 | Chat: { 568 | create: () => { 569 | return ` 570 |
571 |
572 |
573 |
574 | 575 | 576 |
577 |
`; 578 | }, 579 | init: (window) => { 580 | const chatStatus = window.querySelector(".chat-status"); 581 | const chatMessages = window.querySelector(".chat-messages"); 582 | const chatInput = window.querySelector(".chat-input"); 583 | const chatSend = window.querySelector(".chat-send"); 584 | let ws; 585 | 586 | function connect() { 587 | ws = new WebSocket("wss://y2kos.onrender.com"); 588 | 589 | ws.onopen = () => { 590 | chatStatus.innerHTML = "● Connected"; 591 | chatStatus.style.color = "#0f0"; 592 | chatSend.disabled = false; 593 | chatInput.disabled = false; 594 | }; 595 | 596 | ws.onclose = () => { 597 | chatStatus.innerHTML = "● Disconnected - Reconnecting..."; 598 | chatStatus.style.color = "#f00"; 599 | chatSend.disabled = true; 600 | chatInput.disabled = true; 601 | setTimeout(connect, 5000); 602 | }; 603 | 604 | ws.onmessage = (event) => { 605 | try { 606 | if (event.data instanceof Blob) { 607 | event.data 608 | .text() 609 | .then((text) => { 610 | const data = JSON.parse(text); 611 | addMessage(data.username, data.message); 612 | }) 613 | .catch((error) => { 614 | console.error("Error parsing message:", error); 615 | }); 616 | } else { 617 | const data = JSON.parse(event.data); 618 | addMessage(data.username, data.message); 619 | } 620 | } catch (error) { 621 | console.error("Error handling message:", error); 622 | } 623 | }; 624 | 625 | ws.onerror = (error) => { 626 | console.error("WebSocket error:", error); 627 | chatStatus.innerHTML = "● Error connecting"; 628 | chatStatus.style.color = "#f00"; 629 | }; 630 | } 631 | 632 | const userId = Array(4) 633 | .fill(0) 634 | .map(() => { 635 | const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 636 | return chars.charAt(Math.floor(Math.random() * chars.length)); 637 | }) 638 | .join(""); 639 | 640 | function addMessage(username, message) { 641 | const messageDiv = document.createElement("div"); 642 | messageDiv.className = "chat-message"; 643 | messageDiv.innerHTML = ` 644 | ${username}: 645 | ${message} 646 | ${new Date().toLocaleTimeString()} 647 | `; 648 | chatMessages.appendChild(messageDiv); 649 | chatMessages.scrollTop = chatMessages.scrollHeight; 650 | } 651 | 652 | function sendMessage() { 653 | const message = chatInput.value.trim(); 654 | if (message && ws.readyState === WebSocket.OPEN) { 655 | ws.send( 656 | JSON.stringify({ 657 | type: "message", 658 | username: `User [${userId}]`, 659 | message: message, 660 | }) 661 | ); 662 | chatInput.value = ""; 663 | } 664 | } 665 | 666 | chatSend.onclick = sendMessage; 667 | chatInput.onkeypress = (e) => { 668 | if (e.key === "Enter") { 669 | sendMessage(); 670 | } 671 | }; 672 | 673 | connect(); 674 | 675 | return () => { 676 | if (ws) { 677 | ws.close(); 678 | } 679 | }; 680 | }, 681 | }, 682 | Settings: { 683 | create: () => { 684 | return ` 685 |
686 |
687 |

System Colors

688 |
689 |
690 | 691 | 692 |
693 |
694 | 695 | 696 |
697 |
698 | 699 | 700 |
701 |
702 | 703 | 704 |
705 |
706 | 707 | 708 |
709 |
710 |
711 |
712 |

Date & Time

713 |
714 | 715 | 716 |
717 |
718 | 719 |
`; 720 | }, 721 | init: (window) => { 722 | const inputs = { 723 | buttonColor: window.querySelector("#button-color"), 724 | taskbarColor: window.querySelector("#taskbar-color"), 725 | windowTitleColor: window.querySelector("#window-title-color"), 726 | closeBtnColor: window.querySelector("#close-btn-color"), 727 | minBtnColor: window.querySelector("#min-btn-color"), 728 | customYear: window.querySelector("#custom-year"), 729 | }; 730 | 731 | const saveBtn = window.querySelector("#save-settings-btn"); 732 | 733 | saveBtn.onclick = () => { 734 | Object.entries(inputs).forEach(([key, input]) => { 735 | updateColor(key, input.value); 736 | }); 737 | 738 | checkY2K(inputs.customYear.value); 739 | 740 | showModal(` 741 |

Success

742 |

Settings have been saved.

743 | 744 | `); 745 | document.getElementById("modal-close-btn").onclick = hideModal; 746 | }; 747 | 748 | function updateColor(key, value) { 749 | const cssVarMap = { 750 | buttonColor: "--button-color", 751 | taskbarColor: "--taskbar-color", 752 | windowTitleColor: "--window-title-color", 753 | closeBtnColor: "--close-btn-color", 754 | minBtnColor: "--min-btn-color", 755 | }; 756 | 757 | if (cssVarMap[key]) { 758 | document.documentElement.style.setProperty(cssVarMap[key], value); 759 | } 760 | } 761 | 762 | function checkY2K(year) { 763 | if (year === "2000") { 764 | triggerY2KEvent(); 765 | } 766 | } 767 | 768 | function createFireworks() { 769 | const fireworksContainer = document.querySelector(".fireworks"); 770 | const canvas = document.createElement("canvas"); 771 | fireworksContainer.appendChild(canvas); 772 | const ctx = canvas.getContext("2d"); 773 | canvas.width = fireworksContainer.offsetWidth; 774 | canvas.height = fireworksContainer.offsetHeight; 775 | 776 | window.addEventListener("resize", () => { 777 | canvas.width = fireworksContainer.offsetWidth; 778 | canvas.height = fireworksContainer.offsetHeight; 779 | }); 780 | 781 | const particles = []; 782 | const fireworks = []; 783 | const gravity = 0.05; 784 | 785 | class Particle { 786 | constructor(x, y, color) { 787 | this.x = x; 788 | this.y = y; 789 | this.radius = 2; 790 | this.color = color; 791 | this.speed = Math.random() * 3 + 1; 792 | this.angle = Math.random() * Math.PI * 2; 793 | this.velocityX = Math.cos(this.angle) * this.speed; 794 | this.velocityY = Math.sin(this.angle) * this.speed; 795 | this.alpha = 1; 796 | } 797 | 798 | update() { 799 | this.velocityY += gravity; 800 | this.x += this.velocityX; 801 | this.y += this.velocityY; 802 | this.alpha -= 0.01; 803 | } 804 | 805 | draw() { 806 | ctx.save(); 807 | ctx.globalAlpha = this.alpha; 808 | ctx.beginPath(); 809 | ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); 810 | ctx.fillStyle = this.color; 811 | ctx.fill(); 812 | ctx.restore(); 813 | } 814 | } 815 | 816 | class Firework { 817 | constructor(x, y) { 818 | this.x = x; 819 | this.y = y; 820 | this.targetY = (Math.random() * canvas.height) / 2; 821 | this.exploded = false; 822 | this.color = `hsl(${Math.random() * 360}, 100%, 50%)`; 823 | } 824 | 825 | update() { 826 | this.y -= 2; 827 | if (this.y <= this.targetY && !this.exploded) { 828 | this.exploded = true; 829 | this.explode(); 830 | } 831 | } 832 | 833 | explode() { 834 | const particleCount = 100; 835 | new Audio("sfx/blast.mp3").play(); 836 | for (let i = 0; i < particleCount; i++) { 837 | particles.push(new Particle(this.x, this.y, this.color)); 838 | } 839 | } 840 | 841 | draw() { 842 | if (!this.exploded) { 843 | ctx.beginPath(); 844 | ctx.arc(this.x, this.y, 3, 0, Math.PI * 2); 845 | ctx.fillStyle = this.color; 846 | ctx.fill(); 847 | } 848 | } 849 | } 850 | 851 | function launchFirework() { 852 | const x = Math.random() * canvas.width; 853 | const y = canvas.height; 854 | fireworks.push(new Firework(x, y)); 855 | } 856 | 857 | function animate() { 858 | ctx.fillStyle = "rgba(0, 0, 0, 0.1)"; 859 | ctx.fillRect(0, 0, canvas.width, canvas.height); 860 | 861 | fireworks.forEach((firework, index) => { 862 | firework.update(); 863 | firework.draw(); 864 | if (firework.exploded) { 865 | fireworks.splice(index, 1); 866 | } 867 | }); 868 | 869 | particles.forEach((particle, index) => { 870 | particle.update(); 871 | particle.draw(); 872 | if (particle.alpha <= 0) { 873 | particles.splice(index, 1); 874 | } 875 | }); 876 | 877 | requestAnimationFrame(animate); 878 | } 879 | 880 | launchFirework(); 881 | setInterval(launchFirework, 500); 882 | animate(); 883 | } 884 | 885 | function triggerY2KEvent() { 886 | const errors = [ 887 | "CRITICAL ERROR: System time corruption detected", 888 | "WARNING: Date overflow imminent", 889 | "FATAL ERROR: Memory allocation failed", 890 | "ERROR: Operating system crash detected", 891 | "SYSTEM FAILURE: Time paradox detected", 892 | ]; 893 | let errorIndex = 0; 894 | 895 | function showNextError() { 896 | if (errorIndex < errors.length) { 897 | showModal(` 898 |

SYSTEM ERROR

899 |

${errors[errorIndex]}

900 |
Code: Y2K-${Math.floor( 901 | Math.random() * 9999 902 | )}
903 | `); 904 | const errorSound = new Audio("sfx/error.mp3"); 905 | errorSound.play().catch(() => { }); 906 | errorIndex++; 907 | setTimeout(showNextError, 1000); 908 | } else { 909 | hideModal(); 910 | startDramaticSequence(); 911 | } 912 | } 913 | 914 | function startDramaticSequence() { 915 | const messages = [ 916 | { text: "But wait", delay: 1000 }, 917 | { text: "But wait.", delay: 1000 }, 918 | { text: "But wait..", delay: 1000 }, 919 | { text: "But wait...", delay: 2000 }, 920 | ]; 921 | let messageIndex = 0; 922 | 923 | function showMessage() { 924 | if (messageIndex < messages.length) { 925 | document.body.innerHTML = ` 926 |
927 | ${messages[messageIndex].text} 928 |
929 | `; 930 | messageIndex++; 931 | setTimeout(showMessage, messages[messageIndex - 1].delay); 932 | } else { 933 | startBlackout(); 934 | } 935 | } 936 | showMessage(); 937 | } 938 | 939 | function startBlackout() { 940 | document.body.style.transition = "all 1s"; 941 | document.body.style.background = "#000"; 942 | document.body.style.backgroundImage = "none"; 943 | document.body.innerHTML = ""; 944 | 945 | setTimeout(showCelebration, 5000); 946 | 947 | new Audio("sfx/grand.mp3").play(); 948 | } 949 | 950 | function showCelebration() { 951 | document.body.innerHTML = ` 952 |
960 |
968 |
973 |

Welcome to 2000!

980 |

We Made It!

987 |
988 |
989 | `; 990 | 991 | setTimeout(() => { 992 | const h1 = document.querySelector(".celebration-text h1"); 993 | const h2 = document.querySelector(".celebration-text h2"); 994 | if (h1) h1.style.opacity = "1"; 995 | if (h2) h2.style.opacity = "1"; 996 | }, 100); 997 | 998 | createFireworks(); 999 | 1000 | setTimeout(showEndScreen, 20000); 1001 | } 1002 | 1003 | function showEndScreen() { 1004 | document.body.innerHTML = ` 1005 |
1012 |

Thanks for playing!

1013 |

Created by Candy for Codédex Holiday Hackathon 2024

1014 | 1024 |
1025 | `; 1026 | } 1027 | 1028 | showNextError(); 1029 | } 1030 | }, 1031 | }, 1032 | }; 1033 | } 1034 | 1035 | class Calculator { 1036 | constructor() { 1037 | this.display = ""; 1038 | this.previousValue = null; 1039 | this.operation = null; 1040 | this.newNumber = true; 1041 | } 1042 | 1043 | clear() { 1044 | this.display = ""; 1045 | this.previousValue = null; 1046 | this.operation = null; 1047 | this.newNumber = true; 1048 | return "0"; 1049 | } 1050 | 1051 | calculate() { 1052 | if (this.previousValue === null || this.operation === null) 1053 | return this.display; 1054 | 1055 | const prev = parseFloat(this.previousValue); 1056 | const current = parseFloat(this.display); 1057 | let result = 0; 1058 | 1059 | switch (this.operation) { 1060 | case "+": 1061 | result = prev + current; 1062 | break; 1063 | case "-": 1064 | result = prev - current; 1065 | break; 1066 | case "*": 1067 | result = prev * current; 1068 | break; 1069 | case "/": 1070 | result = current !== 0 ? prev / current : "Error"; 1071 | break; 1072 | } 1073 | 1074 | this.previousValue = null; 1075 | this.operation = null; 1076 | this.newNumber = true; 1077 | return result.toString(); 1078 | } 1079 | 1080 | handleInput(value) { 1081 | if (value === "C") { 1082 | return this.clear(); 1083 | } 1084 | 1085 | if (value === "=") { 1086 | this.display = this.calculate(); 1087 | return this.display; 1088 | } 1089 | 1090 | if ("+-*/".includes(value)) { 1091 | if (this.previousValue !== null) { 1092 | this.display = this.calculate(); 1093 | } 1094 | this.previousValue = this.display; 1095 | this.operation = value; 1096 | this.newNumber = true; 1097 | return this.display; 1098 | } 1099 | 1100 | if (this.newNumber) { 1101 | this.display = value; 1102 | this.newNumber = false; 1103 | } else { 1104 | this.display += value; 1105 | } 1106 | return this.display; 1107 | } 1108 | } 1109 | 1110 | class WindowManager { 1111 | constructor() { 1112 | this.windows = new Map(); 1113 | this.zIndex = 100; 1114 | } 1115 | 1116 | createWindow(title, content) { 1117 | const win = document.createElement("div"); 1118 | win.className = "window"; 1119 | win.style.zIndex = this.zIndex++; 1120 | win.style.left = "50px"; 1121 | win.style.top = "50px"; 1122 | 1123 | win.innerHTML = ` 1124 |
1125 | ${title} 1126 |
1127 |
1128 |
1129 |
1130 |
1131 |
${content}
1132 | `; 1133 | 1134 | document.getElementById("desktop").appendChild(win); 1135 | this.makeDraggable(win); 1136 | this.setupControls(win); 1137 | this.windows.set(win, title); 1138 | 1139 | if (AppManager.apps[title]?.init) { 1140 | AppManager.apps[title].init(win); 1141 | } 1142 | 1143 | return win; 1144 | } 1145 | 1146 | makeDraggable(win) { 1147 | const title = win.querySelector(".window-title"); 1148 | let pos1 = 0, 1149 | pos2 = 0, 1150 | pos3 = 0, 1151 | pos4 = 0; 1152 | let dragTimeout = null; 1153 | 1154 | title.onmousedown = dragMouseDown; 1155 | 1156 | const self = this; 1157 | 1158 | function dragMouseDown(e) { 1159 | e.preventDefault(); 1160 | pos3 = e.clientX; 1161 | pos4 = e.clientY; 1162 | document.onmouseup = closeDragElement; 1163 | document.onmousemove = elementDragChoppy; 1164 | win.style.zIndex = self.zIndex++; 1165 | } 1166 | 1167 | function elementDrag(e) { 1168 | pos1 = pos3 - e.clientX; 1169 | pos2 = pos4 - e.clientY; 1170 | pos3 = e.clientX; 1171 | pos4 = e.clientY; 1172 | win.style.top = win.offsetTop - pos2 + "px"; 1173 | win.style.left = win.offsetLeft - pos1 + "px"; 1174 | } 1175 | 1176 | function elementDragChoppy(e) { 1177 | if (!dragTimeout) { 1178 | dragTimeout = setTimeout(() => { 1179 | elementDrag(e); 1180 | dragTimeout = null; 1181 | }, 50); 1182 | } 1183 | } 1184 | 1185 | function closeDragElement() { 1186 | document.onmouseup = null; 1187 | document.onmousemove = null; 1188 | if (dragTimeout) { 1189 | clearTimeout(dragTimeout); 1190 | dragTimeout = null; 1191 | } 1192 | } 1193 | } 1194 | 1195 | setupControls(win) { 1196 | win.querySelector(".close-button").onclick = () => { 1197 | const title = this.windows.get(win); 1198 | win.remove(); 1199 | this.windows.delete(win); 1200 | const icon = document.querySelector(`.icon[data-app="${title}"]`); 1201 | if (icon) { 1202 | icon.style.backgroundColor = ""; 1203 | } 1204 | }; 1205 | 1206 | win.querySelector(".minimize-button").onclick = () => { 1207 | win.style.display = "none"; 1208 | }; 1209 | } 1210 | 1211 | refreshAppWindow(appName) { 1212 | this.windows.forEach((title, win) => { 1213 | if (title === appName) { 1214 | win.querySelector(".window-content").innerHTML = 1215 | AppManager.apps[appName].create(); 1216 | AppManager.apps[appName].init(win); 1217 | } 1218 | }); 1219 | } 1220 | } 1221 | 1222 | const windowManager = new WindowManager(); 1223 | 1224 | const desktopIcons = [ 1225 | { name: "My Files", icon: "icons/file.png" }, 1226 | { name: "Browser", icon: "icons/browser.png" }, 1227 | { name: "Notepad", icon: "icons/notepad.png" }, 1228 | { name: "Calculator", icon: "icons/calculator.png" }, 1229 | { name: "Paint", icon: "icons/paint.png" }, 1230 | { name: "Image Viewer", icon: "icons/picture.png" }, 1231 | { name: "Codédex", icon: "icons/code.png" }, 1232 | { name: "Game", icon: "icons/snake.png" }, 1233 | { name: "Settings", icon: "icons/gear.png" }, 1234 | { name: "Chat", icon: "icons/chat.png" }, 1235 | ]; 1236 | 1237 | const iconsPerColumn = 5; 1238 | const columns = Math.ceil(desktopIcons.length / iconsPerColumn); 1239 | 1240 | document.getElementById("desktop").innerHTML = Array(columns) 1241 | .fill() 1242 | .map( 1243 | (_, colIndex) => ` 1244 |
1246 | ${desktopIcons 1247 | .slice(colIndex * iconsPerColumn, (colIndex + 1) * iconsPerColumn) 1248 | .map( 1249 | ({ name, icon }) => ` 1250 |
1251 |
1252 | ${name} 1253 |
1254 | ` 1255 | ) 1256 | .join("")} 1257 |
1258 | ` 1259 | ) 1260 | .join(""); 1261 | 1262 | document.querySelectorAll(".icon").forEach((icon) => { 1263 | icon.addEventListener("click", () => { 1264 | const appName = icon.dataset.app; 1265 | let existingWindow = null; 1266 | 1267 | windowManager.windows.forEach((title, win) => { 1268 | if (title === appName) { 1269 | existingWindow = win; 1270 | } 1271 | }); 1272 | 1273 | if (existingWindow) { 1274 | existingWindow.style.display = "block"; 1275 | existingWindow.style.zIndex = windowManager.zIndex++; 1276 | } else { 1277 | const app = AppManager.apps[appName]; 1278 | let content = app.create(); 1279 | 1280 | icon.style.backgroundColor = "rgba(255,255,255,0.2)"; 1281 | windowManager.createWindow(appName, content); 1282 | } 1283 | }); 1284 | }); 1285 | 1286 | function showModal(contentHTML) { 1287 | const modal = document.getElementById("modal"); 1288 | const modalBody = document.getElementById("modal-body"); 1289 | modalBody.innerHTML = contentHTML; 1290 | modal.style.display = "block"; 1291 | } 1292 | 1293 | function hideModal() { 1294 | const modal = document.getElementById("modal"); 1295 | modal.style.display = "none"; 1296 | } 1297 | 1298 | document.getElementById("modal-close").onclick = hideModal; 1299 | 1300 | window.onclick = function (event) { 1301 | const modal = document.getElementById("modal"); 1302 | if (event.target === modal) { 1303 | hideModal(); 1304 | } 1305 | }; 1306 | 1307 | function updateClock() { 1308 | const now = new Date(); 1309 | const time = now.toLocaleTimeString(); 1310 | const defaultDate = new Date(now); 1311 | defaultDate.setFullYear(1999); 1312 | const date = window.customDate || defaultDate.toLocaleDateString(); 1313 | document.querySelector(".taskbar-right").innerHTML = ` 1314 |
${time}
1315 |
${date}
1316 | `; 1317 | } 1318 | 1319 | const taskbarRight = document.createElement("div"); 1320 | taskbarRight.className = "taskbar-right"; 1321 | document.getElementById("taskbar").appendChild(taskbarRight); 1322 | 1323 | setInterval(updateClock, 1000); 1324 | updateClock(); 1325 | 1326 | function openWelcomeFile() { 1327 | const path = "notes/welcome.txt"; 1328 | const content = fileSystem.loadFile(path); 1329 | 1330 | const notepadContent = AppManager.apps.Notepad.create(); 1331 | const notepadWindow = windowManager.createWindow("Welcome", notepadContent); 1332 | const textarea = notepadWindow.querySelector(".notepad-content"); 1333 | 1334 | textarea.value = content; 1335 | AppManager.apps.Notepad.init(notepadWindow, true); 1336 | } 1337 | 1338 | const bootScreen = document.getElementById("boot-screen"); 1339 | let bootText = bootScreen.innerHTML; 1340 | bootScreen.innerHTML = ""; 1341 | 1342 | let charIndex = 0; 1343 | const typeSpeed = 10; 1344 | 1345 | function typeText() { 1346 | if (charIndex < bootText.length) { 1347 | bootScreen.innerHTML += bootText.charAt(charIndex); 1348 | charIndex++; 1349 | setTimeout(typeText, typeSpeed); 1350 | } else { 1351 | const boot = new Audio("sfx/boot.mp3"); 1352 | setTimeout(() => { 1353 | bootScreen.classList.add("fade-out"); 1354 | boot.play(); 1355 | setTimeout(openWelcomeFile, 1500); 1356 | }, 1000); 1357 | } 1358 | } 1359 | 1360 | setTimeout(typeText, 500); 1361 | --------------------------------------------------------------------------------