website-xp-phone 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/.astro/content-assets.mjs +1 -0
  2. package/.astro/content-modules.mjs +1 -0
  3. package/.astro/content.d.ts +199 -0
  4. package/.astro/data-store.json +1 -0
  5. package/.astro/settings.json +8 -0
  6. package/.astro/types.d.ts +1 -0
  7. package/.devcontainer/devcontainer.json +23 -0
  8. package/.env.firebase.example +8 -0
  9. package/.firebaserc +5 -0
  10. package/.gitattributes +2 -0
  11. package/.github/copilot-instructions.md +131 -0
  12. package/.github/dependabot.yml +11 -0
  13. package/.github/workflows/ci.yml +45 -0
  14. package/.github/workflows/deploy-admin.yml +48 -0
  15. package/.github/workflows/static.yml +43 -0
  16. package/.gitmodules +5 -0
  17. package/FIREBASE_SETUP.md +69 -0
  18. package/README.md +63 -0
  19. package/SECURITY.md +11 -0
  20. package/admin/Admin.csproj +7 -0
  21. package/admin/Dockerfile +14 -0
  22. package/admin/Program.cs +8 -0
  23. package/deploy-admin-cloud-run.md +229 -0
  24. package/eslint.config.js +28 -0
  25. package/firebase.json +5 -0
  26. package/firestore.rules +29 -0
  27. package/index.html +52 -0
  28. package/package.json +48 -0
  29. package/pagerts_output.json +1 -0
  30. package/public/5.html +967 -0
  31. package/public/BAHNSCHRIFT.TTF +0 -0
  32. package/public/Beep.ogg +0 -0
  33. package/public/Clippy.png +0 -0
  34. package/public/Layered Network Security Model for Home Networks (slides).pdf +0 -0
  35. package/public/Layered Network Security Model for Home Networks.pdf +0 -0
  36. package/public/TODO.pdf +0 -0
  37. package/public/WoW_Config.zip +3 -0
  38. package/public/addons/energy-swing.txt +1 -0
  39. package/public/addons/lego-yoda-death-readme.txt +11 -0
  40. package/public/addons/lego-yoda-death.mp3 +0 -0
  41. package/public/addons/mana-blast.txt +1 -0
  42. package/public/addons/rage-volley.txt +1 -0
  43. package/public/addons/rueg-cell.txt +1 -0
  44. package/public/addons/rueg-elvui-profile.txt +1 -0
  45. package/public/addons/rueg-grid2.txt +214 -0
  46. package/public/addons/rueg-plater-smol.txt +1 -0
  47. package/public/addons/rueg-plater.txt +1 -0
  48. package/public/addons/rueg-wa-druid.txt +1 -0
  49. package/public/addons/rueg-wa-priest.txt +1 -0
  50. package/public/addons/rueg-wa-rogue.txt +1 -0
  51. package/public/addons/rueg-wa-shaman.txt +1 -0
  52. package/public/addons/rueg-wa-warrior.txt +1 -0
  53. package/public/addons/spirit-smash.txt +1 -0
  54. package/public/avatar.jpg +0 -0
  55. package/public/avatar.png +0 -0
  56. package/public/crunchy_kick.ogg +0 -0
  57. package/public/documents/resume.html +312 -0
  58. package/public/favicon.ico +0 -0
  59. package/public/images/Ateric1.png +0 -0
  60. package/public/images/Ateric2.png +0 -0
  61. package/public/images/equal1.png +0 -0
  62. package/public/images/hyperawareofwhatacatis.png +0 -0
  63. package/public/images/kogg1.png +0 -0
  64. package/public/images/kogg2.png +0 -0
  65. package/public/images/rueg1.png +0 -0
  66. package/public/images/rueg2.png +0 -0
  67. package/public/incorrect_responses.txt +126 -0
  68. package/public/loading.css +51 -0
  69. package/public/resume.pdf +0 -0
  70. package/public/robots.txt +9 -0
  71. package/public/soundcloud.json +57 -0
  72. package/public/spinner.svg +12 -0
  73. package/public/tada.wav +0 -0
  74. package/public/yooh.mp3 +0 -0
  75. package/render.yaml +5 -0
  76. package/scripts/ensure-blog-worktree.mjs +24 -0
  77. package/scripts/generate-soundcloud-json.mjs +198 -0
  78. package/scripts/git-worktree-helper.mjs +122 -0
  79. package/scripts/hoist-dev-blog-local.mjs +149 -0
  80. package/scripts/music-schema.mjs +56 -0
  81. package/scripts/publish-soundcloud-json.mjs +32 -0
  82. package/scripts/sync-music-links-from-worktree.mjs +32 -0
  83. package/src/App.tsx +1500 -0
  84. package/src/addons.json +76 -0
  85. package/src/components/Addon.tsx +223 -0
  86. package/src/components/BlogContent.tsx +103 -0
  87. package/src/components/CopyToClipboardButton.tsx +21 -0
  88. package/src/components/MenuBar.tsx +151 -0
  89. package/src/components/MenuBarWithContext.tsx +6 -0
  90. package/src/components/Modal.tsx +17 -0
  91. package/src/components/MusicContent.tsx +309 -0
  92. package/src/components/NavBarController.tsx +55 -0
  93. package/src/components/NavBarControllerWrapper.tsx +13 -0
  94. package/src/components/Page.tsx +56 -0
  95. package/src/components/SitemapContent.tsx +125 -0
  96. package/src/contacts.json +32 -0
  97. package/src/env.d.ts +13 -0
  98. package/src/lib/assistantStateMachine.ts +80 -0
  99. package/src/lib/audioOverlap.ts +99 -0
  100. package/src/lib/keyboardInputUtils.ts +182 -0
  101. package/src/lib/musicSchema.ts +85 -0
  102. package/src/lib/naggingAssistantClient.ts +241 -0
  103. package/src/lib/resumeAnalytics.ts +163 -0
  104. package/src/main.tsx +35 -0
  105. package/src/pages.json +50 -0
  106. package/src/sections.json +243 -0
  107. package/src/src+addons.zip +3 -0
  108. package/src/styles/main.css +465 -0
  109. package/src/utils/blogSecurity.ts +87 -0
  110. package/src/utils/menuItems.ts +33 -0
  111. package/src/windowing/MinimizedSections.tsx +86 -0
  112. package/src/windowing/Section.tsx +586 -0
  113. package/src/windowing/context.tsx +13 -0
  114. package/src/windowing/hooks.ts +10 -0
  115. package/src/windowing/index.ts +7 -0
  116. package/src/windowing/provider.tsx +74 -0
  117. package/src/windowing/server.ts +3 -0
  118. package/src/windowing/types.ts +33 -0
  119. package/src/windowing/utils.ts +135 -0
  120. package/tests/generate-soundcloud-json.test.mjs +63 -0
  121. package/tests/music-schema.test.mjs +53 -0
  122. package/tsconfig.json +26 -0
  123. package/vite.config.ts +304 -0
