@leadertechie/personal-site-kit 0.0.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 (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/dist/api/handlers/aboutme.d.ts +3 -0
  4. package/dist/api/handlers/aboutme.d.ts.map +1 -0
  5. package/dist/api/handlers/content-api.d.ts +5 -0
  6. package/dist/api/handlers/content-api.d.ts.map +1 -0
  7. package/dist/api/handlers/content.d.ts +2 -0
  8. package/dist/api/handlers/content.d.ts.map +1 -0
  9. package/dist/api/handlers/home.d.ts +3 -0
  10. package/dist/api/handlers/home.d.ts.map +1 -0
  11. package/dist/api/handlers/info.d.ts +2 -0
  12. package/dist/api/handlers/info.d.ts.map +1 -0
  13. package/dist/api/handlers/logo.d.ts +2 -0
  14. package/dist/api/handlers/logo.d.ts.map +1 -0
  15. package/dist/api/handlers/staticdetails.d.ts +2 -0
  16. package/dist/api/handlers/staticdetails.d.ts.map +1 -0
  17. package/dist/api/index.d.ts +9 -0
  18. package/dist/api/index.d.ts.map +1 -0
  19. package/dist/api/utils.d.ts +8 -0
  20. package/dist/api/utils.d.ts.map +1 -0
  21. package/dist/api.d.ts +4 -0
  22. package/dist/api.js +591 -0
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.js +354 -0
  25. package/dist/prerender/index.d.ts +5 -0
  26. package/dist/prerender/index.d.ts.map +1 -0
  27. package/dist/prerender/pageContent.d.ts +16 -0
  28. package/dist/prerender/pageContent.d.ts.map +1 -0
  29. package/dist/prerender/prerender.d.ts +3 -0
  30. package/dist/prerender/prerender.d.ts.map +1 -0
  31. package/dist/prerender/template.d.ts +10 -0
  32. package/dist/prerender/template.d.ts.map +1 -0
  33. package/dist/prerender.d.ts +4 -0
  34. package/dist/prerender.js +399 -0
  35. package/dist/shared/config/api.d.ts +2 -0
  36. package/dist/shared/config/api.d.ts.map +1 -0
  37. package/dist/shared/config/index.d.ts +5 -0
  38. package/dist/shared/config/index.d.ts.map +1 -0
  39. package/dist/shared/config/types.d.ts +16 -0
  40. package/dist/shared/config/types.d.ts.map +1 -0
  41. package/dist/shared/core/site-store.d.ts +16 -0
  42. package/dist/shared/core/site-store.d.ts.map +1 -0
  43. package/dist/shared/core/theme-toggle.d.ts +13 -0
  44. package/dist/shared/core/theme-toggle.d.ts.map +1 -0
  45. package/dist/shared/index.d.ts +9 -0
  46. package/dist/shared/index.d.ts.map +1 -0
  47. package/dist/shared/interfaces/iFooterLink.d.ts +5 -0
  48. package/dist/shared/interfaces/iFooterLink.d.ts.map +1 -0
  49. package/dist/shared/interfaces/iRoute.d.ts +5 -0
  50. package/dist/shared/interfaces/iRoute.d.ts.map +1 -0
  51. package/dist/shared/pageContent.d.ts +35 -0
  52. package/dist/shared/pageContent.d.ts.map +1 -0
  53. package/dist/shared/runtime.d.ts +7 -0
  54. package/dist/shared/runtime.d.ts.map +1 -0
  55. package/dist/shared/template.d.ts +8 -0
  56. package/dist/shared/template.d.ts.map +1 -0
  57. package/dist/shared.d.ts +2 -0
  58. package/dist/shared.js +10 -0
  59. package/dist/ui/aboutme/api.d.ts +11 -0
  60. package/dist/ui/aboutme/api.d.ts.map +1 -0
  61. package/dist/ui/aboutme/index.d.ts +26 -0
  62. package/dist/ui/aboutme/index.d.ts.map +1 -0
  63. package/dist/ui/aboutme/renderer.d.ts +5 -0
  64. package/dist/ui/aboutme/renderer.d.ts.map +1 -0
  65. package/dist/ui/aboutme/styles.d.ts +2 -0
  66. package/dist/ui/aboutme/styles.d.ts.map +1 -0
  67. package/dist/ui/admin/index.d.ts +42 -0
  68. package/dist/ui/admin/index.d.ts.map +1 -0
  69. package/dist/ui/admin/styles.d.ts +2 -0
  70. package/dist/ui/admin/styles.d.ts.map +1 -0
  71. package/dist/ui/banner/index.d.ts +17 -0
  72. package/dist/ui/banner/index.d.ts.map +1 -0
  73. package/dist/ui/banner/styles.d.ts +2 -0
  74. package/dist/ui/banner/styles.d.ts.map +1 -0
  75. package/dist/ui/footer/index.d.ts +9 -0
  76. package/dist/ui/footer/index.d.ts.map +1 -0
  77. package/dist/ui/footer/styles.d.ts +2 -0
  78. package/dist/ui/footer/styles.d.ts.map +1 -0
  79. package/dist/ui.d.ts +2 -0
  80. package/dist/ui.js +820 -0
  81. package/package.json +41 -0
  82. package/src/api/__tests__/info.test.ts +44 -0
  83. package/src/api/__tests__/utils.test.ts +78 -0
  84. package/src/api/handlers/aboutme.ts +99 -0
  85. package/src/api/handlers/content-api.ts +268 -0
  86. package/src/api/handlers/content.ts +72 -0
  87. package/src/api/handlers/home.ts +79 -0
  88. package/src/api/handlers/info.ts +12 -0
  89. package/src/api/handlers/logo.ts +55 -0
  90. package/src/api/handlers/staticdetails.ts +48 -0
  91. package/src/api/index.ts +125 -0
  92. package/src/api/utils.ts +16 -0
  93. package/src/prerender/__tests__/pageContent.test.ts +54 -0
  94. package/src/prerender/__tests__/template.test.ts +54 -0
  95. package/src/prerender/index.ts +138 -0
  96. package/src/prerender/pageContent.ts +263 -0
  97. package/src/prerender/prerender.ts +25 -0
  98. package/src/prerender/template.ts +65 -0
  99. package/src/shared/config/api.ts +16 -0
  100. package/src/shared/config/index.ts +41 -0
  101. package/src/shared/config/types.ts +16 -0
  102. package/src/shared/core/__tests__/theme-toggle.test.ts +204 -0
  103. package/src/shared/core/site-store.ts +38 -0
  104. package/src/shared/core/theme-toggle.ts +118 -0
  105. package/src/shared/index.ts +15 -0
  106. package/src/shared/interfaces/iFooterLink.ts +4 -0
  107. package/src/shared/interfaces/iRoute.ts +4 -0
  108. package/src/shared/models/theme-variables.css +25 -0
  109. package/src/shared/pageContent.ts +209 -0
  110. package/src/shared/runtime.ts +11 -0
  111. package/src/shared/styles/markdown.css +129 -0
  112. package/src/shared/template.ts +35 -0
  113. package/src/styles/markdown.css +129 -0
  114. package/src/styles/theme.css +432 -0
  115. package/src/ui/aboutme/api.ts +12 -0
  116. package/src/ui/aboutme/index.ts +155 -0
  117. package/src/ui/aboutme/renderer.ts +7 -0
  118. package/src/ui/aboutme/styles.ts +10 -0
  119. package/src/ui/admin/index.ts +492 -0
  120. package/src/ui/admin/styles.ts +317 -0
  121. package/src/ui/banner/index.ts +38 -0
  122. package/src/ui/banner/styles.ts +10 -0
  123. package/src/ui/footer/index.ts +37 -0
  124. package/src/ui/footer/styles.ts +9 -0
@@ -0,0 +1,432 @@
1
+ /* Theme Variables - Global */
2
+ :root {
3
+ --text-color: #333;
4
+ --background-color: #fff;
5
+ --link-color: #0066cc;
6
+ --link-hover-color: #004d99;
7
+ --nav-link-color: #333;
8
+ --nav-link-hover-bg: #f0f0f0;
9
+ --border-color: #eee;
10
+ --card-bg: #fff;
11
+ --secondary-text: #666;
12
+
13
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
14
+ line-height: 1.5;
15
+ font-weight: 400;
16
+ color-scheme: light dark;
17
+ font-synthesis: none;
18
+ text-rendering: optimizeLegibility;
19
+ -webkit-font-smoothing: antialiased;
20
+ -moz-osx-font-smoothing: grayscale;
21
+ }
22
+
23
+ [data-theme='dark'] {
24
+ --text-color: rgba(255, 255, 255, 0.87);
25
+ --background-color: #242424;
26
+ --link-color: #646cff;
27
+ --link-hover-color: #747bff;
28
+ --nav-link-color: rgba(255, 255, 255, 0.87);
29
+ --nav-link-hover-bg: #333;
30
+ --border-color: #444;
31
+ --card-bg: #1a1a1a;
32
+ --secondary-text: #aaa;
33
+ }
34
+
35
+ * {
36
+ box-sizing: border-box;
37
+ }
38
+
39
+ html, body {
40
+ margin: 0;
41
+ padding: 0;
42
+ min-height: 100vh;
43
+ width: 100%;
44
+ max-width: 100vw;
45
+ overflow-x: hidden;
46
+ }
47
+
48
+ body {
49
+ display: flex;
50
+ flex-direction: column;
51
+ align-items: stretch;
52
+ color: var(--text-color);
53
+ background-color: var(--background-color);
54
+ min-width: 320px;
55
+ }
56
+
57
+ #app {
58
+ max-width: 1280px;
59
+ margin: 0 auto;
60
+ padding: 0;
61
+ text-align: center;
62
+ flex: 1;
63
+ display: flex;
64
+ flex-direction: column;
65
+ width: 100%;
66
+ }
67
+
68
+ main {
69
+ flex: 1 1 auto;
70
+ display: flex;
71
+ flex-direction: column;
72
+ align-items: center;
73
+ justify-content: flex-start;
74
+ width: 100%;
75
+ padding: 1rem;
76
+ }
77
+
78
+ @media (max-width: 768px) {
79
+ main {
80
+ padding: 0.75rem;
81
+ }
82
+ }
83
+
84
+ .page-title {
85
+ font-size: 3.2em;
86
+ line-height: 1.1;
87
+ margin: 0 0 1rem 0;
88
+ font-weight: 600;
89
+ }
90
+
91
+ .page-subtitle {
92
+ font-size: 1.5em;
93
+ line-height: 1.3;
94
+ margin: 0 0 0.75rem 0;
95
+ }
96
+
97
+ .content a,
98
+ .page-content a {
99
+ font-weight: 500;
100
+ color: var(--link-color);
101
+ text-decoration: inherit;
102
+ transition: color 0.2s ease;
103
+ }
104
+ .content a:hover,
105
+ .page-content a:hover {
106
+ color: var(--link-hover-color);
107
+ }
108
+
109
+ .nav-link {
110
+ color: var(--nav-link-color);
111
+ text-decoration: none;
112
+ padding: 0.5rem 1rem;
113
+ border-radius: 5px;
114
+ transition: background-color 0.3s ease;
115
+ display: inline-block;
116
+ }
117
+
118
+ .nav-link:hover {
119
+ background-color: var(--nav-link-hover-bg);
120
+ text-decoration: none;
121
+ }
122
+
123
+ .container {
124
+ padding: 2rem;
125
+ margin: 0 auto;
126
+ min-height: 60vh;
127
+ width: 100%;
128
+ }
129
+
130
+ .container-narrow {
131
+ max-width: 700px;
132
+ }
133
+
134
+ .container-medium {
135
+ max-width: 900px;
136
+ }
137
+
138
+ .container-wide {
139
+ max-width: 1200px;
140
+ }
141
+
142
+ .column-layout {
143
+ display: flex;
144
+ gap: 2rem;
145
+ flex-wrap: wrap;
146
+ width: 100%;
147
+ }
148
+
149
+ .row-layout {
150
+ flex-direction: row;
151
+ align-items: flex-start;
152
+ }
153
+
154
+ .main-column { flex: 2; min-width: 300px; }
155
+ .sidebar-column { flex: 1; min-width: 250px; }
156
+ .wide-main-column { flex: 3; min-width: 300px; }
157
+
158
+ .text-center { text-align: center; }
159
+ .text-left { text-align: left; }
160
+
161
+ .mt-1 { margin-top: 10px; }
162
+ .mt-2 { margin-top: 2rem; }
163
+ .mb-1 { margin-bottom: 1rem; }
164
+ .mb-15 { margin-bottom: 1.5rem; }
165
+ .mb-2 { margin-bottom: 2rem; }
166
+ .pb-05 { padding-bottom: 0.5rem; }
167
+
168
+ .search-input {
169
+ width: 100%;
170
+ padding: 10px;
171
+ margin-bottom: 20px;
172
+ border: 1px solid var(--border-color);
173
+ border-radius: 4px;
174
+ background: var(--background-color);
175
+ color: var(--text-color);
176
+ box-sizing: border-box;
177
+ }
178
+
179
+ .search-input-lg {
180
+ padding: 0.8rem;
181
+ }
182
+
183
+ .border-bottom {
184
+ border-bottom: 1px solid var(--border-color, #eee);
185
+ }
186
+
187
+ .hidden {
188
+ display: none !important;
189
+ }
190
+
191
+ /* Markdown Content Styles - Flat heading classes from md2html */
192
+ .md-h1, .h1 { font-size: 2.5rem; margin: 0 0 1rem 0; font-weight: 600; line-height: 1.2; }
193
+ .md-h2, .h2 { font-size: 2rem; margin: 0 0 0.875rem 0; font-weight: 600; line-height: 1.25; }
194
+ .md-h3, .h3 { font-size: 1.5rem; margin: 0 0 0.75rem 0; font-weight: 600; line-height: 1.3; }
195
+ .md-h4, .h4 { font-size: 1.25rem; margin: 0 0 0.5rem 0; font-weight: 600; line-height: 1.35; }
196
+ .md-h5, .h5, .md-h6, .h6 { font-size: 1rem; margin: 0 0 0.5rem 0; font-weight: 600; }
197
+
198
+ .md-paragraph {
199
+ margin: 0 0 1rem 0;
200
+ line-height: 1.7;
201
+ color: var(--text-color, #213547);
202
+ }
203
+
204
+ .md-list {
205
+ margin: 0 0 1rem 0;
206
+ padding-left: 1.5rem;
207
+ }
208
+
209
+ .md-list-item {
210
+ margin-bottom: 0.5rem;
211
+ line-height: 1.6;
212
+ }
213
+
214
+ .md-blockquote {
215
+ margin: 1rem 0;
216
+ padding: 0.75rem 1rem;
217
+ border-left: 4px solid var(--link-color, #646cff);
218
+ background: var(--nav-link-hover-bg, #f8f9fa);
219
+ border-radius: 0 8px 8px 0;
220
+ color: var(--secondary-text, #666);
221
+ }
222
+
223
+ .md-blockquote .md-paragraph {
224
+ margin: 0;
225
+ }
226
+
227
+ .md-code {
228
+ font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
229
+ font-size: 0.9em;
230
+ background: var(--nav-link-hover-bg, #f5f5f5);
231
+ padding: 0.2rem 0.4rem;
232
+ border-radius: 4px;
233
+ }
234
+
235
+ .md-image {
236
+ max-width: 100%;
237
+ height: auto;
238
+ border-radius: 8px;
239
+ margin: 1rem 0;
240
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
241
+ }
242
+
243
+ .md-container {
244
+ margin: 1rem 0;
245
+ }
246
+
247
+ .md-container hr {
248
+ border: none;
249
+ height: 2px;
250
+ background: var(--border-color, #eee);
251
+ margin: 2rem 0;
252
+ border-radius: 1px;
253
+ }
254
+
255
+ .md-container[tag="hr"] {
256
+ margin: 2rem 0;
257
+ }
258
+
259
+ .md-paragraph a,
260
+ .md-list-item a {
261
+ color: var(--link-color, #646cff);
262
+ text-decoration: none;
263
+ transition: color 0.2s ease;
264
+ }
265
+
266
+ .md-paragraph a:hover,
267
+ .md-list-item a:hover {
268
+ color: var(--link-hover-color, #535bf2);
269
+ text-decoration: underline;
270
+ }
271
+
272
+ .md-paragraph strong,
273
+ .md-list-item strong {
274
+ font-weight: 600;
275
+ color: var(--text-color, #1a1a1a);
276
+ }
277
+
278
+ .md-paragraph em,
279
+ .md-list-item em {
280
+ font-style: italic;
281
+ }
282
+
283
+ .md-container table {
284
+ width: 100%;
285
+ border-collapse: collapse;
286
+ margin: 1rem 0;
287
+ }
288
+
289
+ .md-container th,
290
+ .md-container td {
291
+ padding: 0.75rem;
292
+ border: 1px solid var(--border-color, #ddd);
293
+ text-align: left;
294
+ }
295
+
296
+ .md-container th {
297
+ background: var(--nav-link-hover-bg, #f5f5f5);
298
+ font-weight: 600;
299
+ }
300
+
301
+ html[data-theme="dark"] .md-code {
302
+ background: rgba(255, 255, 255, 0.1);
303
+ }
304
+
305
+ html[data-theme="dark"] .md-container th {
306
+ background: rgba(255, 255, 255, 0.05);
307
+ }
308
+
309
+ /* Gist Card */
310
+ .gist-card {
311
+ margin-bottom: 1.5rem;
312
+ text-align: left;
313
+ }
314
+
315
+ .gist-card a {
316
+ color: var(--link-color);
317
+ text-decoration: none;
318
+ transition: color 0.2s ease;
319
+ }
320
+
321
+ .gist-card a:hover {
322
+ color: var(--link-hover-color);
323
+ }
324
+
325
+ .gist-card h4 {
326
+ margin: 0 0 0.25rem 0;
327
+ font-size: 1rem;
328
+ }
329
+
330
+ .gist-card p {
331
+ font-size: 0.9rem;
332
+ margin-bottom: 0.25rem;
333
+ }
334
+
335
+ .gist-card small {
336
+ color: var(--secondary-text, #666);
337
+ }
338
+
339
+ /* Sidebar */
340
+ .sidebar-nav {
341
+ max-height: 80vh;
342
+ overflow-y: auto;
343
+ border-right: 1px solid var(--border-color);
344
+ padding-right: 1rem;
345
+ }
346
+
347
+ .sidebar-item {
348
+ padding: 0.75rem;
349
+ border-bottom: 1px solid var(--border-color);
350
+ border-radius: 4px;
351
+ margin-bottom: 0.5rem;
352
+ text-decoration: none;
353
+ color: inherit;
354
+ display: block;
355
+ }
356
+
357
+ .sidebar-item:hover {
358
+ background: var(--nav-link-hover-bg);
359
+ }
360
+
361
+ .sidebar-item.active {
362
+ background: var(--nav-link-hover-bg);
363
+ border-left: 4px solid var(--link-color);
364
+ }
365
+
366
+ .sidebar-item-link {
367
+ padding: 0;
368
+ border: none;
369
+ margin: 0;
370
+ width: 100%;
371
+ color: inherit;
372
+ display: block;
373
+ }
374
+
375
+ .sidebar-item-link:hover {
376
+ background: transparent;
377
+ }
378
+
379
+ .sidebar-item-link h4 {
380
+ margin: 0;
381
+ font-size: 1rem;
382
+ }
383
+
384
+ /* Banner Component - now handled by banner.css */
385
+
386
+ /* Footer Component - external styles for Shadow DOM penetration */
387
+ my-footer {
388
+ display: flex;
389
+ align-items: center;
390
+ justify-content: space-between;
391
+ padding: 1rem 2rem;
392
+ background: var(--background-color);
393
+ color: var(--text-color);
394
+ border-top: 1px solid var(--nav-link-hover-bg);
395
+ flex-wrap: wrap;
396
+ }
397
+
398
+ my-footer .footer-content {
399
+ display: flex;
400
+ flex-direction: column;
401
+ align-items: flex-start;
402
+ }
403
+
404
+ my-footer .links {
405
+ display: flex;
406
+ gap: 1rem;
407
+ margin-top: 0.5rem;
408
+ }
409
+
410
+ my-footer .links a {
411
+ color: inherit;
412
+ text-decoration: none;
413
+ padding: 0.25rem 0.5rem;
414
+ border-radius: 4px;
415
+ transition: background-color 0.3s ease;
416
+ }
417
+
418
+ my-footer .links a:hover {
419
+ background-color: rgba(0, 0, 0, 0.1);
420
+ }
421
+
422
+ @media (min-width: 768px) {
423
+ my-footer .footer-content {
424
+ flex-direction: row;
425
+ align-items: center;
426
+ width: 100%;
427
+ justify-content: space-between;
428
+ }
429
+ my-footer .links {
430
+ margin-top: 0;
431
+ }
432
+ }
@@ -0,0 +1,12 @@
1
+ export interface Profile {
2
+ name: string;
3
+ title: string;
4
+ experience: string;
5
+ profileImageUrl?: string;
6
+ }
7
+
8
+ export async function fetchAboutMe(url: string): Promise<{ profile: Profile; contentNodes: any[] }> {
9
+ const res = await fetch(`${url}/api/aboutme`);
10
+ if (!res.ok) throw new Error('Failed to fetch');
11
+ return res.json();
12
+ }
@@ -0,0 +1,155 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { customElement, property, state } from 'lit/decorators.js';
3
+
4
+ import { ContentNode } from '@leadertechie/md2html';
5
+ import { Profile } from './api';
6
+
7
+ import { aboutmeStyles } from './styles';
8
+
9
+ import { AboutMeRenderer } from './renderer';
10
+ import { fetchAboutMe } from './api';
11
+
12
+ interface AboutMeData {
13
+ profile: Profile;
14
+ contentNodes: ContentNode[];
15
+ }
16
+
17
+ @customElement('my-aboutme')
18
+ export class MyAboutme extends LitElement {
19
+ static styles = aboutmeStyles;
20
+
21
+ @property({ type: String })
22
+ accessor baseUrl = '';
23
+
24
+ @state()
25
+ accessor profile: Profile | null = null;
26
+
27
+ @state()
28
+ accessor contentNodes: ContentNode[] = [];
29
+
30
+ @state()
31
+ accessor loading = true;
32
+
33
+ private renderer: AboutMeRenderer;
34
+ /**
35
+ * Injectable fetcher function used to retrieve About Me data.
36
+ * Tests can override this (e.g. componentInstance.fetcher = myMock)
37
+ * to avoid network I/O. By default it uses the stable `fetchAboutMe` helper.
38
+ */
39
+ public fetcher = fetchAboutMe;
40
+
41
+ constructor(renderer?: AboutMeRenderer) {
42
+ super();
43
+ this.renderer = renderer || new AboutMeRenderer();
44
+ }
45
+
46
+ private get apiBaseUrl(): string {
47
+ return this.baseUrl || this.getAttribute('base-url') || '';
48
+ }
49
+
50
+ async connectedCallback() {
51
+ super.connectedCallback();
52
+
53
+ // If we have initial data from SSR, use it immediately.
54
+ if (typeof window !== 'undefined' && (window as any).__HYDRATION_DATA__) {
55
+ const hydrationData = (window as any).__HYDRATION_DATA__;
56
+ this.profile = hydrationData.profile;
57
+ this.contentNodes = hydrationData.contentNodes;
58
+ this.loading = false;
59
+ return;
60
+ }
61
+
62
+ // Load content if a baseUrl is provided. In dev/prod, this will be set.
63
+ const url = this.apiBaseUrl;
64
+ if (url) {
65
+ this.loadContent();
66
+ }
67
+ }
68
+
69
+ updated(changedProperties: Map<string, any>) {
70
+ super.updated(changedProperties);
71
+
72
+ // Only load content from API if we haven't already loaded via hydration data
73
+ if (this.loading === false && this.profile && this.contentNodes.length > 0) {
74
+ return;
75
+ }
76
+
77
+ // If baseUrl changed and we have a valid baseUrl, load content
78
+ if (changedProperties.has('baseUrl') || changedProperties.has('base-url')) {
79
+ const url = this.apiBaseUrl;
80
+ if (url) {
81
+ this.loadContent();
82
+ }
83
+ }
84
+ }
85
+
86
+ private async loadContent() {
87
+ try {
88
+ this.loading = true;
89
+
90
+ // Fetch content from API using the injectable fetcher.
91
+ // Use the apiBaseUrl getter to get either property or attribute
92
+ const url = this.apiBaseUrl;
93
+ const data = await this.fetcher(url);
94
+
95
+ this.profile = data.profile;
96
+ this.contentNodes = data.contentNodes;
97
+ this.loading = false;
98
+ // Wait for the render to complete
99
+ await this.updateComplete;
100
+ } catch (error) {
101
+ this.loading = false;
102
+ // Set fallback content
103
+ this.setFallbackContent();
104
+ // Wait for the render to complete
105
+ await this.updateComplete;
106
+ }
107
+ }
108
+
109
+ private setFallbackContent() {
110
+ this.profile = null; // No profile data
111
+ this.contentNodes = [
112
+ {
113
+ type: 'paragraph',
114
+ content: 'Unable to Load Content'
115
+ }
116
+ ];
117
+ }
118
+
119
+ render() {
120
+ if (this.loading) {
121
+ return html`<div class="aboutme"><div class="loading">Loading...</div></div>`;
122
+ }
123
+
124
+ // If profile is not available, check if fallback content is present.
125
+ if (!this.profile) {
126
+ if (this.contentNodes && this.contentNodes.length > 0) {
127
+ return html`<div class="aboutme">${this.renderer.renderContent(this.contentNodes)}</div>`;
128
+ }
129
+ return html`<div class="aboutme"><div class="loading">Failed to load content</div></div>`;
130
+ }
131
+
132
+ // Render profile header + content
133
+ const profileImageUrl = this.profile.profileImageUrl
134
+ ? (this.profile.profileImageUrl.startsWith('http') || this.profile.profileImageUrl.startsWith('/')
135
+ ? this.profile.profileImageUrl
136
+ : `images/${this.profile.profileImageUrl}`)
137
+ : '';
138
+
139
+ return html`
140
+ <div class="aboutme">
141
+ <div class="profile-section">
142
+ ${profileImageUrl ? html`
143
+ <img src="${profileImageUrl}" alt="${this.profile.name}"
144
+ class="profile-picture">
145
+ ` : ''}
146
+ <h1>${this.profile.name}</h1>
147
+ <p class="profile-title">${this.profile.title} • ${this.profile.experience}</p>
148
+ </div>
149
+ <div class="content-section">
150
+ ${this.renderer.renderContent(this.contentNodes)}
151
+ </div>
152
+ </div>
153
+ `;
154
+ }
155
+ }
@@ -0,0 +1,7 @@
1
+ import { ContentNode } from '@leadertechie/md2html';
2
+
3
+ export class AboutMeRenderer {
4
+ renderContent(nodes: ContentNode[]): unknown {
5
+ return nodes;
6
+ }
7
+ }
@@ -0,0 +1,10 @@
1
+ import { css } from 'lit';
2
+
3
+ export const aboutmeStyles = css`
4
+ .aboutme {
5
+ padding: 2rem;
6
+ }
7
+ .loading {
8
+ opacity: 0.7;
9
+ }
10
+ `;