├── .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 | 
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 | 
27 | 
28 | 
29 | 
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 |
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 | `;
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 |

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 |
332 |
333 | ${Object.entries(data.content)
334 | .map(
335 | ([filename, file]) => `
336 |
337 |

339 | .endsWith()
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 |

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 |
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 |
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 |
--------------------------------------------------------------------------------