@uptime.link/statuspage 1.0.74 → 1.2.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 (96) hide show
  1. package/dist_bundle/bundle.js +5019 -519
  2. package/dist_bundle/bundle.js.map +4 -4
  3. package/dist_ts_web/00_commitinfo_data.js +2 -2
  4. package/dist_ts_web/elements/index.d.ts +3 -0
  5. package/dist_ts_web/elements/index.js +6 -1
  6. package/dist_ts_web/elements/internal/uplinternal-miniheading.d.ts +1 -0
  7. package/dist_ts_web/elements/internal/uplinternal-miniheading.js +78 -28
  8. package/dist_ts_web/elements/upl-statuspage-assetsselector.d.ts +14 -0
  9. package/dist_ts_web/elements/upl-statuspage-assetsselector.demo.d.ts +1 -0
  10. package/dist_ts_web/elements/upl-statuspage-assetsselector.demo.js +575 -0
  11. package/dist_ts_web/elements/upl-statuspage-assetsselector.js +679 -43
  12. package/dist_ts_web/elements/upl-statuspage-footer.d.ts +46 -2
  13. package/dist_ts_web/elements/upl-statuspage-footer.demo.d.ts +1 -0
  14. package/dist_ts_web/elements/upl-statuspage-footer.demo.js +679 -0
  15. package/dist_ts_web/elements/upl-statuspage-footer.js +846 -61
  16. package/dist_ts_web/elements/upl-statuspage-header.d.ts +5 -1
  17. package/dist_ts_web/elements/upl-statuspage-header.demo.d.ts +1 -0
  18. package/dist_ts_web/elements/upl-statuspage-header.demo.js +220 -0
  19. package/dist_ts_web/elements/upl-statuspage-header.js +373 -86
  20. package/dist_ts_web/elements/upl-statuspage-incidents.d.ts +22 -4
  21. package/dist_ts_web/elements/upl-statuspage-incidents.demo.d.ts +1 -0
  22. package/dist_ts_web/elements/upl-statuspage-incidents.demo.js +1147 -0
  23. package/dist_ts_web/elements/upl-statuspage-incidents.js +937 -74
  24. package/dist_ts_web/elements/upl-statuspage-pagetitle.d.ts +15 -0
  25. package/dist_ts_web/elements/upl-statuspage-pagetitle.demo.d.ts +1 -0
  26. package/dist_ts_web/elements/upl-statuspage-pagetitle.demo.js +25 -0
  27. package/dist_ts_web/elements/upl-statuspage-pagetitle.js +148 -0
  28. package/dist_ts_web/elements/upl-statuspage-statsgrid.d.ts +23 -0
  29. package/dist_ts_web/elements/upl-statuspage-statsgrid.demo.d.ts +1 -0
  30. package/dist_ts_web/elements/upl-statuspage-statsgrid.demo.js +295 -0
  31. package/dist_ts_web/elements/upl-statuspage-statsgrid.js +549 -0
  32. package/dist_ts_web/elements/upl-statuspage-statusbar.d.ts +4 -0
  33. package/dist_ts_web/elements/upl-statuspage-statusbar.demo.d.ts +1 -0
  34. package/dist_ts_web/elements/upl-statuspage-statusbar.demo.js +365 -0
  35. package/dist_ts_web/elements/upl-statuspage-statusbar.js +408 -44
  36. package/dist_ts_web/elements/upl-statuspage-statusdetails.d.ts +14 -0
  37. package/dist_ts_web/elements/upl-statuspage-statusdetails.demo.d.ts +1 -0
  38. package/dist_ts_web/elements/upl-statuspage-statusdetails.demo.js +706 -0
  39. package/dist_ts_web/elements/upl-statuspage-statusdetails.js +397 -62
  40. package/dist_ts_web/elements/upl-statuspage-statusmonth.d.ts +17 -0
  41. package/dist_ts_web/elements/upl-statuspage-statusmonth.demo.d.ts +1 -0
  42. package/dist_ts_web/elements/upl-statuspage-statusmonth.demo.js +798 -0
  43. package/dist_ts_web/elements/upl-statuspage-statusmonth.js +662 -103
  44. package/dist_ts_web/interfaces/index.d.ts +84 -0
  45. package/dist_ts_web/interfaces/index.js +4 -0
  46. package/dist_ts_web/pages/index.d.ts +4 -1
  47. package/dist_ts_web/pages/index.js +5 -2
  48. package/dist_ts_web/pages/statuspage-allgreen.d.ts +1 -0
  49. package/dist_ts_web/pages/statuspage-allgreen.js +386 -0
  50. package/dist_ts_web/pages/statuspage-demo.d.ts +1 -0
  51. package/dist_ts_web/pages/statuspage-demo.js +616 -0
  52. package/dist_ts_web/pages/statuspage-maintenance.d.ts +1 -0
  53. package/dist_ts_web/pages/statuspage-maintenance.js +544 -0
  54. package/dist_ts_web/pages/statuspage-outage.d.ts +1 -0
  55. package/dist_ts_web/pages/statuspage-outage.js +543 -0
  56. package/dist_ts_web/styles/shared.styles.d.ts +102 -0
  57. package/dist_ts_web/styles/shared.styles.js +494 -0
  58. package/dist_watch/bundle.js +52265 -32033
  59. package/dist_watch/bundle.js.map +4 -4
  60. package/dist_watch/index.html +1 -0
  61. package/npmextra.json +9 -3
  62. package/package.json +19 -19
  63. package/readme.hints.md +292 -0
  64. package/readme.md +326 -149
  65. package/readme.plan.md +261 -0
  66. package/ts_web/00_commitinfo_data.ts +1 -1
  67. package/ts_web/elements/index.ts +6 -0
  68. package/ts_web/elements/internal/uplinternal-miniheading.ts +24 -17
  69. package/ts_web/elements/upl-statuspage-assetsselector.demo.ts +607 -0
  70. package/ts_web/elements/upl-statuspage-assetsselector.ts +600 -18
  71. package/ts_web/elements/upl-statuspage-footer.demo.ts +744 -0
  72. package/ts_web/elements/upl-statuspage-footer.ts +662 -30
  73. package/ts_web/elements/upl-statuspage-header.demo.ts +241 -0
  74. package/ts_web/elements/upl-statuspage-header.ts +289 -52
  75. package/ts_web/elements/upl-statuspage-incidents.demo.ts +1216 -0
  76. package/ts_web/elements/upl-statuspage-incidents.ts +840 -26
  77. package/ts_web/elements/upl-statuspage-pagetitle.demo.ts +25 -0
  78. package/ts_web/elements/upl-statuspage-pagetitle.ts +89 -0
  79. package/ts_web/elements/upl-statuspage-statsgrid.demo.ts +315 -0
  80. package/ts_web/elements/upl-statuspage-statsgrid.ts +478 -0
  81. package/ts_web/elements/upl-statuspage-statusbar.demo.ts +393 -0
  82. package/ts_web/elements/upl-statuspage-statusbar.ts +332 -20
  83. package/ts_web/elements/upl-statuspage-statusdetails.demo.ts +754 -0
  84. package/ts_web/elements/upl-statuspage-statusdetails.ts +321 -37
  85. package/ts_web/elements/upl-statuspage-statusmonth.demo.ts +876 -0
  86. package/ts_web/elements/upl-statuspage-statusmonth.ts +584 -79
  87. package/ts_web/interfaces/index.ts +95 -0
  88. package/ts_web/pages/index.ts +4 -1
  89. package/ts_web/pages/statuspage-allgreen.ts +412 -0
  90. package/ts_web/pages/statuspage-demo.ts +653 -0
  91. package/ts_web/pages/statuspage-maintenance.ts +570 -0
  92. package/ts_web/pages/statuspage-outage.ts +568 -0
  93. package/ts_web/styles/shared.styles.ts +531 -0
  94. package/dist_ts_web/pages/page1.d.ts +0 -1
  95. package/dist_ts_web/pages/page1.js +0 -11
  96. package/ts_web/pages/page1.ts +0 -11
