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 @@
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,8 @@
1
+ {
2
+ "_variables": {
3
+ "lastUpdateCheck": 1773132643874
4
+ },
5
+ "devToolbar": {
6
+ "enabled": false
7
+ }
8
+ }
@@ -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
@@ -0,0 +1,5 @@
1
+ {
2
+ "projects": {
3
+ "default": "akinevz-store"
4
+ }
5
+ }
package/.gitattributes ADDED
@@ -0,0 +1,2 @@
1
+ * text=auto
2
+ *.zip filter=lfs diff=lfs merge=lfs -text
@@ -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,5 @@
1
+ [submodule "resume-pandoc"]
2
+ path = resume-pandoc
3
+ url = https://github.com/john-bokma/resume-pandoc.git
4
+ [submodule "public/xp.css"]
5
+ path = public/xp.css
@@ -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.