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.
- package/.astro/content-assets.mjs +1 -0
- package/.astro/content-modules.mjs +1 -0
- package/.astro/content.d.ts +199 -0
- package/.astro/data-store.json +1 -0
- package/.astro/settings.json +8 -0
- package/.astro/types.d.ts +1 -0
- package/.devcontainer/devcontainer.json +23 -0
- package/.env.firebase.example +8 -0
- package/.firebaserc +5 -0
- package/.gitattributes +2 -0
- package/.github/copilot-instructions.md +131 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/ci.yml +45 -0
- package/.github/workflows/deploy-admin.yml +48 -0
- package/.github/workflows/static.yml +43 -0
- package/.gitmodules +5 -0
- package/FIREBASE_SETUP.md +69 -0
- package/README.md +63 -0
- package/SECURITY.md +11 -0
- package/admin/Admin.csproj +7 -0
- package/admin/Dockerfile +14 -0
- package/admin/Program.cs +8 -0
- package/deploy-admin-cloud-run.md +229 -0
- package/eslint.config.js +28 -0
- package/firebase.json +5 -0
- package/firestore.rules +29 -0
- package/index.html +52 -0
- package/package.json +48 -0
- package/pagerts_output.json +1 -0
- package/public/5.html +967 -0
- package/public/BAHNSCHRIFT.TTF +0 -0
- package/public/Beep.ogg +0 -0
- package/public/Clippy.png +0 -0
- package/public/Layered Network Security Model for Home Networks (slides).pdf +0 -0
- package/public/Layered Network Security Model for Home Networks.pdf +0 -0
- package/public/TODO.pdf +0 -0
- package/public/WoW_Config.zip +3 -0
- package/public/addons/energy-swing.txt +1 -0
- package/public/addons/lego-yoda-death-readme.txt +11 -0
- package/public/addons/lego-yoda-death.mp3 +0 -0
- package/public/addons/mana-blast.txt +1 -0
- package/public/addons/rage-volley.txt +1 -0
- package/public/addons/rueg-cell.txt +1 -0
- package/public/addons/rueg-elvui-profile.txt +1 -0
- package/public/addons/rueg-grid2.txt +214 -0
- package/public/addons/rueg-plater-smol.txt +1 -0
- package/public/addons/rueg-plater.txt +1 -0
- package/public/addons/rueg-wa-druid.txt +1 -0
- package/public/addons/rueg-wa-priest.txt +1 -0
- package/public/addons/rueg-wa-rogue.txt +1 -0
- package/public/addons/rueg-wa-shaman.txt +1 -0
- package/public/addons/rueg-wa-warrior.txt +1 -0
- package/public/addons/spirit-smash.txt +1 -0
- package/public/avatar.jpg +0 -0
- package/public/avatar.png +0 -0
- package/public/crunchy_kick.ogg +0 -0
- package/public/documents/resume.html +312 -0
- package/public/favicon.ico +0 -0
- package/public/images/Ateric1.png +0 -0
- package/public/images/Ateric2.png +0 -0
- package/public/images/equal1.png +0 -0
- package/public/images/hyperawareofwhatacatis.png +0 -0
- package/public/images/kogg1.png +0 -0
- package/public/images/kogg2.png +0 -0
- package/public/images/rueg1.png +0 -0
- package/public/images/rueg2.png +0 -0
- package/public/incorrect_responses.txt +126 -0
- package/public/loading.css +51 -0
- package/public/resume.pdf +0 -0
- package/public/robots.txt +9 -0
- package/public/soundcloud.json +57 -0
- package/public/spinner.svg +12 -0
- package/public/tada.wav +0 -0
- package/public/yooh.mp3 +0 -0
- package/render.yaml +5 -0
- package/scripts/ensure-blog-worktree.mjs +24 -0
- package/scripts/generate-soundcloud-json.mjs +198 -0
- package/scripts/git-worktree-helper.mjs +122 -0
- package/scripts/hoist-dev-blog-local.mjs +149 -0
- package/scripts/music-schema.mjs +56 -0
- package/scripts/publish-soundcloud-json.mjs +32 -0
- package/scripts/sync-music-links-from-worktree.mjs +32 -0
- package/src/App.tsx +1500 -0
- package/src/addons.json +76 -0
- package/src/components/Addon.tsx +223 -0
- package/src/components/BlogContent.tsx +103 -0
- package/src/components/CopyToClipboardButton.tsx +21 -0
- package/src/components/MenuBar.tsx +151 -0
- package/src/components/MenuBarWithContext.tsx +6 -0
- package/src/components/Modal.tsx +17 -0
- package/src/components/MusicContent.tsx +309 -0
- package/src/components/NavBarController.tsx +55 -0
- package/src/components/NavBarControllerWrapper.tsx +13 -0
- package/src/components/Page.tsx +56 -0
- package/src/components/SitemapContent.tsx +125 -0
- package/src/contacts.json +32 -0
- package/src/env.d.ts +13 -0
- package/src/lib/assistantStateMachine.ts +80 -0
- package/src/lib/audioOverlap.ts +99 -0
- package/src/lib/keyboardInputUtils.ts +182 -0
- package/src/lib/musicSchema.ts +85 -0
- package/src/lib/naggingAssistantClient.ts +241 -0
- package/src/lib/resumeAnalytics.ts +163 -0
- package/src/main.tsx +35 -0
- package/src/pages.json +50 -0
- package/src/sections.json +243 -0
- package/src/src+addons.zip +3 -0
- package/src/styles/main.css +465 -0
- package/src/utils/blogSecurity.ts +87 -0
- package/src/utils/menuItems.ts +33 -0
- package/src/windowing/MinimizedSections.tsx +86 -0
- package/src/windowing/Section.tsx +586 -0
- package/src/windowing/context.tsx +13 -0
- package/src/windowing/hooks.ts +10 -0
- package/src/windowing/index.ts +7 -0
- package/src/windowing/provider.tsx +74 -0
- package/src/windowing/server.ts +3 -0
- package/src/windowing/types.ts +33 -0
- package/src/windowing/utils.ts +135 -0
- package/tests/generate-soundcloud-json.test.mjs +63 -0
- package/tests/music-schema.test.mjs +53 -0
- package/tsconfig.json +26 -0
- package/vite.config.ts +304 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default new Map();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default new Map();
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
declare module 'astro:content' {
|
|
2
|
+
export interface RenderResult {
|
|
3
|
+
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
|
|
4
|
+
headings: import('astro').MarkdownHeading[];
|
|
5
|
+
remarkPluginFrontmatter: Record<string, any>;
|
|
6
|
+
}
|
|
7
|
+
interface Render {
|
|
8
|
+
'.md': Promise<RenderResult>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface RenderedContent {
|
|
12
|
+
html: string;
|
|
13
|
+
metadata?: {
|
|
14
|
+
imagePaths: Array<string>;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
declare module 'astro:content' {
|
|
21
|
+
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
|
|
22
|
+
|
|
23
|
+
export type CollectionKey = keyof AnyEntryMap;
|
|
24
|
+
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
|
|
25
|
+
|
|
26
|
+
export type ContentCollectionKey = keyof ContentEntryMap;
|
|
27
|
+
export type DataCollectionKey = keyof DataEntryMap;
|
|
28
|
+
|
|
29
|
+
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
|
|
30
|
+
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
|
|
31
|
+
ContentEntryMap[C]
|
|
32
|
+
>['slug'];
|
|
33
|
+
|
|
34
|
+
export type ReferenceDataEntry<
|
|
35
|
+
C extends CollectionKey,
|
|
36
|
+
E extends keyof DataEntryMap[C] = string,
|
|
37
|
+
> = {
|
|
38
|
+
collection: C;
|
|
39
|
+
id: E;
|
|
40
|
+
};
|
|
41
|
+
export type ReferenceContentEntry<
|
|
42
|
+
C extends keyof ContentEntryMap,
|
|
43
|
+
E extends ValidContentEntrySlug<C> | (string & {}) = string,
|
|
44
|
+
> = {
|
|
45
|
+
collection: C;
|
|
46
|
+
slug: E;
|
|
47
|
+
};
|
|
48
|
+
export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = {
|
|
49
|
+
collection: C;
|
|
50
|
+
id: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** @deprecated Use `getEntry` instead. */
|
|
54
|
+
export function getEntryBySlug<
|
|
55
|
+
C extends keyof ContentEntryMap,
|
|
56
|
+
E extends ValidContentEntrySlug<C> | (string & {}),
|
|
57
|
+
>(
|
|
58
|
+
collection: C,
|
|
59
|
+
// Note that this has to accept a regular string too, for SSR
|
|
60
|
+
entrySlug: E,
|
|
61
|
+
): E extends ValidContentEntrySlug<C>
|
|
62
|
+
? Promise<CollectionEntry<C>>
|
|
63
|
+
: Promise<CollectionEntry<C> | undefined>;
|
|
64
|
+
|
|
65
|
+
/** @deprecated Use `getEntry` instead. */
|
|
66
|
+
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
|
|
67
|
+
collection: C,
|
|
68
|
+
entryId: E,
|
|
69
|
+
): Promise<CollectionEntry<C>>;
|
|
70
|
+
|
|
71
|
+
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
|
|
72
|
+
collection: C,
|
|
73
|
+
filter?: (entry: CollectionEntry<C>) => entry is E,
|
|
74
|
+
): Promise<E[]>;
|
|
75
|
+
export function getCollection<C extends keyof AnyEntryMap>(
|
|
76
|
+
collection: C,
|
|
77
|
+
filter?: (entry: CollectionEntry<C>) => unknown,
|
|
78
|
+
): Promise<CollectionEntry<C>[]>;
|
|
79
|
+
|
|
80
|
+
export function getLiveCollection<C extends keyof LiveContentConfig['collections']>(
|
|
81
|
+
collection: C,
|
|
82
|
+
filter?: LiveLoaderCollectionFilterType<C>,
|
|
83
|
+
): Promise<
|
|
84
|
+
import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>
|
|
85
|
+
>;
|
|
86
|
+
|
|
87
|
+
export function getEntry<
|
|
88
|
+
C extends keyof ContentEntryMap,
|
|
89
|
+
E extends ValidContentEntrySlug<C> | (string & {}),
|
|
90
|
+
>(
|
|
91
|
+
entry: ReferenceContentEntry<C, E>,
|
|
92
|
+
): E extends ValidContentEntrySlug<C>
|
|
93
|
+
? Promise<CollectionEntry<C>>
|
|
94
|
+
: Promise<CollectionEntry<C> | undefined>;
|
|
95
|
+
export function getEntry<
|
|
96
|
+
C extends keyof DataEntryMap,
|
|
97
|
+
E extends keyof DataEntryMap[C] | (string & {}),
|
|
98
|
+
>(
|
|
99
|
+
entry: ReferenceDataEntry<C, E>,
|
|
100
|
+
): E extends keyof DataEntryMap[C]
|
|
101
|
+
? Promise<DataEntryMap[C][E]>
|
|
102
|
+
: Promise<CollectionEntry<C> | undefined>;
|
|
103
|
+
export function getEntry<
|
|
104
|
+
C extends keyof ContentEntryMap,
|
|
105
|
+
E extends ValidContentEntrySlug<C> | (string & {}),
|
|
106
|
+
>(
|
|
107
|
+
collection: C,
|
|
108
|
+
slug: E,
|
|
109
|
+
): E extends ValidContentEntrySlug<C>
|
|
110
|
+
? Promise<CollectionEntry<C>>
|
|
111
|
+
: Promise<CollectionEntry<C> | undefined>;
|
|
112
|
+
export function getEntry<
|
|
113
|
+
C extends keyof DataEntryMap,
|
|
114
|
+
E extends keyof DataEntryMap[C] | (string & {}),
|
|
115
|
+
>(
|
|
116
|
+
collection: C,
|
|
117
|
+
id: E,
|
|
118
|
+
): E extends keyof DataEntryMap[C]
|
|
119
|
+
? string extends keyof DataEntryMap[C]
|
|
120
|
+
? Promise<DataEntryMap[C][E]> | undefined
|
|
121
|
+
: Promise<DataEntryMap[C][E]>
|
|
122
|
+
: Promise<CollectionEntry<C> | undefined>;
|
|
123
|
+
export function getLiveEntry<C extends keyof LiveContentConfig['collections']>(
|
|
124
|
+
collection: C,
|
|
125
|
+
filter: string | LiveLoaderEntryFilterType<C>,
|
|
126
|
+
): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>;
|
|
127
|
+
|
|
128
|
+
/** Resolve an array of entry references from the same collection */
|
|
129
|
+
export function getEntries<C extends keyof ContentEntryMap>(
|
|
130
|
+
entries: ReferenceContentEntry<C, ValidContentEntrySlug<C>>[],
|
|
131
|
+
): Promise<CollectionEntry<C>[]>;
|
|
132
|
+
export function getEntries<C extends keyof DataEntryMap>(
|
|
133
|
+
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
|
|
134
|
+
): Promise<CollectionEntry<C>[]>;
|
|
135
|
+
|
|
136
|
+
export function render<C extends keyof AnyEntryMap>(
|
|
137
|
+
entry: AnyEntryMap[C][string],
|
|
138
|
+
): Promise<RenderResult>;
|
|
139
|
+
|
|
140
|
+
export function reference<C extends keyof AnyEntryMap>(
|
|
141
|
+
collection: C,
|
|
142
|
+
): import('astro/zod').ZodEffects<
|
|
143
|
+
import('astro/zod').ZodString,
|
|
144
|
+
C extends keyof ContentEntryMap
|
|
145
|
+
? ReferenceContentEntry<C, ValidContentEntrySlug<C>>
|
|
146
|
+
: ReferenceDataEntry<C, keyof DataEntryMap[C]>
|
|
147
|
+
>;
|
|
148
|
+
// Allow generic `string` to avoid excessive type errors in the config
|
|
149
|
+
// if `dev` is not running to update as you edit.
|
|
150
|
+
// Invalid collection names will be caught at build time.
|
|
151
|
+
export function reference<C extends string>(
|
|
152
|
+
collection: C,
|
|
153
|
+
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
|
|
154
|
+
|
|
155
|
+
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
|
|
156
|
+
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
|
|
157
|
+
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
|
|
158
|
+
>;
|
|
159
|
+
|
|
160
|
+
type ContentEntryMap = {
|
|
161
|
+
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
type DataEntryMap = {
|
|
165
|
+
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
type AnyEntryMap = ContentEntryMap & DataEntryMap;
|
|
169
|
+
|
|
170
|
+
type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader<
|
|
171
|
+
infer TData,
|
|
172
|
+
infer TEntryFilter,
|
|
173
|
+
infer TCollectionFilter,
|
|
174
|
+
infer TError
|
|
175
|
+
>
|
|
176
|
+
? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError }
|
|
177
|
+
: { data: never; entryFilter: never; collectionFilter: never; error: never };
|
|
178
|
+
type ExtractDataType<T> = ExtractLoaderTypes<T>['data'];
|
|
179
|
+
type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
|
|
180
|
+
type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
|
|
181
|
+
type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];
|
|
182
|
+
|
|
183
|
+
type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> =
|
|
184
|
+
LiveContentConfig['collections'][C]['schema'] extends undefined
|
|
185
|
+
? ExtractDataType<LiveContentConfig['collections'][C]['loader']>
|
|
186
|
+
: import('astro/zod').infer<
|
|
187
|
+
Exclude<LiveContentConfig['collections'][C]['schema'], undefined>
|
|
188
|
+
>;
|
|
189
|
+
type LiveLoaderEntryFilterType<C extends keyof LiveContentConfig['collections']> =
|
|
190
|
+
ExtractEntryFilterType<LiveContentConfig['collections'][C]['loader']>;
|
|
191
|
+
type LiveLoaderCollectionFilterType<C extends keyof LiveContentConfig['collections']> =
|
|
192
|
+
ExtractCollectionFilterType<LiveContentConfig['collections'][C]['loader']>;
|
|
193
|
+
type LiveLoaderErrorType<C extends keyof LiveContentConfig['collections']> = ExtractErrorType<
|
|
194
|
+
LiveContentConfig['collections'][C]['loader']
|
|
195
|
+
>;
|
|
196
|
+
|
|
197
|
+
export type ContentConfig = typeof import("../src/content.config.mjs");
|
|
198
|
+
export type LiveContentConfig = never;
|
|
199
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.16.15","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"static\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":true,\"port\":8086,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[]},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false,\"svgo\":false},\"legacy\":{\"collections\":false}}"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="astro/client" />
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
|
2
|
+
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
|
|
3
|
+
{
|
|
4
|
+
"name": "Debian",
|
|
5
|
+
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
|
6
|
+
"image": "mcr.microsoft.com/devcontainers/base:bullseye",
|
|
7
|
+
// Features to add to the dev container. More info: https://containers.dev/features.
|
|
8
|
+
"features": {
|
|
9
|
+
"ghcr.io/devcontainers/features/node:1": {
|
|
10
|
+
"version": "latest",
|
|
11
|
+
"nvmVersion": "latest"
|
|
12
|
+
},
|
|
13
|
+
"ghcr.io/devcontainers-community/npm-features/typescript:1": {
|
|
14
|
+
"version": "latest"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
|
18
|
+
// "forwardPorts": [],
|
|
19
|
+
// Configure tool-specific properties.
|
|
20
|
+
// "customizations": {},
|
|
21
|
+
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
|
22
|
+
// "remoteUser": "root"
|
|
23
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Frontend Firebase config (from Firebase Project Settings -> Your apps -> Web app)
|
|
2
|
+
VITE_FIREBASE_API_KEY=
|
|
3
|
+
VITE_FIREBASE_AUTH_DOMAIN=
|
|
4
|
+
VITE_FIREBASE_PROJECT_ID=
|
|
5
|
+
VITE_FIREBASE_STORAGE_BUCKET=
|
|
6
|
+
VITE_FIREBASE_MESSAGING_SENDER_ID=
|
|
7
|
+
VITE_FIREBASE_APP_ID=
|
|
8
|
+
VITE_FIREBASE_MEASUREMENT_ID=
|
package/.firebaserc
ADDED
package/.gitattributes
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# GitHub Copilot Instructions
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
Personal website built with **Astro 5 + React 19**, using a JSON-to-HTML transformation architecture with Windows XP aesthetic (xp.css). The site features a fixed Windows XP-style menu bar at the top of every page and renders nested, interactive "window" components styled as Windows 98/XP UI elements.
|
|
5
|
+
|
|
6
|
+
## Core Architecture Pattern: JSON → Components → Astro
|
|
7
|
+
|
|
8
|
+
### Content Declaration (JSON-first)
|
|
9
|
+
- **Home page**: `src/sections.json` - nested sections with heading/content structure
|
|
10
|
+
- **Addons page**: `src/addons.json` - addon listings with status, links, and descriptions
|
|
11
|
+
- JSON structure supports recursive nesting: strings, arrays, or nested objects with `heading`/`content`
|
|
12
|
+
|
|
13
|
+
### Component Hierarchy
|
|
14
|
+
```
|
|
15
|
+
Astro Pages (.astro)
|
|
16
|
+
├─ Load JSON (sections.json / addons.json)
|
|
17
|
+
├─ Pass to React components with client:load
|
|
18
|
+
└─ React Components (.tsx)
|
|
19
|
+
├─ Section.tsx - renders nested sections as XP windows
|
|
20
|
+
├─ Addon.tsx - renders addons with copy-to-clipboard
|
|
21
|
+
├─ Page.tsx - orchestrates Section/Addon arrays
|
|
22
|
+
└─ SectionProvider - manages localStorage state
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Critical**: `.astro` files are server-rendered; use `client:load` for React interactivity.
|
|
26
|
+
|
|
27
|
+
## Component-Specific Patterns
|
|
28
|
+
|
|
29
|
+
### MenuBar Component (`src/components/MenuBar.astro`)
|
|
30
|
+
- **Fixed position**: Always visible at top of every page (Windows XP menu bar style)
|
|
31
|
+
- **Auto-discovery**: Automatically finds all `.astro` pages in `src/pages/` via `Astro.glob()`
|
|
32
|
+
- **Custom links**: Use `additionalLinks` prop to add non-page links (PDFs, external URLs)
|
|
33
|
+
- **Manual override**: Pass `links` prop to manually specify all menu items
|
|
34
|
+
- **Label mapping**: Default labels defined in `src/utils/menuItems.ts` (home → '/', addons → '/addons/', etc.)
|
|
35
|
+
- **Keyboard shortcuts**: Each menu item's first letter is underlined and acts as keyboard shortcut
|
|
36
|
+
- **Styling**: Uses gradient background (`#f0f0f0` to `#e0e0e0`) with subtle shadow
|
|
37
|
+
- **Pattern**: Must be placed first in `<body>` tag before other content
|
|
38
|
+
- **Accessibility**: Uses proper ARIA roles (`menubar`, `menuitem`)
|
|
39
|
+
- **Mobile**: Horizontal scrolling on small screens with touch-friendly targets
|
|
40
|
+
|
|
41
|
+
### Section Component (`src/components/Section.tsx`)
|
|
42
|
+
- **Recursive rendering**: `content` can contain more `SectionProps` objects
|
|
43
|
+
- **Collapse/expand state**: First render shows "OK" button; tracks expansion in localStorage via `SectionContext`
|
|
44
|
+
- **Window controls**: Minimize (no-op), Maximize (modal overlay if `depth > 0`), Close (plays sound)
|
|
45
|
+
- **Sound effect**: `playSound()` loads `/crunchy_kick.ogg` on close
|
|
46
|
+
|
|
47
|
+
### Addon Component (`src/components/Addon.tsx`)
|
|
48
|
+
- Extends `SectionProps` with `status`, `text`, `link` fields
|
|
49
|
+
- `CopyToClipboardButton` uses `navigator.clipboard` + `react-toastify`
|
|
50
|
+
- Supports same window-style UI as Section
|
|
51
|
+
|
|
52
|
+
### Page Component (`src/components/Page.tsx`)
|
|
53
|
+
- `PageContent`: Wraps sections in `SectionProvider` for context
|
|
54
|
+
- `PageWithAddons`: Similar but for addons, no provider needed
|
|
55
|
+
|
|
56
|
+
## Development Workflow
|
|
57
|
+
|
|
58
|
+
### Local Development
|
|
59
|
+
```bash
|
|
60
|
+
npm run dev # Starts on port 8086 (see astro.config.mjs)
|
|
61
|
+
npm run build # Production build
|
|
62
|
+
npm run preview # Preview production build
|
|
63
|
+
npm run lint # Run ESLint
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Dev container note**: Uses polling for file watching (see `vite.server.watch.usePolling` in astro.config.mjs) - necessary for containerized environments.
|
|
67
|
+
|
|
68
|
+
### Port Configuration
|
|
69
|
+
- Default: `8086` (configured in astro.config.mjs)
|
|
70
|
+
- Host: `true` (accessible from outside container)
|
|
71
|
+
|
|
72
|
+
## Styling Guidelines
|
|
73
|
+
|
|
74
|
+
### XP.css Integration
|
|
75
|
+
- External CDN: `https://unpkg.com/xp.css@0.2.3/dist/98.css`
|
|
76
|
+
- **Loading pattern**: All pages wait for xp.css to load before showing content (see `DOMContentLoaded` script in pages)
|
|
77
|
+
- Load state class: `styles-loaded` added to `<body>` when ready
|
|
78
|
+
|
|
79
|
+
### Custom Overrides (`src/styles/main.css`)
|
|
80
|
+
- Menu bar styles: Fixed position at top, XP-style gradient background
|
|
81
|
+
- Remove window borders on top-level `.page > .window` (avoid double borders)
|
|
82
|
+
- Title bar text forced to `#eee` for readability
|
|
83
|
+
- Body padding-top: `24px` (accounts for fixed menu bar height)
|
|
84
|
+
- CSS variables: `--background-color`, `--border-size`, `--gap-size`
|
|
85
|
+
- **Mobile responsive**: Menu bar scrolls horizontally on screens ≤768px with larger touch targets
|
|
86
|
+
|
|
87
|
+
**Pattern**: Never override xp.css directly; use higher-specificity selectors or `!important` sparingly.
|
|
88
|
+
|
|
89
|
+
## State Management
|
|
90
|
+
|
|
91
|
+
### SectionContext (`src/components/SectionContext.tsx`)
|
|
92
|
+
- Tracks expanded sections via `Set<string>`
|
|
93
|
+
- Persists "what is kine" expansion to localStorage key `expandedWhatIsKine`
|
|
94
|
+
- **Usage**: Wrap components in `<SectionProvider>`, access via `useSectionContext()`
|
|
95
|
+
|
|
96
|
+
## File Conventions
|
|
97
|
+
|
|
98
|
+
### Static Assets
|
|
99
|
+
- Addons text files: `public/addons/*.txt` (referenced as `/addons/filename.txt`)
|
|
100
|
+
- Audio: `public/*.ogg` files for UI sounds
|
|
101
|
+
- Images: `public/*.png` for metadata (og:image)
|
|
102
|
+
|
|
103
|
+
### TypeScript
|
|
104
|
+
- Strict mode enabled (`astro/tsconfigs/strict`)
|
|
105
|
+
- JSX: `react-jsx` with `jsxImportSource: "react"`
|
|
106
|
+
- Type exports: Use `type` keyword (`export type SectionProps`)
|
|
107
|
+
|
|
108
|
+
## Key Dependencies
|
|
109
|
+
|
|
110
|
+
- **Astro**: 5.x - use Astro pages for routes, React for interactivity
|
|
111
|
+
- **React**: 19.x - use `client:load` directive in Astro files
|
|
112
|
+
- **xp.css**: 0.2.6 (npm) + 0.2.3 (CDN) - Windows XP styling
|
|
113
|
+
- **react-toastify**: Clipboard notifications
|
|
114
|
+
|
|
115
|
+
## Common Tasks
|
|
116
|
+
|
|
117
|
+
### Adding a new page
|
|
118
|
+
1. Create `.astro` file in `src/pages/` (auto-routes)
|
|
119
|
+
2. Import content, styles
|
|
120
|
+
3. Include xp.css load script
|
|
121
|
+
4. Add `<MenuBar />` first in `<body>` (auto-discovers pages)
|
|
122
|
+
5. Use `client:load` for React components
|
|
123
|
+
6. **Optional**: Update `src/utils/menuItems.ts` to customize label and order
|
|
124
|
+
|
|
125
|
+
### Adding content
|
|
126
|
+
- **Home page**: Edit `src/sections.json`
|
|
127
|
+
- **Addons page**: Edit `src/addons.json`, add text file to `public/addons/`
|
|
128
|
+
|
|
129
|
+
### Modifying window behavior
|
|
130
|
+
- Sound effects: Update `playSound()` in Section.tsx/Addon.tsx
|
|
131
|
+
- Window controls logic: See `handleMaximize`, `handleClose`, `handleExpand`
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: "npm" # See documentation for possible values
|
|
9
|
+
directory: "/" # Location of package manifests
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: Website CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
env:
|
|
14
|
+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
verify:
|
|
18
|
+
name: Lint, Test, Build, Audit
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
timeout-minutes: 20
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- name: Checkout
|
|
24
|
+
uses: actions/checkout@v6
|
|
25
|
+
|
|
26
|
+
- name: Setup Node.js
|
|
27
|
+
uses: actions/setup-node@v6
|
|
28
|
+
with:
|
|
29
|
+
node-version: "24"
|
|
30
|
+
cache: "npm"
|
|
31
|
+
|
|
32
|
+
- name: Install dependencies
|
|
33
|
+
run: npm ci
|
|
34
|
+
|
|
35
|
+
- name: Lint
|
|
36
|
+
run: npm run lint
|
|
37
|
+
|
|
38
|
+
- name: Run tests
|
|
39
|
+
run: npm test
|
|
40
|
+
|
|
41
|
+
- name: Build site
|
|
42
|
+
run: npm run build
|
|
43
|
+
|
|
44
|
+
- name: Audit dependencies
|
|
45
|
+
run: npm run check:audit
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: Deploy Admin to Cloud Run
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths:
|
|
7
|
+
- admin/**
|
|
8
|
+
- .github/workflows/deploy-admin.yml
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
env:
|
|
12
|
+
REGION: europe-west1
|
|
13
|
+
SERVICE: admin
|
|
14
|
+
IMAGE: europe-west1-docker.pkg.dev/${{ secrets.GCP_PROJECT_ID }}/admin/app
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
deploy:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
timeout-minutes: 30
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout
|
|
23
|
+
uses: actions/checkout@v6
|
|
24
|
+
|
|
25
|
+
- name: Authenticate to Google Cloud
|
|
26
|
+
uses: google-github-actions/auth@v3
|
|
27
|
+
with:
|
|
28
|
+
credentials_json: ${{ secrets.GCP_SA_KEY }}
|
|
29
|
+
|
|
30
|
+
- name: Setup gcloud
|
|
31
|
+
uses: google-github-actions/setup-gcloud@v3
|
|
32
|
+
|
|
33
|
+
- name: Configure Docker auth
|
|
34
|
+
run: gcloud auth configure-docker ${{ env.REGION }}-docker.pkg.dev --quiet
|
|
35
|
+
|
|
36
|
+
- name: Build and push image
|
|
37
|
+
run: |
|
|
38
|
+
docker build -t ${{ env.IMAGE }}:${{ github.sha }} ./admin
|
|
39
|
+
docker push ${{ env.IMAGE }}:${{ github.sha }}
|
|
40
|
+
|
|
41
|
+
- name: Deploy to Cloud Run
|
|
42
|
+
run: |
|
|
43
|
+
gcloud run deploy ${{ env.SERVICE }} \
|
|
44
|
+
--image ${{ env.IMAGE }}:${{ github.sha }} \
|
|
45
|
+
--region ${{ env.REGION }} \
|
|
46
|
+
--platform managed \
|
|
47
|
+
--allow-unauthenticated \
|
|
48
|
+
--port 8080
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Simple workflow for deploying static content to GitHub Pages
|
|
2
|
+
name: Deploy static content to Pages
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
# Runs on pushes targeting the default branch
|
|
6
|
+
# push:
|
|
7
|
+
# branches: ["main"]
|
|
8
|
+
|
|
9
|
+
# Allows you to run this workflow manually from the Actions tab
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
pages: write
|
|
16
|
+
id-token: write
|
|
17
|
+
|
|
18
|
+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
|
19
|
+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
|
20
|
+
concurrency:
|
|
21
|
+
group: "pages"
|
|
22
|
+
cancel-in-progress: true
|
|
23
|
+
|
|
24
|
+
jobs:
|
|
25
|
+
# Single deploy job since we're just deploying
|
|
26
|
+
deploy:
|
|
27
|
+
environment:
|
|
28
|
+
name: github-pages
|
|
29
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
- name: Checkout
|
|
33
|
+
uses: actions/checkout@v4
|
|
34
|
+
- name: Setup Pages
|
|
35
|
+
uses: actions/configure-pages@v5
|
|
36
|
+
- name: Upload artifact
|
|
37
|
+
uses: actions/upload-pages-artifact@v3
|
|
38
|
+
with:
|
|
39
|
+
# Upload entire repository
|
|
40
|
+
path: '.'
|
|
41
|
+
- name: Deploy to GitHub Pages
|
|
42
|
+
id: deployment
|
|
43
|
+
uses: actions/deploy-pages@v4
|
package/.gitmodules
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Firebase Resume Interest Setup (Spark Tier)
|
|
2
|
+
|
|
3
|
+
This setup records resume interest email + timestamp + user agent directly into Firestore and keeps Firebase Analytics events on the frontend.
|
|
4
|
+
|
|
5
|
+
## 1) One-time prerequisites
|
|
6
|
+
|
|
7
|
+
From website root:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx firebase login
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 2) Create/select Firebase project
|
|
14
|
+
|
|
15
|
+
1. Go to Firebase Console.
|
|
16
|
+
2. Create a project (or select existing).
|
|
17
|
+
3. Enable Firestore Database (Native mode).
|
|
18
|
+
4. Enable Google Analytics for the project (recommended for event visibility).
|
|
19
|
+
|
|
20
|
+
## 3) Register web app and copy env values
|
|
21
|
+
|
|
22
|
+
1. In Firebase Console -> Project Settings -> General -> Your apps -> Add Web app.
|
|
23
|
+
2. Copy config values into local env file:
|
|
24
|
+
cp .env.firebase.example .env.local
|
|
25
|
+
3. Fill all `VITE_FIREBASE_*` values.
|
|
26
|
+
|
|
27
|
+
## 4) Deploy Firestore security rules
|
|
28
|
+
|
|
29
|
+
From website root:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx firebase use <your-project-id>
|
|
33
|
+
npx firebase deploy --only firestore:rules
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 5) Run and verify locally
|
|
37
|
+
|
|
38
|
+
From website root:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm run dev
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Open `/resume`, use **Share Interest Email**, and submit a test email.
|
|
45
|
+
|
|
46
|
+
## 6) Verify data landing
|
|
47
|
+
|
|
48
|
+
1. Firestore collection: `resume_interest`
|
|
49
|
+
2. Fields written:
|
|
50
|
+
- `email`
|
|
51
|
+
- `source`
|
|
52
|
+
- `userAgent`
|
|
53
|
+
- `createdAt`
|
|
54
|
+
|
|
55
|
+
## 7) Analytics events from frontend
|
|
56
|
+
|
|
57
|
+
The client logs these GA/Firebase Analytics events when configured:
|
|
58
|
+
|
|
59
|
+
- `resume_page_view`
|
|
60
|
+
- `resume_open_click`
|
|
61
|
+
- `resume_interest_submit`
|
|
62
|
+
- `resume_interest_submit_failed`
|
|
63
|
+
|
|
64
|
+
## Notes
|
|
65
|
+
|
|
66
|
+
- No route gate is required.
|
|
67
|
+
- Spark-compatible mode does not collect server-side IP.
|
|
68
|
+
- Writes are validated by strict Firestore rules in `firestore.rules`.
|
|
69
|
+
- Add a brief privacy notice on the resume page if collecting personal data.
|