@@ -1,5 +1,7 @@
1
- import { DeesElement, property, html, customElement, type TemplateResult, css, cssManager } from '@design.estate/dees-element';
1
+ import { DeesElement, property, html, customElement, type TemplateResult, css, cssManager, unsafeCSS } from '@design.estate/dees-element';
2
2
  import * as domtools from '@design.estate/dees-domtools';
3
+ import * as sharedStyles from '../styles/shared.styles.js';
4
+ import { demoFunc } from './upl-statuspage-footer.demo.js';
3
5
 
4
6
  declare global {
5
7
  interface HTMLElementTagNameMap {
@@ -10,18 +12,80 @@ declare global {
10
12
  @customElement('upl-statuspage-footer')
11
13
  export class UplStatuspageFooter extends DeesElement {
12
14
  // STATIC
13
- public static demo = () => html`
14
- <upl-statuspage-footer></upl-statuspage-footer>
15
- `;
15
+ public static demo = demoFunc;
16
16
 
17
17
  // INSTANCE
18
- @property()
19
- public legalInfo: string = "https://lossless.gmbh";
18
+ @property({ type: String })
19
+ accessor companyName: string = '';
20
20
 
21
- @property({
22
- type: Boolean
23
- })
24
- public whitelabel = false;
21
+ @property({ type: String })
22
+ accessor legalUrl: string = '';
23
+
24
+ @property({ type: String })
25
+ accessor supportEmail: string = '';
26
+
27
+ @property({ type: String })
28
+ accessor statusPageUrl: string = '';
29
+
30
+ @property({ type: Boolean })
31
+ accessor whitelabel: boolean = false;
32
+
33
+ @property({ type: Number })
34
+ accessor lastUpdated: number | null = null;
35
+
36
+ @property({ type: Number })
37
+ accessor currentYear: number = new Date().getFullYear();
38
+
39
+ @property({ type: Array })
40
+ accessor socialLinks: Array<{ platform: string; url: string }> = [];
41
+
42
+ @property({ type: Array })
43
+ accessor additionalLinks: Array<{ label: string; url: string }> = [];
44
+
45
+ @property({ type: String })
46
+ accessor rssFeedUrl: string = '';
47
+
48
+ @property({ type: String })
49
+ accessor apiStatusUrl: string = '';
50
+
51
+ @property({ type: Boolean })
52
+ accessor loading: boolean = false;
53
+
54
+ @property({ type: String })
55
+ accessor errorMessage: string | null = null;
56
+
57
+ @property({ type: Boolean })
58
+ accessor offline: boolean = false;
59
+
60
+ @property({ type: String })
61
+ accessor latestStatusUpdate: string = '';
62
+
63
+ @property({ type: Boolean })
64
+ accessor enableSubscribe: boolean = false;
65
+
66
+ @property({ type: Boolean })
67
+ accessor enableReportIssue: boolean = false;
68
+
69
+ @property({ type: Boolean })
70
+ accessor enableLanguageSelector: boolean = false;
71
+
72
+ @property({ type: Boolean })
73
+ accessor enableThemeToggle: boolean = false;
74
+
75
+ @property({ type: Array })
76
+ accessor languageOptions: Array<{ code: string; label: string }> = [];
77
+
78
+ @property({ type: String })
79
+ accessor currentLanguage: string = 'en';
80
+
81
+ @property({ type: String })
82
+ accessor currentTheme: string = 'light';
83
+
84
+ @property({ type: Number })
85
+ accessor subscriberCount: number = 0;
86
+
87
+ @property({ type: Object })
88
+ accessor customBranding: { primaryColor?: string; logoUrl?: string; footerText?: string } | null = null;
25
89
 
26
90
 
27
91
  constructor() {
@@ -30,43 +94,611 @@ export class UplStatuspageFooter extends DeesElement {
30
94
 
31
95
  public static styles = [
32
96
  domtools.elementBasic.staticStyles,
97
+ sharedStyles.commonStyles,
33
98
  css`
34
99
  :host {
35
- display: block;
36
- background: ${cssManager.bdTheme('#ffffff', '#000000')};
37
- font-family: Inter;
38
- color: ${cssManager.bdTheme('#333333', '#ffffff')};
100
+ display: block;
101
+ background: ${sharedStyles.colors.background.primary};
102
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
103
+ color: ${sharedStyles.colors.text.primary};
104
+ font-size: 14px;
105
+ border-top: 1px solid ${sharedStyles.colors.border.default};
106
+ }
107
+
108
+ .container {
109
+ max-width: 1200px;
110
+ margin: 0 auto;
111
+ padding: ${unsafeCSS(sharedStyles.spacing['2xl'])} ${unsafeCSS(sharedStyles.spacing.lg)};
112
+ }
113
+
114
+ .footer-content {
115
+ display: flex;
116
+ flex-direction: column;
117
+ gap: ${unsafeCSS(sharedStyles.spacing.xl)};
118
+ }
119
+
120
+ .footer-main {
121
+ display: flex;
122
+ justify-content: space-between;
123
+ align-items: start;
124
+ gap: ${unsafeCSS(sharedStyles.spacing['2xl'])};
125
+ }
126
+
127
+ .company-info {
128
+ display: flex;
129
+ flex-direction: column;
130
+ gap: ${unsafeCSS(sharedStyles.spacing.lg)};
131
+ }
132
+
133
+ .company-name {
134
+ font-size: 16px;
135
+ font-weight: 500;
136
+ color: ${sharedStyles.colors.text.primary};
137
+ letter-spacing: -0.01em;
138
+ }
139
+
140
+ .company-links {
141
+ display: flex;
142
+ flex-wrap: wrap;
143
+ gap: ${unsafeCSS(sharedStyles.spacing.lg)};
144
+ }
145
+
146
+ .footer-link {
147
+ color: ${sharedStyles.colors.text.secondary};
148
+ text-decoration: none;
149
+ transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
150
+ font-size: 13px;
151
+ font-weight: 400;
152
+ position: relative;
153
+ }
154
+
155
+ .footer-link::after {
156
+ content: '';
157
+ position: absolute;
158
+ bottom: -2px;
159
+ left: 0;
160
+ width: 0;
161
+ height: 1px;
162
+ background: ${sharedStyles.colors.text.primary};
163
+ transition: width ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
164
+ }
165
+
166
+ .footer-link:hover {
167
+ color: ${sharedStyles.colors.text.primary};
168
+ }
169
+
170
+ .footer-link:hover::after {
171
+ width: 100%;
172
+ }
173
+
174
+ .footer-actions {
175
+ display: flex;
176
+ flex-direction: column;
177
+ gap: ${unsafeCSS(sharedStyles.spacing.md)};
178
+ }
179
+
180
+ .action-button {
181
+ padding: 8px 16px;
182
+ height: 36px;
183
+ border: 1px solid ${sharedStyles.colors.border.default};
184
+ background: ${sharedStyles.colors.background.card};
185
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
186
+ cursor: pointer;
187
+ text-align: center;
188
+ transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
189
+ white-space: nowrap;
190
+ font-size: 13px;
191
+ font-weight: 500;
192
+ color: ${sharedStyles.colors.text.primary};
193
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
194
+ display: inline-flex;
195
+ align-items: center;
196
+ justify-content: center;
197
+ gap: 6px;
198
+ }
199
+
200
+ .action-button:hover {
201
+ background: ${sharedStyles.colors.background.secondary};
202
+ border-color: ${sharedStyles.colors.border.muted};
203
+ transform: translateY(-1px);
204
+ box-shadow: ${unsafeCSS(sharedStyles.shadows.sm)};
205
+ }
206
+
207
+ .action-button:active {
208
+ transform: translateY(0);
209
+ box-shadow: none;
210
+ }
211
+
212
+ .footer-bottom {
213
+ display: flex;
214
+ justify-content: space-between;
215
+ align-items: center;
216
+ padding-top: ${unsafeCSS(sharedStyles.spacing.lg)};
217
+ margin-top: ${unsafeCSS(sharedStyles.spacing.lg)};
218
+ border-top: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
219
+ }
220
+
221
+ .footer-meta {
222
+ display: flex;
223
+ gap: ${unsafeCSS(sharedStyles.spacing.lg)};
224
+ align-items: center;
225
+ flex-wrap: wrap;
226
+ }
227
+
228
+ .social-links {
229
+ display: flex;
230
+ gap: ${unsafeCSS(sharedStyles.spacing.sm)};
231
+ align-items: center;
232
+ }
233
+
234
+ .social-link {
235
+ display: inline-flex;
236
+ align-items: center;
237
+ justify-content: center;
238
+ width: 34px;
239
+ height: 34px;
240
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
241
+ transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
242
+ color: ${sharedStyles.colors.text.muted};
243
+ border: 1px solid transparent;
244
+ }
245
+
246
+ .social-link:hover {
247
+ color: ${sharedStyles.colors.text.primary};
248
+ background: ${sharedStyles.colors.background.secondary};
249
+ border-color: ${sharedStyles.colors.border.default};
250
+ transform: translateY(-2px);
251
+ box-shadow: ${unsafeCSS(sharedStyles.shadows.sm)};
252
+ }
253
+
254
+ .social-link:active {
255
+ transform: translateY(0);
256
+ }
257
+
258
+ .social-link svg {
259
+ width: 16px;
260
+ height: 16px;
261
+ fill: currentColor;
262
+ transition: transform ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
263
+ }
264
+
265
+ .social-link:hover svg {
266
+ transform: scale(1.1);
267
+ }
268
+
269
+ .copyright {
270
+ font-size: 13px;
271
+ color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
272
+ }
273
+
274
+ .last-updated {
275
+ font-size: 12px;
276
+ color: ${cssManager.bdTheme('#9ca3af', '#71717a')};
277
+ }
278
+
279
+ .powered-by {
280
+ font-size: 12px;
281
+ color: ${sharedStyles.colors.text.muted};
282
+ text-align: right;
283
+ }
284
+
285
+ .powered-by a {
286
+ color: ${sharedStyles.colors.text.secondary};
287
+ text-decoration: none;
288
+ transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
289
+ font-weight: 500;
290
+ position: relative;
291
+ }
292
+
293
+ .powered-by a::after {
294
+ content: '';
295
+ position: absolute;
296
+ bottom: -1px;
297
+ left: 0;
298
+ width: 0;
299
+ height: 1px;
300
+ background: ${sharedStyles.colors.text.primary};
301
+ transition: width ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
302
+ }
303
+
304
+ .powered-by a:hover {
305
+ color: ${sharedStyles.colors.text.primary};
306
+ }
307
+
308
+ .powered-by a:hover::after {
309
+ width: 100%;
310
+ }
311
+
312
+ .status-update {
313
+ padding: 12px 16px;
314
+ background: ${cssManager.bdTheme('#f9fafb', '#18181b')};
315
+ border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
316
+ border-radius: 6px;
317
+ font-size: 13px;
318
+ margin-bottom: ${unsafeCSS(sharedStyles.spacing.lg)};
319
+ line-height: 1.5;
320
+ color: ${cssManager.bdTheme('#4b5563', '#d1d5db')};
321
+ }
322
+
323
+ .language-selector {
324
+ position: relative;
325
+ }
326
+
327
+ .language-selector select {
328
+ padding: ${unsafeCSS(sharedStyles.spacing.xs)} ${unsafeCSS(sharedStyles.spacing.sm)};
329
+ border: 1px solid ${sharedStyles.colors.border.default};
330
+ background: ${sharedStyles.colors.background.primary};
331
+ color: ${sharedStyles.colors.text.primary};
332
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
333
+ font-size: 14px;
334
+ cursor: pointer;
335
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
336
+ }
337
+
338
+ .theme-toggle {
339
+ cursor: pointer;
340
+ padding: ${unsafeCSS(sharedStyles.spacing.xs)} ${unsafeCSS(sharedStyles.spacing.sm)};
341
+ border: 1px solid ${sharedStyles.colors.border.default};
342
+ background: transparent;
343
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
344
+ font-size: 14px;
345
+ transition: all 0.2s ease;
346
+ color: ${sharedStyles.colors.text.primary};
347
+ font-family: ${unsafeCSS(sharedStyles.fonts.base)};
348
+ }
349
+
350
+ .theme-toggle:hover {
351
+ background: ${sharedStyles.colors.background.secondary};
352
+ border-color: ${sharedStyles.colors.border.muted};
353
+ }
354
+
355
+ .subscribe-wrapper {
356
+ display: flex;
357
+ flex-direction: column;
358
+ align-items: center;
359
+ gap: ${unsafeCSS(sharedStyles.spacing.xs)};
360
+ }
361
+
362
+ .subscriber-count {
363
+ font-size: 12px;
364
+ color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
365
+ }
366
+
367
+ .error-message {
368
+ padding: ${unsafeCSS(sharedStyles.spacing.md)};
369
+ background: ${cssManager.bdTheme('#fee9e9', '#7f1d1d')};
370
+ color: ${cssManager.bdTheme('#dc2626', '#fca5a5')};
371
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
372
+ margin-bottom: ${unsafeCSS(sharedStyles.spacing.lg)};
373
+ font-size: 14px;
374
+ border: 1px solid ${cssManager.bdTheme('#fecaca', '#991b1b')};
375
+ }
376
+
377
+ .offline-indicator {
378
+ display: inline-flex;
379
+ align-items: center;
380
+ gap: ${unsafeCSS(sharedStyles.spacing.xs)};
381
+ padding: ${unsafeCSS(sharedStyles.spacing.xs)} ${unsafeCSS(sharedStyles.spacing.md)};
382
+ background: ${sharedStyles.colors.status.degraded};
383
+ color: white;
384
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.full)};
385
+ font-size: 13px;
386
+ font-weight: 500;
387
+ }
388
+
389
+ .loading-skeleton {
390
+ height: 200px;
391
+ background: ${cssManager.bdTheme(
392
+ 'linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%)',
393
+ 'linear-gradient(90deg, #1f1f1f 25%, #262626 50%, #1f1f1f 75%)'
394
+ )};
395
+ background-size: 200% 100%;
396
+ animation: loading 1.5s infinite;
397
+ border-radius: ${unsafeCSS(sharedStyles.borderRadius.md)};
398
+ }
399
+
400
+ @keyframes loading {
401
+ 0% { background-position: 200% 0; }
402
+ 100% { background-position: -200% 0; }
403
+ }
404
+
405
+ .additional-links {
406
+ display: flex;
407
+ flex-wrap: wrap;
408
+ gap: ${unsafeCSS(sharedStyles.spacing.md)};
409
+ margin-top: ${unsafeCSS(sharedStyles.spacing.md)};
410
+ }
411
+
412
+ .additional-link {
413
+ font-size: 13px;
414
+ color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
415
+ text-decoration: none;
416
+ transition: color 0.15s ease;
417
+ }
418
+
419
+ .additional-link:hover {
420
+ color: ${sharedStyles.colors.text.primary};
421
+ }
422
+
423
+ @media (max-width: 640px) {
424
+ .container {
425
+ padding: ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.md)};
426
+ }
427
+
428
+ .footer-main {
429
+ flex-direction: column;
430
+ gap: ${unsafeCSS(sharedStyles.spacing.lg)};
431
+ }
432
+
433
+ .footer-bottom {
434
+ flex-direction: column;
435
+ gap: ${unsafeCSS(sharedStyles.spacing.lg)};
436
+ align-items: start;
437
+ }
438
+
439
+ .footer-meta {
440
+ flex-direction: column;
441
+ align-items: start;
442
+ gap: ${unsafeCSS(sharedStyles.spacing.md)};
39
443
  }
40
444
 
41
- .mainbox {
42
- max-width: 900px;
43
- margin: auto;
44
- padding-top: 20px;
45
- padding-bottom: 20px;
445
+ .company-links {
446
+ flex-direction: column;
447
+ gap: ${unsafeCSS(sharedStyles.spacing.sm)};
46
448
  }
47
-
449
+
450
+ .powered-by {
451
+ text-align: left;
452
+ }
453
+ }
48
454
  `
49
455
  ]
50
456
 
51
457
  public render(): TemplateResult {
458
+ if (this.loading) {
459
+ return html`
460
+ <div class="container">
461
+ <div class="loading-skeleton"></div>
462
+ </div>
463
+ `;
464
+ }
465
+
52
466
  return html`
53
- ${domtools.elementBasic.styles}
54
- <style></style>
55
- <div class="mainbox">
56
- Hi there
467
+ <div class="container">
468
+ <div class="footer-content">
469
+ ${this.errorMessage ? html`
470
+ <div class="error-message">${this.errorMessage}</div>
471
+ ` : ''}
472
+
473
+ ${this.offline ? html`
474
+ <div class="offline-indicator">
475
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
476
+ <path d="M6 2V6M6 10H6.01M11 6C11 8.76142 8.76142 11 6 11C3.23858 11 1 8.76142 1 6C1 3.23858 3.23858 1 6 1C8.76142 1 11 3.23858 11 6Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
477
+ </svg>
478
+ You are currently offline
479
+ </div>
480
+ ` : ''}
481
+
482
+ ${this.latestStatusUpdate ? html`
483
+ <div class="status-update">
484
+ Latest Update: ${this.latestStatusUpdate}
485
+ </div>
486
+ ` : ''}
487
+
488
+ <div class="footer-main">
489
+ <div class="company-info">
490
+ ${this.companyName ? html`
491
+ <div class="company-name">${this.companyName}</div>
492
+ ` : ''}
493
+
494
+ <div class="company-links">
495
+ ${this.legalUrl && this.isValidUrl(this.legalUrl) ? html`
496
+ <a href="${this.legalUrl}" class="footer-link" @click=${(e: Event) => this.handleLinkClick(e, 'legal', this.legalUrl)}>Legal</a>
497
+ ` : ''}
498
+
499
+ ${this.supportEmail && this.isValidEmail(this.supportEmail) ? html`
500
+ <a href="mailto:${this.supportEmail}" class="footer-link" @click=${(e: Event) => this.handleLinkClick(e, 'support', this.supportEmail)}>Support</a>
501
+ ` : ''}
502
+
503
+ ${this.statusPageUrl && this.isValidUrl(this.statusPageUrl) ? html`
504
+ <a href="${this.statusPageUrl}" class="footer-link" @click=${(e: Event) => this.handleLinkClick(e, 'status', this.statusPageUrl)}>Status Page</a>
505
+ ` : ''}
506
+
507
+ ${this.rssFeedUrl && this.isValidUrl(this.rssFeedUrl) ? html`
508
+ <a href="${this.rssFeedUrl}" class="footer-link" @click=${(e: Event) => this.handleLinkClick(e, 'rss', this.rssFeedUrl)}>RSS Feed</a>
509
+ ` : ''}
510
+
511
+ ${this.apiStatusUrl && this.isValidUrl(this.apiStatusUrl) ? html`
512
+ <a href="${this.apiStatusUrl}" class="footer-link" @click=${(e: Event) => this.handleLinkClick(e, 'api', this.apiStatusUrl)}>API Status</a>
513
+ ` : ''}
514
+ </div>
515
+
516
+ ${this.additionalLinks && this.additionalLinks.length > 0 ? html`
517
+ <div class="additional-links">
518
+ ${this.additionalLinks.map(link => html`
519
+ ${link.label && link.url && this.isValidUrl(link.url) ? html`
520
+ <a href="${link.url}" class="additional-link" @click=${(e: Event) => this.handleLinkClick(e, 'additional', link.url, link.label)}>
521
+ ${link.label}
522
+ </a>
523
+ ` : ''}
524
+ `)}
525
+ </div>
526
+ ` : ''}
527
+ </div>
528
+
529
+ <div class="footer-actions">
530
+ ${this.enableSubscribe ? html`
531
+ <div class="subscribe-wrapper">
532
+ <button class="action-button" @click=${this.handleSubscribeClick}>
533
+ Subscribe to Updates
534
+ </button>
535
+ ${this.subscriberCount > 0 ? html`
536
+ <div class="subscriber-count">${this.subscriberCount.toLocaleString()} subscribers</div>
537
+ ` : ''}
538
+ </div>
539
+ ` : ''}
540
+
541
+ ${this.enableReportIssue ? html`
542
+ <button class="action-button" @click=${this.handleReportIncidentClick}>
543
+ Report an Issue
544
+ </button>
545
+ ` : ''}
546
+
547
+ ${this.enableLanguageSelector && this.languageOptions.length > 0 ? html`
548
+ <div class="language-selector">
549
+ <select @change=${this.handleLanguageChange} .value=${this.currentLanguage}>
550
+ ${this.languageOptions.map(option => html`
551
+ <option value="${option.code}" ?selected=${option.code === this.currentLanguage}>
552
+ ${option.label}
553
+ </option>
554
+ `)}
555
+ </select>
556
+ </div>
557
+ ` : ''}
558
+
559
+ ${this.enableThemeToggle ? html`
560
+ <button class="theme-toggle" @click=${this.handleThemeToggle}>
561
+ ${this.currentTheme === 'dark' ? '☀️' : '🌙'} ${this.currentTheme === 'dark' ? 'Light' : 'Dark'} Mode
562
+ </button>
563
+ ` : ''}
564
+ </div>
565
+ </div>
566
+
567
+ <div class="footer-bottom">
568
+ <div class="footer-meta">
569
+ <div class="copyright">
570
+ © ${this.currentYear} ${this.companyName || 'Status Page'}
571
+ </div>
572
+
573
+ ${this.lastUpdated ? html`
574
+ <div class="last-updated">
575
+ Last updated: ${this.formatLastUpdated()}
576
+ </div>
577
+ ` : ''}
578
+
579
+ ${this.socialLinks && this.socialLinks.length > 0 ? html`
580
+ <div class="social-links">
581
+ ${this.socialLinks.map(social => html`
582
+ ${social.platform && social.url && this.isValidUrl(social.url) ? html`
583
+ <a href="${social.url}" class="social-link" title="${social.platform}" @click=${(e: Event) => this.handleLinkClick(e, 'social', social.url, social.platform)}>
584
+ ${this.getSocialIcon(social.platform)}
585
+ </a>
586
+ ` : ''}
587
+ `)}
588
+ </div>
589
+ ` : ''}
590
+ </div>
591
+
592
+ ${!this.whitelabel ? html`
593
+ <div class="powered-by">
594
+ ${this.customBranding?.footerText || html`
595
+ Powered by <a href="https://uptime.link" target="_blank" @click=${(e: Event) => this.handleLinkClick(e, 'powered-by', 'https://uptime.link')}>uptime.link</a>
596
+ `}
597
+ </div>
598
+ ` : ''}
599
+ </div>
600
+ </div>
57
601
  </div>
58
602
  `;
59
603
  }
60
604
 
605
+ private isValidUrl(url: string): boolean {
606
+ if (!url) return false;
607
+ try {
608
+ new URL(url);
609
+ return true;
610
+ } catch {
611
+ return url.startsWith('#') || url.startsWith('/');
612
+ }
613
+ }
614
+
615
+ private isValidEmail(email: string): boolean {
616
+ if (!email) return false;
617
+ return email.includes('@');
618
+ }
619
+
620
+ private formatLastUpdated(): string {
621
+ if (!this.lastUpdated) return 'Never';
622
+ const date = new Date(this.lastUpdated);
623
+ const now = Date.now();
624
+ const diff = now - this.lastUpdated;
625
+
626
+ if (diff < 60000) {
627
+ return 'Just now';
628
+ } else if (diff < 3600000) {
629
+ const minutes = Math.floor(diff / 60000);
630
+ return `${minutes} minute${minutes !== 1 ? 's' : ''} ago`;
631
+ } else if (diff < 86400000) {
632
+ const hours = Math.floor(diff / 3600000);
633
+ return `${hours} hour${hours !== 1 ? 's' : ''} ago`;
634
+ } else {
635
+ return date.toLocaleDateString();
636
+ }
637
+ }
638
+
639
+ private getSocialIcon(platform: string): TemplateResult {
640
+ const icons: Record<string, TemplateResult> = {
641
+ twitter: html`<svg viewBox="0 0 24 24"><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg>`,
642
+ github: html`<svg viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>`,
643
+ linkedin: html`<svg viewBox="0 0 24 24"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>`,
644
+ facebook: html`<svg viewBox="0 0 24 24"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>`,
645
+ youtube: html`<svg viewBox="0 0 24 24"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>`,
646
+ instagram: html`<svg viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zM5.838 12a6.162 6.162 0 1 1 12.324 0 6.162 6.162 0 0 1-12.324 0zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm4.965-10.405a1.44 1.44 0 1 1 2.881.001 1.44 1.44 0 0 1-2.881-.001z"/></svg>`,
647
+ slack: html`<svg viewBox="0 0 24 24"><path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z"/></svg>`,
648
+ discord: html`<svg viewBox="0 0 24 24"><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg>`,
649
+ generic: html`<svg viewBox="0 0 24 24"><path d="M13.46,12L19,17.54V19H17.54L12,13.46L6.46,19H5V17.54L10.54,12L5,6.46V5H6.46L12,10.54L17.54,5H19V6.46L13.46,12Z"/></svg>`
650
+ };
651
+ return icons[platform.toLowerCase()] || icons.generic;
652
+ }
653
+
654
+ private handleLinkClick(event: Event, type: string, url: string, label?: string) {
655
+ this.dispatchEvent(new CustomEvent('footerLinkClick', {
656
+ detail: { type, url, label },
657
+ bubbles: true,
658
+ composed: true
659
+ }));
660
+ }
661
+
662
+ private handleSubscribeClick() {
663
+ this.dispatchEvent(new CustomEvent('subscribeClick', {
664
+ bubbles: true,
665
+ composed: true
666
+ }));
667
+ }
668
+
669
+ private handleReportIncidentClick() {
670
+ this.dispatchEvent(new CustomEvent('reportIncidentClick', {
671
+ bubbles: true,
672
+ composed: true
673
+ }));
674
+ }
675
+
676
+ private handleLanguageChange(event: Event) {
677
+ const select = event.target as HTMLSelectElement;
678
+ const language = select.value;
679
+ this.currentLanguage = language;
680
+ this.dispatchEvent(new CustomEvent('languageChange', {
681
+ detail: { language },
682
+ bubbles: true,
683
+ composed: true
684
+ }));
685
+ }
686
+
687
+ private handleThemeToggle() {
688
+ const newTheme = this.currentTheme === 'dark' ? 'light' : 'dark';
689
+ this.currentTheme = newTheme;
690
+ this.dispatchEvent(new CustomEvent('themeToggle', {
691
+ detail: { theme: newTheme },
692
+ bubbles: true,
693
+ composed: true
694
+ }));
695
+ }
696
+
61
697
  public dispatchReportNewIncident() {
62
- this.dispatchEvent(new CustomEvent('reportNewIncident', {
63
-
64
- }))
698
+ this.handleReportIncidentClick();
65
699
  }
66
700
 
67
701
  public dispatchStatusSubscribe() {
68
- this.dispatchEvent(new CustomEvent('statusSubscribe', {
69
-
70
- }))
702
+ this.handleSubscribeClick();
71
703
  }
72
704
  }