5 | *
6 | * @param {HTMLElement} el
7 | * @param {HTMLElement|string} target DOM Element or CSS Selector
8 | */
9 | export function portal(el: HTMLElement, target: HTMLElement | string = 'body') {
10 | let targetEl: HTMLElement;
11 |
12 | async function update(newTarget: HTMLElement | string) {
13 | target = newTarget;
14 |
15 | if (typeof target === 'string') {
16 | targetEl = document.querySelector(target);
17 |
18 | if (targetEl === null) {
19 | await tick();
20 | targetEl = document.querySelector(target);
21 | }
22 |
23 | if (targetEl === null) {
24 | throw new Error(`No element found matching css selector: "${target}"`);
25 | }
26 | } else if (target instanceof HTMLElement) {
27 | targetEl = target;
28 | } else {
29 | throw new TypeError(
30 | `Unknown portal target type: ${
31 | target === null ? 'null' : typeof target
32 | }. Allowed types: string (CSS selector) or HTMLElement.`,
33 | );
34 | }
35 | targetEl.appendChild(el);
36 | el.hidden = false;
37 | }
38 |
39 | function destroy() {
40 | if (el.parentNode) {
41 | el.parentNode.removeChild(el);
42 | }
43 | }
44 |
45 | update(target);
46 |
47 | return {
48 | update,
49 | destroy,
50 | };
51 | }
52 |
--------------------------------------------------------------------------------
/public/assets/cursors/normal-select.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
--------------------------------------------------------------------------------
/src/components/Desktop/Window/WindowsArea.svelte:
--------------------------------------------------------------------------------
1 |
29 |
30 |
31 | {#each Object.keys(appsConfig) as appID}
32 | {#if $openApps[appID] && appsConfig[appID].shouldOpenWindow}
33 | {#await import('./Window.svelte') then { default: Window }}
34 |
35 | {/await}
36 | {/if}
37 | {/each}
38 |
39 |
40 |
54 |
--------------------------------------------------------------------------------
/src/components/SVG/SwitchSVG.svelte:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/public/assets/cursors/link-select.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
19 |
20 |
--------------------------------------------------------------------------------
/src/css/reset.css:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | div,
4 | span,
5 | applet,
6 | object,
7 | iframe,
8 | h1,
9 | h2,
10 | h3,
11 | h4,
12 | h5,
13 | h6,
14 | p,
15 | blockquote,
16 | pre,
17 | a,
18 | abbr,
19 | acronym,
20 | address,
21 | big,
22 | cite,
23 | code,
24 | del,
25 | dfn,
26 | em,
27 | img,
28 | ins,
29 | kbd,
30 | q,
31 | s,
32 | samp,
33 | small,
34 | strike,
35 | strong,
36 | sub,
37 | sup,
38 | tt,
39 | var,
40 | b,
41 | u,
42 | i,
43 | center,
44 | dl,
45 | dt,
46 | dd,
47 | ol,
48 | ul,
49 | li,
50 | fieldset,
51 | form,
52 | label,
53 | legend,
54 | table,
55 | caption,
56 | tbody,
57 | tfoot,
58 | thead,
59 | tr,
60 | th,
61 | td,
62 | article,
63 | aside,
64 | canvas,
65 | details,
66 | embed,
67 | figure,
68 | figcaption,
69 | footer,
70 | header,
71 | hgroup,
72 | menu,
73 | nav,
74 | output,
75 | ruby,
76 | section,
77 | summary,
78 | time,
79 | mark,
80 | audio,
81 | video {
82 | margin: 0;
83 | padding: 0;
84 | border: 0;
85 | font-size: 100%;
86 | font: inherit;
87 | vertical-align: baseline;
88 | }
89 | /* HTML5 display-role reset for older browsers */
90 | article,
91 | aside,
92 | details,
93 | figcaption,
94 | figure,
95 | footer,
96 | header,
97 | hgroup,
98 | menu,
99 | nav,
100 | section {
101 | display: block;
102 | }
103 | body {
104 | line-height: 1;
105 | }
106 | ol,
107 | ul {
108 | list-style: none;
109 | }
110 | blockquote,
111 | q {
112 | quotes: none;
113 | }
114 | blockquote:before,
115 | blockquote:after,
116 | q:before,
117 | q:after {
118 | content: '';
119 | content: none;
120 | }
121 | table {
122 | border-collapse: collapse;
123 | border-spacing: 0;
124 | }
125 |
--------------------------------------------------------------------------------
/src/stores/apps.store.ts:
--------------------------------------------------------------------------------
1 | import { writable } from 'svelte/store';
2 | import type { appsConfig } from '🍎/configs/apps/apps-config';
3 |
4 | export type AppID = keyof typeof appsConfig;
5 |
6 | /** Which apps are currently open */
7 | export const openApps = writable
>({
8 | wallpapers: false,
9 | finder: true,
10 | vscode: false,
11 | calculator: false,
12 | // safari: false,
13 | appstore: false,
14 | calendar: false,
15 | // 'system-preferences': false,
16 |
17 | 'purus-twitter': false,
18 | 'view-source': true,
19 |
20 | vercel: true,
21 |
22 | ukraine: true,
23 | });
24 |
25 | /** Which app is currently focused */
26 | export const activeApp = writable('finder');
27 |
28 | /**
29 | * Maximum zIndex for the active app
30 | * Initialize with -2, so that it becomes 0 when initialised
31 | */
32 | export const activeAppZIndex = writable(-2);
33 |
34 | export const appZIndices = writable>({
35 | wallpapers: 0,
36 | finder: 0,
37 | vscode: 0,
38 | calculator: 0,
39 | // safari: 0,
40 | appstore: 0,
41 | calendar: 0,
42 | // 'system-preferences': 0,
43 |
44 | 'purus-twitter': 0,
45 | 'view-source': 0,
46 |
47 | vercel: 0,
48 |
49 | ukraine: 0,
50 | });
51 |
52 | export const isAppBeingDragged = writable(false);
53 |
54 | export const appsInFullscreen = writable>({
55 | wallpapers: false,
56 | finder: false,
57 | vscode: false,
58 | calculator: false,
59 | // safari: false,
60 | appstore: false,
61 | calendar: false,
62 | // 'system-preferences': false,
63 |
64 | 'purus-twitter': false,
65 | 'view-source': false,
66 |
67 | vercel: false,
68 |
69 | ukraine: false,
70 | });
71 |
--------------------------------------------------------------------------------
/src/components/apps/Calendar/calendar-utils.ts:
--------------------------------------------------------------------------------
1 | import { getDay, getMonth, getYear, startOfMonth } from 'date-fns';
2 | import { DAYS, DAYS_LEAP, NUMBER_OF_CELLS_IN_CALENDAR } from './calendar-constants';
3 |
4 | /**
5 | * Check if the year is a leap year
6 | * @param year
7 | */
8 | export function isLeapYear(year: number) {
9 | return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
10 | }
11 |
12 | /**
13 | * Get an array of whole number integers in the range (lower, upper]
14 | * @param lower lower bound, exclusive
15 | * @param upper upper bound, inclusive
16 | */
17 | export function getRangeArray(lower: number, upper: number) {
18 | const arr: number[] = [];
19 | for (let i = lower + 1; i <= upper; i++) {
20 | arr.push(i);
21 | }
22 | return arr;
23 | }
24 |
25 | /**
26 | * Get the display days in 3 parts:
27 | * Ones belong to previous month,
28 | * Ones belong to this month, and
29 | * Ones belong to next month.
30 | * @param selectedDate the selected date which indicates the current month
31 | */
32 | export function getDisplayDays(selectedDate: Date) {
33 | const thisMonth = getMonth(selectedDate);
34 | const prevMonth = thisMonth - 1 < 0 ? 11 : thisMonth - 1;
35 |
36 | const days = isLeapYear(getYear(selectedDate)) ? DAYS_LEAP : DAYS;
37 | const weekday = getDay(startOfMonth(selectedDate));
38 |
39 | // If it's Sunday, weekday is 0
40 | const daysToShowInPrevMonth = weekday === 0 ? 6 : weekday - 1;
41 | const daysToShowInNextMonth =
42 | NUMBER_OF_CELLS_IN_CALENDAR - days[thisMonth] - daysToShowInPrevMonth;
43 |
44 | const daysInPrevMonth = getRangeArray(days[prevMonth] - daysToShowInPrevMonth, days[prevMonth]);
45 | const daysInThisMonth = getRangeArray(0, days[thisMonth]);
46 | const daysInNextMonth = getRangeArray(0, daysToShowInNextMonth);
47 |
48 | return {
49 | daysInPrevMonth,
50 | daysInThisMonth,
51 | daysInNextMonth,
52 | };
53 | }
54 |
--------------------------------------------------------------------------------
/src/configs/theme/colors.config.ts:
--------------------------------------------------------------------------------
1 | type SingleThemeConfig = {
2 | hsl: string;
3 | contrastHsl: string;
4 | };
5 |
6 | const colorsConfig = (
7 | et: Record,
8 | ) => et;
9 |
10 | export const colors = colorsConfig({
11 | orange: {
12 | light: {
13 | hsl: '35deg, 100%, 50%',
14 | contrastHsl: '240, 3%, 11%',
15 | },
16 | dark: {
17 | hsl: '36deg, 100%, 52%',
18 | contrastHsl: '240, 3%, 11%',
19 | },
20 | },
21 |
22 | green: {
23 | light: {
24 | hsl: '135deg, 59%, 49%',
25 | contrastHsl: '135deg, 60%, 4%',
26 | },
27 | dark: {
28 | hsl: '135deg, 64%, 50%',
29 | contrastHsl: '135deg, 60%, 4%',
30 | },
31 | },
32 |
33 | cyan: {
34 | light: {
35 | hsl: '199deg, 78%, 55%',
36 | contrastHsl: '199deg, 78%, 100%',
37 | },
38 | dark: {
39 | hsl: '197deg, 100%, 70%',
40 | contrastHsl: '197deg, 100%, 5%',
41 | },
42 | },
43 |
44 | blue: {
45 | light: {
46 | hsl: '211, 100%, 50%',
47 | contrastHsl: '240, 24%, 100%',
48 | },
49 | dark: {
50 | hsl: '210, 100%, 52%',
51 | contrastHsl: '210, 92%, 5%',
52 | },
53 | },
54 |
55 | indigo: {
56 | light: {
57 | hsl: '241deg, 61%, 59%',
58 | contrastHsl: '241deg, 61%, 98%',
59 | },
60 | dark: {
61 | hsl: '241deg, 73%, 63%',
62 | contrastHsl: '241deg, 73%, 5%',
63 | },
64 | },
65 |
66 | purple: {
67 | light: {
68 | hsl: '280deg, 68%, 60%',
69 | contrastHsl: '280deg, 68%, 98%',
70 | },
71 | dark: {
72 | hsl: '280deg, 85%, 65%',
73 | contrastHsl: '280deg, 85%, 5%',
74 | },
75 | },
76 |
77 | pink: {
78 | light: {
79 | hsl: '349deg, 100%, 59%',
80 | contrastHsl: '349deg, 100%, 95%',
81 | },
82 | dark: {
83 | hsl: '348deg, 100%, 61%',
84 | contrastHsl: '348deg, 100%, 5%',
85 | },
86 | },
87 | });
88 |
--------------------------------------------------------------------------------
/src/components/apps/AppStore/AppStore.svelte:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
25 |
26 |
31 |
32 |
33 |
34 |
35 | Nothing here yet
40 |
41 |
42 |
43 |
44 |
79 |
--------------------------------------------------------------------------------
/public/assets/cursors/vertical-resize.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
24 |
25 |
--------------------------------------------------------------------------------
/src/css/global.scss:
--------------------------------------------------------------------------------
1 | @import './reset';
2 | @import './theme';
3 |
4 | html,
5 | body {
6 | height: 100%;
7 | }
8 |
9 | body {
10 | cursor: var(--system-cursor-default), auto;
11 |
12 | font-family: var(--system-font-family);
13 |
14 | overflow: hidden;
15 |
16 | -webkit-font-smoothing: antialiased;
17 | -moz-osx-font-smoothing: grayscale;
18 | }
19 |
20 | * {
21 | box-sizing: border-box;
22 |
23 | transition: background-color 150ms ease-in, background 150ms ease-in;
24 | }
25 |
26 | *:focus {
27 | outline: none;
28 | }
29 |
30 | *:focus-visible {
31 | outline: none;
32 |
33 | box-shadow: var(--system-focus-outline);
34 | }
35 |
36 | #root {
37 | width: 100%;
38 | height: 100%;
39 |
40 | isolation: isolate;
41 | }
42 |
43 | button {
44 | color: inherit;
45 | text-decoration: none;
46 | vertical-align: middle;
47 | font-family: inherit;
48 |
49 | border: 0;
50 | border-radius: 0;
51 |
52 | outline: 0;
53 |
54 | margin: 0;
55 | padding: 0;
56 |
57 | display: inline-flex;
58 | align-items: center;
59 | justify-content: center;
60 |
61 | position: relative;
62 |
63 | user-select: none;
64 | appearance: none;
65 | cursor: var(--system-cursor-default), auto;
66 |
67 | background-color: transparent;
68 |
69 | -webkit-tap-highlight-color: transparent;
70 | }
71 |
72 | * {
73 | cursor: var(--system-cursor-default), auto;
74 | }
75 |
76 |
77 | /* Scrollbar Stuffs */
78 | ::-webkit-scrollbar {
79 | width: 5px;
80 | }
81 |
82 | /* Track */
83 | ::-webkit-scrollbar-track {
84 | background-color: transparent;
85 | border-radius: 0.75rem;
86 | width: 5px;
87 | transition-duration: 0.4s;
88 | }
89 |
90 | /* Handle */
91 | ::-webkit-scrollbar-thumb {
92 | background-color: #888;
93 | border-radius: 0.75rem;
94 | width: 5px;
95 | transition-duration: 0.4s;
96 | }
97 |
98 | /* Handle on hover */
99 | ::-webkit-scrollbar-thumb:hover {
100 | background-color: #666;
101 | }
102 |
103 | /* Handle on clicked */
104 | ::-webkit-scrollbar-thumb:active {
105 | background-color: #444;
106 | }
107 |
108 |
--------------------------------------------------------------------------------
/src/components/TopBar/Menu.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 | {#each Object.entries(menu) as [, val]}
9 |
10 | {#if val.breakAfter}
11 |
12 | {/if}
13 | {/each}
14 |
15 |
16 |
89 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | macOS in Svelte
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
50 |
51 |
--------------------------------------------------------------------------------
/public/assets/cursors/diagonal-resize-2.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
26 |
27 |
--------------------------------------------------------------------------------
/public/assets/cursors/diagonal-resize-1.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
26 |
27 |
--------------------------------------------------------------------------------
/src/components/Desktop/Window/TrafficLights.svelte:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
97 |
--------------------------------------------------------------------------------
/src/configs/apps/apps-config.ts:
--------------------------------------------------------------------------------
1 | import { createAppConfig } from '🍎/helpers/create-app-config';
2 |
3 | const wallpapers = createAppConfig({
4 | title: 'Wallpapers',
5 | resizable: true,
6 |
7 | height: 600,
8 | width: 800,
9 |
10 | dockBreaksBefore: true,
11 | });
12 |
13 | const calculator = createAppConfig({
14 | title: 'Calculator',
15 |
16 | expandable: true,
17 | resizable: false,
18 |
19 | height: 300 * 1.414,
20 | width: 300,
21 | });
22 |
23 | const calendar = createAppConfig({
24 | title: 'Calendar',
25 | resizable: true,
26 | });
27 |
28 | const vscode = createAppConfig({
29 | title: 'VSCode',
30 | resizable: true,
31 |
32 | height: 600,
33 | width: 800,
34 | });
35 |
36 | const finder = createAppConfig({
37 | title: 'Finder',
38 | resizable: true,
39 |
40 | // dockBreaksBefore: true,
41 | shouldOpenWindow: false,
42 | });
43 |
44 | const safari = createAppConfig({
45 | title: 'Safari',
46 | resizable: true,
47 | });
48 |
49 | const systemPreferences = createAppConfig({
50 | title: 'System Preferences',
51 | resizable: true,
52 | });
53 |
54 | const purusTwitter = createAppConfig({
55 | title: `About the Developer`,
56 | resizable: true,
57 |
58 | dockBreaksBefore: true,
59 |
60 | height: 600,
61 | width: 800,
62 | });
63 |
64 | const viewSource = createAppConfig({
65 | title: `View Source`,
66 | resizable: true,
67 |
68 | shouldOpenWindow: false,
69 | externalAction: () => window.open('https://github.com/puruvj/macos-web', '_blank'),
70 | });
71 |
72 | const ukraine = createAppConfig({
73 | title: `Support Ukraine`,
74 | resizable: true,
75 |
76 | shouldOpenWindow: false,
77 | externalAction: () => window.open('https://www.stopputin.net/', '_blank'),
78 |
79 | dockBreaksBefore: true,
80 | });
81 |
82 | const vercel = createAppConfig({
83 | title: `Powered by Vercel`,
84 | resizable: true,
85 |
86 | shouldOpenWindow: false,
87 | externalAction: () =>
88 | window.open('https://vercel.com/?utm_source=purus-projects&utm_campaign=oss', '_blank'),
89 |
90 | dockBreaksBefore: true,
91 | });
92 |
93 | const appstore = createAppConfig({
94 | title: 'App Store',
95 | resizable: true,
96 | });
97 |
98 | export const appsConfig = {
99 | finder,
100 | wallpapers,
101 | calculator,
102 | calendar,
103 | vscode,
104 | appstore,
105 | // safari,
106 |
107 | // 'system-preferences': systemPreferences,
108 |
109 | 'purus-twitter': purusTwitter,
110 | 'view-source': viewSource,
111 |
112 | vercel,
113 |
114 | ukraine,
115 | };
116 |
--------------------------------------------------------------------------------
/src/components/Desktop/BootupScreen.svelte:
--------------------------------------------------------------------------------
1 |
19 |
20 | {#if !(hiddenSplashScreen || import.meta.env.DEV)}
21 |
35 | {/if}
36 |
37 |
38 | {#if import.meta.env.PROD}
39 |
46 | {/if}
47 |
48 |
104 |
--------------------------------------------------------------------------------
/public/assets/cursors/unavailable.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
32 |
33 |
--------------------------------------------------------------------------------
/public/assets/cursors/move.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
34 |
--------------------------------------------------------------------------------
/public/assets/cursors/pirate.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
34 |
--------------------------------------------------------------------------------
/src/components/SystemUI/SystemDialog.svelte:
--------------------------------------------------------------------------------
1 |
38 |
39 | {#if isOpen}
40 |
41 | backdropDismiss && close() }}
52 | on:click|stopPropagation={() => {}}
53 | >
54 |
55 |
56 |
57 | {/if}
58 |
59 |
94 |
--------------------------------------------------------------------------------
/src/components/TopBar/MenuBar.svelte:
--------------------------------------------------------------------------------
1 |
7 |
8 | ($activeMenu = '') }}
11 | use:focusOutside={{ callback: () => ($activeMenu = '') }}
12 | >
13 | {#each Object.entries($menuBarMenus) as [menuID, menuConfig]}
14 |
15 |
16 |
31 |
32 |
33 |
40 |
41 | {/each}
42 |
43 |
44 |
107 |
--------------------------------------------------------------------------------
/src/components/TopBar/TopBar.svelte:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | {#if $shouldShowNotch}
16 |
17 |
18 |
19 | {/if}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
128 |
--------------------------------------------------------------------------------
/src/components/Dock/Dock.svelte:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 | (mouseX = event.x)}
20 | on:mouseleave={() => (mouseX = null)}
21 | >
22 | {#each Object.entries(appsConfig) as [appID, config]}
23 | {#if config.dockBreaksBefore}
24 |
25 | {/if}
26 |
27 | {/each}
28 |
29 |
30 |
31 |
120 |
--------------------------------------------------------------------------------
/src/css/theme.scss:
--------------------------------------------------------------------------------
1 | body,
2 | body[data-theme='light'] {
3 | /* // Primary */
4 | --system-color-primary: hsl(211, 100%, 50%);
5 | --system-color-primary-hsl: 211, 100%, 50%;
6 | --system-color-primary-contrast: hsl(240, 24%, 100%);
7 | --system-color-primary-contrast-hsl: 240, 24%, 100%;
8 |
9 | /* // Dark */
10 | --system-color-dark: hsl(240, 3%, 11%);
11 | --system-color-dark-hsl: 240, 3%, 11%;
12 | --system-color-dark-contrast: hsl(240, 24%, 100%);
13 | --system-color-dark-contrast-hsl: 240, 24%, 100%;
14 |
15 | /* // Light */
16 | --system-color-light: hsl(240, 24%, 100%);
17 | --system-color-light-hsl: 240, 24%, 100%;
18 | --system-color-light-contrast: hsl(0, 0%, 11%);
19 | --system-color-light-contrast-hsl: 0, 0%, 11%;
20 |
21 | --system-font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'Helvetica Neue', 'Helvetica',
22 | 'Arial', sans-serif;
23 |
24 | --system-focus-outline: 0 0 0 3px hsla(var(--system-color-primary-hsl), 0.5);
25 |
26 | --system-color-grey-50: #fafafa;
27 | --system-color-grey-50-hsl: 0, 0%, 98%;
28 |
29 | --system-color-grey-100: #f5f5f5;
30 | --system-color-grey-100-hsl: 0, 0%, 96%;
31 |
32 | --system-color-grey-200: #eeeeee;
33 | --system-color-grey-200-hsl: 0, 0%, 93%;
34 |
35 | --system-color-grey-300: #e0e0e0;
36 | --system-color-grey-300-hsl: 0, 0%, 88%;
37 |
38 | --system-color-grey-400: #bdbdbd;
39 | --system-color-grey-400-hsl: 0, 0%, 74%;
40 |
41 | --system-color-grey-500: #9e9e9e;
42 | --system-color-grey-500-hsl: 0, 0%, 62%;
43 |
44 | --system-color-grey-600: #757575;
45 | --system-color-grey-600-hsl: 0, 0%, 46%;
46 |
47 | --system-color-grey-700: #616161;
48 | --system-color-grey-700-hsl: 0, 0%, 38%;
49 |
50 | --system-color-grey-800: #424242;
51 | --system-color-grey-800-hsl: 0, 0%, 26%;
52 |
53 | --system-color-grey-900: #212121;
54 | --system-color-grey-900-hsl: 0, 0%, 13%;
55 |
56 | --system-color-grey-A100: #d5d5d5;
57 | --system-color-grey-A100-hsl: 0, 0%, 84%;
58 |
59 | --system-color-grey-A200: #aaa;
60 | --system-color-grey-A200-hsl: 0, 0%, 67%;
61 |
62 | --system-color-grey-A400: #303030;
63 | --system-color-grey-A400-hsl: 0, 0%, 19%;
64 |
65 | --system-color-grey-A700: #616161;
66 | --system-color-grey-A700-hsl: 0, 0%, 38%;
67 |
68 | // Cursors
69 | --system-cursor-default: url('/assets/cursors/normal-select.svg');
70 | --system-cursor-pointer: url('/assets/cursors/link-select.svg');
71 | --system-cursor-text-select: url('/assets/cursors/text-select.svg');
72 | --system-cursor-help-select: url('/assets/cursors/help-select.svg');
73 |
74 | // the following two cursors are animated, but will be rendered as a static
75 | // image by the browser
76 | --system-cursor-busy: url('/assets/cursors/busy.webp');
77 | --system-cursor-working-in-bg: url('/assets/cursors/working-in-background.webp');
78 | }
79 |
80 | body.dark {
81 | /* // Primary */
82 | --system-color-primary: #0a85ff;
83 | --system-color-primary-hsl: 210, 100%, 52%;
84 | --system-color-primary-contrast: hsl(210, 92%, 5%);
85 | --system-color-primary-contrast-hsl: 210, 92%, 5%;
86 |
87 | /* // Dark */
88 | --system-color-dark: hsl(240, 24%, 100%);
89 | --system-color-dark-hsl: 240, 24%, 100%;
90 | --system-color-dark-contrast: hsl(0, 0%, 11%);
91 | --system-color-dark-contrast-hsl: 0, 0%, 11%;
92 |
93 | /* // Light */
94 | --system-color-light: hsl(240, 3%, 11%);
95 | --system-color-light-hsl: 240, 3%, 11%;
96 | --system-color-light-contrast: hsl(240, 24%, 100%);
97 | --system-color-light-contrast-hsl: 240, 24%, 100%;
98 | }
99 |
--------------------------------------------------------------------------------
/src/components/apps/Calculator/Calculator.svelte:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
49 |
50 |
51 |
127 |
--------------------------------------------------------------------------------
/src/components/Desktop/ContextMenu.svelte:
--------------------------------------------------------------------------------
1 |
33 |
34 |
35 |
36 | {#if isMenuVisible}
37 |
44 | {#each Object.values(contextMenuConfig.default) as contents}
45 |
46 |
47 | {#if contents.breakAfter}
48 |
49 | {/if}
50 | {/each}
51 |
52 | {/if}
53 |
54 |
146 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { svelte } from '@sveltejs/vite-plugin-svelte';
2 | import UnpluginIcons from 'unplugin-icons/vite';
3 | import { defineConfig } from 'vite';
4 | import { VitePWA } from 'vite-plugin-pwa';
5 | import { prefetch } from './prefetch-plugin';
6 |
7 | export default defineConfig({
8 | plugins: [
9 | svelte(),
10 | prefetch(),
11 | // replace({ ...replacePlugin() }),
12 | UnpluginIcons({ autoInstall: true, compiler: 'svelte' }),
13 | VitePWA({
14 | includeAssets: [
15 | 'robots.txt',
16 | 'assets/app-icons/finder/32.png',
17 | 'assets/cover-image.png',
18 | 'assets/cursors/(normal|link|text|help)-select.svg',
19 | 'assets/**/*.mp3',
20 | 'assets/**/*.webp',
21 | 'assets/wallpapers/37-[12].jpg',
22 | ],
23 | manifest: {
24 | name: 'Mac OS Monterey Svelte Web',
25 | short_name: 'macOS Svelte',
26 | theme_color: '#ffffff',
27 | description: 'Mac OS Monterey Web written in Svelte',
28 | icons: [
29 | {
30 | src: 'assets/app-icons/finder/128.png',
31 | sizes: '128x128',
32 | type: 'image/png',
33 | },
34 | {
35 | src: 'assets/app-icons/finder/192.png',
36 | sizes: '192x192',
37 | type: 'image/png',
38 | },
39 | {
40 | src: 'assets/app-icons/finder/256.png',
41 | sizes: '256x256',
42 | type: 'image/png',
43 | },
44 | {
45 | src: 'assets/app-icons/finder/512.png',
46 | sizes: '512x512',
47 | type: 'image/png',
48 | },
49 | {
50 | src: 'assets/app-icons/finder/512.png',
51 | sizes: '512x512',
52 | type: 'image/png',
53 | purpose: 'any maskable',
54 | },
55 | ],
56 | },
57 | workbox: {
58 | runtimeCaching: [
59 | {
60 | urlPattern: /^https:\/\/fonts\.googleapis\.com\/.*/i,
61 | handler: 'CacheFirst',
62 | options: {
63 | cacheName: 'google-fonts-cache',
64 | expiration: {
65 | maxEntries: 10,
66 | maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
67 | },
68 | cacheableResponse: {
69 | statuses: [0, 200],
70 | },
71 | },
72 | },
73 | {
74 | urlPattern: /^https:\/\/fonts\.gstatic\.com\/.*/i,
75 | handler: 'CacheFirst',
76 | options: {
77 | cacheName: 'gstatic-fonts-cache',
78 | expiration: {
79 | maxEntries: 10,
80 | maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
81 | },
82 | cacheableResponse: {
83 | statuses: [0, 200],
84 | },
85 | },
86 | },
87 | {
88 | urlPattern: /^https:\/\/github1s.com\/.*/i,
89 | handler: 'CacheFirst',
90 | options: {
91 | cacheName: 'github1s-api-cache',
92 | expiration: {
93 | maxEntries: 10,
94 | maxAgeSeconds: 60 * 60 * 24 * 365, // <== 365 days
95 | },
96 | cacheableResponse: {
97 | statuses: [0, 200],
98 | },
99 | },
100 | },
101 | ],
102 | },
103 | }),
104 | ],
105 | resolve: {
106 | alias: {
107 | '🍎': new URL('./src/', import.meta.url).pathname,
108 | },
109 | },
110 | build: {
111 | minify: 'terser',
112 | },
113 | });
114 |
--------------------------------------------------------------------------------
/src/components/Desktop/SystemUpdate.svelte:
--------------------------------------------------------------------------------
1 |
45 |
46 |
47 |
48 |
55 |
56 | Updates Available
57 | Do you want to restart to install these updates now?
58 |
59 |
60 | Later
61 | Update
62 |
63 |
64 |
65 |
66 | {buildDate}
67 |
68 |
136 |
--------------------------------------------------------------------------------
/src/components/apps/Calendar/MonthView.svelte:
--------------------------------------------------------------------------------
1 |
26 |
27 |
28 | {#each DAYS_OF_THE_WEEK as day, i}
29 |
{day}
30 | {/each}
31 |
32 | {#each daysInPrevMonth as date (dayKey(date))}
33 |
36 | {/each}
37 |
38 | {#each daysInThisMonth as date (dayKey(date))}
39 |
42 | {/each}
43 |
44 | {#each daysInNextMonth as date (dayKey(date))}
45 |
48 | {/each}
49 |
50 |
51 |
147 |
--------------------------------------------------------------------------------
/src/components/apps/Calendar/Calendar.svelte:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
27 |
28 |
29 |
45 |
46 | {#if view === 'year'}
47 |
48 | {:else if view === 'month'}
49 |
50 | {:else if view === 'week'}
51 |
52 | {:else}
53 |
54 | {/if}
55 |
56 |
57 |
58 |
149 |
--------------------------------------------------------------------------------
/src/components/apps/WallpaperApp/Wallpaper.svelte:
--------------------------------------------------------------------------------
1 |
82 |
83 |
84 |
85 | {#each Object.values(wallpapersConfig) as { thumbnail }}
86 |
87 | {/each}
88 |
89 |
90 |
91 |
97 |
98 |
103 |
104 |
131 |
--------------------------------------------------------------------------------
/src/components/TopBar/ActionCenterToggle.svelte:
--------------------------------------------------------------------------------
1 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {#if visible}
31 |
34 | {/if}
35 |
36 |
37 | (isThemeWarningDialogOpen = false)}>
38 |
39 |
45 |
46 | Current Wallpaper Settings prevent changing theme
47 | Head over to Wallpapers app to change this setting or choose a standalone wallpaper.
48 |
49 |
50 | themeWarningDialog.close()}>Close
51 | {
54 | themeWarningDialog.close();
55 |
56 | $openApps.wallpapers = true;
57 | $activeApp = 'wallpapers';
58 | }}
59 | >
60 | Go to Wallpapers
61 |
62 |
63 |
64 |
65 |
66 |
176 |
--------------------------------------------------------------------------------
/src/configs/wallpapers/wallpaper.config.ts:
--------------------------------------------------------------------------------
1 | import type { Theme } from '🍎/stores/theme.store';
2 |
3 | export type Wallpaper = {
4 | name: string;
5 | type: 'standalone' | 'automatic' | 'dynamic';
6 |
7 | thumbnail: string;
8 |
9 | /** Timestamps definition in terms of when a new wallpaper should take effect */
10 | timestamps?: {
11 | wallpaper?: Record;
12 | theme?: Record;
13 | };
14 | };
15 |
16 | const createWallpapersConfig = (et: Record) => et;
17 |
18 | export const wallpapersConfig = createWallpapersConfig({
19 | monterey: {
20 | name: 'Monterey',
21 | type: 'dynamic',
22 | thumbnail: '37-2',
23 | timestamps: {
24 | wallpaper: {
25 | 7: '37-2',
26 | 9: '37-3',
27 | 11: '37-4',
28 | 13: '37-5',
29 | 15: '37-6',
30 | 16: '37-7',
31 | 17: '37-8',
32 | 18: '37-1',
33 | },
34 | theme: {
35 | 7: 'light',
36 | 18: 'dark',
37 | },
38 | },
39 | },
40 |
41 | 'big-sur-graphic': {
42 | name: 'Big Sur Graphic',
43 | type: 'dynamic',
44 | thumbnail: '3-2',
45 | timestamps: {
46 | wallpaper: {
47 | 7: '3-2',
48 | 18: '3-1',
49 | },
50 | theme: {
51 | 7: 'light',
52 | 18: 'dark',
53 | },
54 | },
55 | },
56 |
57 | 'big-sur': {
58 | name: 'Monterey',
59 | type: 'dynamic',
60 | thumbnail: '12-4',
61 | timestamps: {
62 | wallpaper: {
63 | 7: '12-2',
64 | 9: '12-3',
65 | 11: '12-4',
66 | 13: '12-5',
67 | 15: '12-6',
68 | 16: '12-7',
69 | 17: '12-8',
70 | 18: '12-1',
71 | },
72 | theme: {
73 | 7: 'light',
74 | 18: 'dark',
75 | },
76 | },
77 | },
78 |
79 | catalina: {
80 | name: 'Catalina',
81 | type: 'dynamic',
82 | thumbnail: '24-3',
83 | timestamps: {
84 | wallpaper: {
85 | 7: '24-2',
86 | 9: '24-3',
87 | 11: '24-4',
88 | 13: '24-5',
89 | 15: '24-6',
90 | 16: '24-7',
91 | 17: '24-8',
92 | 18: '24-1',
93 | },
94 | theme: {
95 | 9: 'light',
96 | 17: 'dark',
97 | },
98 | },
99 | },
100 |
101 | 'kryptonian-demise': {
102 | name: 'Kryptonian Demise',
103 | type: 'standalone',
104 | thumbnail: '38',
105 | },
106 |
107 | 'nahargarh-sunset': {
108 | name: 'Nahargarh Sunset',
109 | type: 'standalone',
110 | thumbnail: '39',
111 | },
112 |
113 | 'somber-forest': {
114 | name: 'Somber Forest',
115 | type: 'standalone',
116 | thumbnail: '40',
117 | },
118 |
119 | 'blade-runner-2149': {
120 | name: 'Blade Runner 2149',
121 | type: 'standalone',
122 | thumbnail: '41',
123 | },
124 |
125 | 'lone-dune-wolf': {
126 | name: 'Lone Dune Wolf',
127 | type: 'standalone',
128 | thumbnail: '42',
129 | },
130 |
131 | 'childhood-innocence': {
132 | name: 'Childhood Innocence',
133 | type: 'standalone',
134 | thumbnail: '43',
135 | },
136 |
137 | 'fox-in-somber-forest': {
138 | name: 'Fox in Somber Forest',
139 | type: 'standalone',
140 | thumbnail: '44',
141 | },
142 |
143 | 'blood-diamond': {
144 | name: 'Blood Diamond',
145 | type: 'standalone',
146 | thumbnail: '45',
147 | },
148 |
149 | 'black-bird-in-a-city': {
150 | name: 'Black Bird in a City',
151 | type: 'standalone',
152 | thumbnail: '46',
153 | },
154 |
155 | 'sunrise-of-dreams': {
156 | name: 'Sunrise of Dreams',
157 | type: 'standalone',
158 | thumbnail: '47',
159 | },
160 |
161 | 'how-do-we-get-down': {
162 | name: 'How do we get down?',
163 | type: 'standalone',
164 | thumbnail: '48',
165 | },
166 |
167 | 'cozy-night-with-cat': {
168 | name: 'Cozy Night with Cat',
169 | type: 'standalone',
170 | thumbnail: '49',
171 | },
172 |
173 | 'age-of-titans': {
174 | name: 'Age of Titans',
175 | type: 'standalone',
176 | thumbnail: '50',
177 | },
178 |
179 | dune: {
180 | name: 'Dune',
181 | type: 'standalone',
182 | thumbnail: '51',
183 | },
184 |
185 | 'vibrant-night': {
186 | name: 'Vibrant Night',
187 | type: 'standalone',
188 | thumbnail: '52',
189 | },
190 |
191 | 'cabin-in-woods': {
192 | name: 'Cabin in the Woods',
193 | type: 'standalone',
194 | thumbnail: '53',
195 | },
196 |
197 | 'asgardian-sunrise': {
198 | name: 'Asgardian Sunrise',
199 | type: 'standalone',
200 | thumbnail: '54',
201 | },
202 |
203 | 'asura-lok': {
204 | name: 'Asura Lok',
205 | type: 'standalone',
206 | thumbnail: '55',
207 | },
208 |
209 | 'my-neighbour-totoro': {
210 | name: 'My Neighbour Totoro',
211 | type: 'standalone',
212 | thumbnail: '56',
213 | },
214 |
215 | tron: {
216 | name: 'Tron',
217 | type: 'standalone',
218 | thumbnail: '57',
219 | },
220 | });
221 |
222 | export type WallpaperID = keyof typeof wallpapersConfig;
223 |
--------------------------------------------------------------------------------
/src/components/Desktop/Window/Window.svelte:
--------------------------------------------------------------------------------
1 |
110 |
111 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
183 |
--------------------------------------------------------------------------------
/src/components/apps/PurusProfile/PurusProfile.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 | About the Developer
18 |
19 |
20 |
35 |
36 |
37 |
43 |
44 |
45 |
46 | Hi, I'm Puru
47 |
48 |
49 | I'm the creator of macOS Web, which you're on right now
50 |
51 |
52 |
53 |
54 |
55 |
56 | I am a fullstack web developer, with an infinite amount of love for frontend web development,
57 | esp JavaScript, TypeScript, and for frontend frameworks like Svelte, Vue and React
58 |
59 |
60 |
61 |
62 |
63 |
64 | However, my love for tech doesn't end there. I enjoy writing backend APIs, scripts, working
65 | with databases, and my fav platforms are NodeJS, Deno and Go
66 |
67 |
68 |
69 |
70 |
230 |
--------------------------------------------------------------------------------