@@ -0,0 +1,163 @@
1
+ import { getApps, initializeApp, type FirebaseApp } from "firebase/app";
2
+ import { getAnalytics, isSupported, logEvent, type Analytics } from "firebase/analytics";
3
+ import {
4
+ addDoc,
5
+ collection,
6
+ getFirestore,
7
+ serverTimestamp,
8
+ type Firestore,
9
+ } from "firebase/firestore";
10
+
11
+ type EventParam = string | number | boolean;
12
+
13
+ type ResumeInterestPayload = {
14
+ email: string;
15
+ source: "resume_page";
16
+ userAgent: string;
17
+ createdAt: ReturnType<typeof serverTimestamp>;
18
+ };
19
+
20
+ const getRequiredValue = (value: string | undefined) => {
21
+ const trimmed = value?.trim();
22
+ return trimmed ? trimmed : null;
23
+ };
24
+
25
+ const getFirebaseApp = (): FirebaseApp | null => {
26
+ const apiKey = getRequiredValue(import.meta.env.VITE_FIREBASE_API_KEY);
27
+ const authDomain = getRequiredValue(import.meta.env.VITE_FIREBASE_AUTH_DOMAIN);
28
+ const projectId = getRequiredValue(import.meta.env.VITE_FIREBASE_PROJECT_ID);
29
+ const appId = getRequiredValue(import.meta.env.VITE_FIREBASE_APP_ID);
30
+
31
+ if (!apiKey || !authDomain || !projectId || !appId) {
32
+ return null;
33
+ }
34
+
35
+ const messagingSenderId = getRequiredValue(
36
+ import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
37
+ );
38
+ const storageBucket = getRequiredValue(import.meta.env.VITE_FIREBASE_STORAGE_BUCKET);
39
+ const measurementId = getRequiredValue(import.meta.env.VITE_FIREBASE_MEASUREMENT_ID);
40
+
41
+ const existingApp = getApps()[0];
42
+ if (existingApp) {
43
+ return existingApp;
44
+ }
45
+
46
+ return initializeApp({
47
+ apiKey,
48
+ authDomain,
49
+ projectId,
50
+ appId,
51
+ ...(messagingSenderId ? { messagingSenderId } : {}),
52
+ ...(storageBucket ? { storageBucket } : {}),
53
+ ...(measurementId ? { measurementId } : {}),
54
+ });
55
+ };
56
+
57
+ let analyticsPromise: Promise<Analytics | null> | null = null;
58
+ let firestoreClient: Firestore | null = null;
59
+
60
+ const RESUME_INTEREST_LAST_SUBMIT_KEY = "resumeInterestLastSubmitAt";
61
+ const MIN_SUBMIT_INTERVAL_MS = 30_000;
62
+
63
+ const getAnalyticsClient = async () => {
64
+ if (analyticsPromise) {
65
+ return analyticsPromise;
66
+ }
67
+
68
+ analyticsPromise = (async () => {
69
+ const app = getFirebaseApp();
70
+ if (!app) {
71
+ return null;
72
+ }
73
+
74
+ const analyticsSupported = await isSupported();
75
+ if (!analyticsSupported) {
76
+ return null;
77
+ }
78
+
79
+ return getAnalytics(app);
80
+ })();
81
+
82
+ return analyticsPromise;
83
+ };
84
+
85
+ const getFirestoreClient = () => {
86
+ if (firestoreClient) {
87
+ return firestoreClient;
88
+ }
89
+
90
+ const app = getFirebaseApp();
91
+ if (!app) {
92
+ return null;
93
+ }
94
+
95
+ firestoreClient = getFirestore(app);
96
+ return firestoreClient;
97
+ };
98
+
99
+ const isSubmitRateLimited = () => {
100
+ const lastSubmitAtRaw = window.localStorage.getItem(RESUME_INTEREST_LAST_SUBMIT_KEY);
101
+ if (!lastSubmitAtRaw) {
102
+ return false;
103
+ }
104
+
105
+ const lastSubmitAt = Number(lastSubmitAtRaw);
106
+ if (!Number.isFinite(lastSubmitAt)) {
107
+ return false;
108
+ }
109
+
110
+ return Date.now() - lastSubmitAt < MIN_SUBMIT_INTERVAL_MS;
111
+ };
112
+
113
+ export const trackResumeEvent = async (
114
+ eventName: string,
115
+ params: Record<string, EventParam> = {},
116
+ ) => {
117
+ try {
118
+ const analytics = await getAnalyticsClient();
119
+ if (!analytics) {
120
+ return;
121
+ }
122
+
123
+ logEvent(analytics, eventName, params);
124
+ } catch {
125
+ // Non-blocking analytics.
126
+ }
127
+ };
128
+
129
+ export const submitResumeInterest = async (email: string) => {
130
+ if (isSubmitRateLimited()) {
131
+ throw new Error("Please wait before submitting again.");
132
+ }
133
+
134
+ const firestore = getFirestoreClient();
135
+ if (!firestore) {
136
+ throw new Error("Missing Firebase configuration.");
137
+ }
138
+
139
+ const payload: ResumeInterestPayload = {
140
+ email: email.trim().toLowerCase(),
141
+ source: "resume_page",
142
+ userAgent: window.navigator.userAgent || "unknown",
143
+ createdAt: serverTimestamp(),
144
+ };
145
+
146
+ try {
147
+ await addDoc(collection(firestore, "resume_interest"), payload);
148
+ } catch {
149
+ await trackResumeEvent("resume_interest_submit_failed", {
150
+ status_code: 500,
151
+ });
152
+ throw new Error("Failed to submit resume interest.");
153
+ }
154
+
155
+ window.localStorage.setItem(
156
+ RESUME_INTEREST_LAST_SUBMIT_KEY,
157
+ String(Date.now()),
158
+ );
159
+
160
+ await trackResumeEvent("resume_interest_submit", {
161
+ status: "ok",
162
+ });
163
+ };
package/src/main.tsx ADDED
@@ -0,0 +1,35 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import "react-toastify/dist/ReactToastify.css";
4
+ import "./styles/main.css";
5
+ import App from "./App";
6
+
7
+ const checkStylesLoaded = () => {
8
+ const themeCssLink = document.getElementById("theme-css") as
9
+ | HTMLLinkElement
10
+ | null;
11
+ if (themeCssLink?.sheet) {
12
+ document.body.classList.add("styles-loaded");
13
+ return true;
14
+ }
15
+ return false;
16
+ };
17
+
18
+ if (!checkStylesLoaded()) {
19
+ const themeCssLink = document.getElementById("theme-css");
20
+ themeCssLink?.addEventListener("load", () => {
21
+ document.body.classList.add("styles-loaded");
22
+ });
23
+
24
+ window.setTimeout(() => {
25
+ if (!document.body.classList.contains("styles-loaded")) {
26
+ document.body.classList.add("styles-loaded");
27
+ }
28
+ }, 100);
29
+ }
30
+
31
+ createRoot(document.getElementById("root")!).render(
32
+ <StrictMode>
33
+ <App />
34
+ </StrictMode>,
35
+ );
package/src/pages.json ADDED
@@ -0,0 +1,50 @@
1
+ [
2
+ {
3
+ "path": "/",
4
+ "menuLabel": "home",
5
+ "title": "home of kine",
6
+ "description": "my cozy little personal website"
7
+ },
8
+ {
9
+ "path": "/addons",
10
+ "menuLabel": "addons",
11
+ "title": "ui of kine",
12
+ "description": "look at my UI in wow!"
13
+ },
14
+ {
15
+ "path": "/wow",
16
+ "menuLabel": "wow config",
17
+ "title": "WoW Config - kine",
18
+ "description": "Download World of Warcraft configuration files"
19
+ },
20
+ {
21
+ "path": "/blog",
22
+ "menuLabel": "blog",
23
+ "title": "blog of kine",
24
+ "description": "posts and notes from kine"
25
+ },
26
+ {
27
+ "path": "/music",
28
+ "menuLabel": "music",
29
+ "title": "glitchbox tracks",
30
+ "description": "my featured uploads from SoundCloud"
31
+ },
32
+ {
33
+ "path": "/sitemap",
34
+ "menuLabel": "sitemap",
35
+ "title": "sitemap of kine",
36
+ "description": "an immersive map of the site, assets, and crawlable surfaces"
37
+ },
38
+ {
39
+ "path": "/contact",
40
+ "menuLabel": "links",
41
+ "title": "try contact kine",
42
+ "description": "contact me!"
43
+ },
44
+ {
45
+ "path": "/resume",
46
+ "menuLabel": "resume",
47
+ "title": "please hire kine",
48
+ "description": "hire me!"
49
+ }
50
+ ]
@@ -0,0 +1,243 @@
1
+ {
2
+ "heading": "Welcome to kine's website!",
3
+ "content": [
4
+ {
5
+ "heading": "what is kine",
6
+ "content": [
7
+ "its my name",
8
+ {
9
+ "heading": "why is kine?",
10
+ "content": [
11
+ "because kine is short word",
12
+ "it is stable, and calm by",
13
+ "nature of its content"
14
+ ]
15
+ }
16
+ ]
17
+ },
18
+ {
19
+ "heading": "who is kine",
20
+ "content": [
21
+ {
22
+ "heading": "current occupation",
23
+ "content": "to graduate University of Sussex!"
24
+ },
25
+ {
26
+ "heading": "current hobbies",
27
+ "content": "gaming, programming, music"
28
+ },
29
+ {
30
+ "heading": "current status",
31
+ "content": "hapax proxima search"
32
+ }
33
+ ]
34
+ },
35
+ {
36
+ "heading": "what's kine busy with",
37
+ "content": [
38
+ "preparing to:",
39
+ {
40
+ "content": [
41
+ "graduate University of Sussex",
42
+ "apply for a very prestige job in the IT sector",
43
+ "meet new people",
44
+ "travel to new exotic places"
45
+ ]
46
+ },
47
+ "actively implementing:",
48
+ {
49
+ "content": [
50
+ "blogging platform",
51
+ "audio compression/filtering algorithms",
52
+ "python rapid development framework",
53
+ "fluent web app admin panel",
54
+ "bulletproof security"
55
+ ]
56
+ }
57
+ ]
58
+ },
59
+ {
60
+ "heading": "what's kine favourite games",
61
+ "content": [
62
+ {
63
+ "heading": "world of warcraft",
64
+ "content": [
65
+ {
66
+ "heading": "what is a Rueg",
67
+ "content": [
68
+ "playing on Thunderstrike as Rueg",
69
+ {
70
+ "heading": "why the name Rueg?",
71
+ "content": "rueg is a rogue skeleton"
72
+ },
73
+ {
74
+ "heading": "what is the appearance of Rueg?",
75
+ "content": [
76
+ "he is an unnamed skeleton of mystery",
77
+ "a short non-descript skeleton",
78
+ "with various head accessories",
79
+ "sometimes no eyes",
80
+ "sometimes no jaws",
81
+ {
82
+ "heading": "reveal rueg",
83
+ "content": [
84
+ "<img src=\"https://akinevz.com/images/rueg1.png\" alt=\"rueg image 1\" style=\"width: auto; height: auto; max-height: 600px;\" />"
85
+ ]
86
+ },
87
+ {
88
+ "heading": "reveal rueg",
89
+ "content": [
90
+ "<img src=\"https://akinevz.com/images/rueg2.png\" alt=\"rueg image 2\" style=\"width: auto; height: auto; max-height: 600px;\" />"
91
+ ]
92
+ }
93
+ ]
94
+ },
95
+ {
96
+ "heading": "where to find Rueg?",
97
+ "content": "anywhere, really"
98
+ }
99
+ ]
100
+ },
101
+ {
102
+ "heading": "who is Ateric",
103
+ "content": [
104
+ "playing on Thunderstrike as Ateric the Gnome",
105
+ {
106
+ "heading": "what class is Ateric?",
107
+ "content": "a Mage"
108
+ },
109
+ {
110
+ "heading": "will we ever see Ateric?",
111
+ "content": [
112
+ {
113
+ "heading": "reveal ateric",
114
+ "content": [
115
+ "<img src=\"https://akinevz.com/images/Ateric1.png\" alt=\"ateric image 1\" style=\"width: auto; height: auto; max-height: 600px;\" />"
116
+ ]
117
+ },
118
+ {
119
+ "heading": "reveal ateric",
120
+ "content": [
121
+ "<img src=\"https://akinevz.com/images/Ateric2.png\" alt=\"ateric image 2\" style=\"width: auto; height: auto; max-height: 600px;\" />"
122
+ ]
123
+ }
124
+ ]
125
+ }
126
+ ]
127
+ },
128
+ {
129
+ "heading": "who is Equal",
130
+ "content": [
131
+ "playing on Thunderstrike as Equal the Human",
132
+ {
133
+ "heading": "what class is Equal?",
134
+ "content": "a Priest"
135
+ },
136
+ {
137
+ "heading": "is she real?",
138
+ "content": [
139
+ "<img src=\"https://akinevz.com/images/equal1.png\" alt=\"equal dance\" style=\"width: auto; height: auto; max-height: 600px;\" />"
140
+ ]
141
+ }
142
+ ]
143
+ },
144
+ {
145
+ "heading": "who is Jungie",
146
+ "content": [
147
+ "playing on Spineshatter as Jungie the Troll",
148
+ {
149
+ "heading": "what class is Jungie?",
150
+ "content": [
151
+ "a Shaman",
152
+ "uhh... actually he's a Hunter, but there's also a Warrior..."
153
+ ]
154
+ }
155
+ ]
156
+ },
157
+ {
158
+ "heading": "who is Kogg",
159
+ "content": [
160
+ "playing on Thunderstrike as Kogg the Tauren",
161
+ {
162
+ "heading": "what class is Kogg?",
163
+ "content": "a Shaman"
164
+ },
165
+ {
166
+ "heading": "where does Kogg come from?",
167
+ "content": [
168
+ "born in Northrend, he was raised by the Taunka people",
169
+ "he was recruited into the Horde war effort by the Warsong"
170
+ ]
171
+ },
172
+ {
173
+ "heading": "does Kogg ever stand still?",
174
+ "content": [
175
+ "<img src=\"/images/kogg1.png\" alt=\"kogg image 1\" style=\"width: auto; height: auto; max-height: 600px;\" />",
176
+ "<img src=\"/images/kogg2.png\" alt=\"kogg image 2\" style=\"width: auto; height: auto; max-height: 600px;\" />"
177
+ ]
178
+ }
179
+ ]
180
+ }
181
+ ]
182
+ }
183
+ ]
184
+ },
185
+ {
186
+ "heading": "what's kine fascinations",
187
+ "content": [
188
+ {
189
+ "heading": "the concept of:",
190
+ "content": [
191
+ "programming and technology",
192
+ "and cats [\\[?\\]](https://akinevz.com/images/hyperawareofwhatacatis.png)",
193
+ {
194
+ "heading": "open source",
195
+ "content": [
196
+ "**public live projects**:",
197
+ {
198
+ "heading": "pagerts, my first npm package",
199
+ "link": "https://www.npmjs.com/package/pagerts"
200
+ }
201
+ ]
202
+ },
203
+ {
204
+ "heading": "secret",
205
+ "content": [
206
+ "soon (tm)",
207
+ {
208
+ "heading": "what does this mean?",
209
+ "content": "this is ancient Blizzard Studios terminology for something that is inevitably postponed"
210
+ }
211
+ ]
212
+ },
213
+ {
214
+ "heading": "music",
215
+ "link": "https://soundcloud.com/akinevz"
216
+ },
217
+ {
218
+ "heading": "abstract and surreal",
219
+ "content": []
220
+ }
221
+ ]
222
+ }
223
+ ]
224
+ },
225
+ {
226
+ "heading": "where's kine been",
227
+ "content": [
228
+ {
229
+ "heading": "for studies",
230
+ "content": "currently at University of Sussex"
231
+ },
232
+ {
233
+ "heading": "for work",
234
+ "content": [
235
+ "at Revolent Group (Salesforce)",
236
+ "in the wild (freelance)",
237
+ "at CMS (during merger w/ Nabarro+Olswang)"
238
+ ]
239
+ }
240
+ ]
241
+ }
242
+ ]
243
+ }
@@ -0,0 +1,3 @@
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1bb88bc21e6e8de5e5cd604dc3bb9073d9db79ccdd2a69c5a52663e0351d864a
3
+ size 16884