@startup-api/cloudflare 0.2.0 → 0.3.1
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/README.md +28 -1
- package/package.json +1 -1
- package/public/users/accounts.html +4 -7
- package/public/users/admin/index.html +73 -19
- package/public/users/power-strip.js +279 -42
- package/public/users/profile.html +5 -8
- package/public/users/style.css +149 -59
- package/src/PowerStrip.ts +1 -1
- package/src/createStartupAPI.ts +48 -5
- package/src/policy/accessPolicy.ts +4 -3
- package/src/schemas/policy.ts +25 -1
|
@@ -5,6 +5,10 @@ class PowerStrip extends HTMLElement {
|
|
|
5
5
|
this.basePath = this.detectBasePath();
|
|
6
6
|
this.user = null;
|
|
7
7
|
this.accounts = [];
|
|
8
|
+
// Theme watchers, wired up once on connect and torn down on disconnect.
|
|
9
|
+
this._mediaQuery = null;
|
|
10
|
+
this._onPreferenceChange = null;
|
|
11
|
+
this._pageObserver = null;
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
detectBasePath() {
|
|
@@ -27,17 +31,130 @@ class PowerStrip extends HTMLElement {
|
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
async connectedCallback() {
|
|
34
|
+
// Resolve the theme before the first paint so the strip never flashes the
|
|
35
|
+
// wrong colors, then keep it in sync with the page from here on.
|
|
36
|
+
this.applyTheme();
|
|
37
|
+
this.watchThemeChanges();
|
|
30
38
|
await this.fetchUser();
|
|
31
39
|
this.render();
|
|
32
40
|
this.addEventListeners();
|
|
33
41
|
}
|
|
34
42
|
|
|
43
|
+
disconnectedCallback() {
|
|
44
|
+
if (this._mediaQuery && this._onPreferenceChange) {
|
|
45
|
+
this._mediaQuery.removeEventListener('change', this._onPreferenceChange);
|
|
46
|
+
}
|
|
47
|
+
if (this._pageObserver) {
|
|
48
|
+
this._pageObserver.disconnect();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
35
52
|
async refresh() {
|
|
36
53
|
await this.fetchUser();
|
|
37
54
|
this.render();
|
|
38
55
|
this.addEventListeners();
|
|
39
56
|
}
|
|
40
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Decide whether the strip should render light or dark by measuring the
|
|
60
|
+
* actual background the strip sits on. This makes the strip match the page
|
|
61
|
+
* regardless of *how* the page chose its theme — a hardcoded dark page, a
|
|
62
|
+
* hardcoded light page, or a page that respects the user's OS preference all
|
|
63
|
+
* resolve to a concrete background color we can read here. When nothing
|
|
64
|
+
* conclusive is found (e.g. a transparent body over an image) we fall back to
|
|
65
|
+
* the user's OS-level color-scheme preference.
|
|
66
|
+
*/
|
|
67
|
+
detectPageTheme() {
|
|
68
|
+
const bg = this.getEffectiveBackgroundColor();
|
|
69
|
+
if (bg) {
|
|
70
|
+
return this.isDarkColor(bg) ? 'dark' : 'light';
|
|
71
|
+
}
|
|
72
|
+
return this.prefersDark() ? 'dark' : 'light';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
prefersDark() {
|
|
76
|
+
return typeof window.matchMedia === 'function' && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Walk up from the strip's placement looking for the first ancestor that
|
|
81
|
+
* paints an opaque (or partly opaque) background, mirroring how the strip is
|
|
82
|
+
* actually composited over the page. Falls back to the document element.
|
|
83
|
+
*/
|
|
84
|
+
getEffectiveBackgroundColor() {
|
|
85
|
+
let el = this.parentElement;
|
|
86
|
+
while (el) {
|
|
87
|
+
const color = getComputedStyle(el).backgroundColor;
|
|
88
|
+
if (color && !this.isTransparentColor(color)) {
|
|
89
|
+
return color;
|
|
90
|
+
}
|
|
91
|
+
el = el.parentElement;
|
|
92
|
+
}
|
|
93
|
+
const rootColor = getComputedStyle(document.documentElement).backgroundColor;
|
|
94
|
+
if (rootColor && !this.isTransparentColor(rootColor)) {
|
|
95
|
+
return rootColor;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
parseColor(color) {
|
|
101
|
+
const parts = (color.match(/[\d.]+/g) || []).map(Number);
|
|
102
|
+
if (parts.length < 3) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return { r: parts[0], g: parts[1], b: parts[2], a: parts.length >= 4 ? parts[3] : 1 };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
isTransparentColor(color) {
|
|
109
|
+
if (!color || color === 'transparent') {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
const c = this.parseColor(color);
|
|
113
|
+
return !c || c.a === 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
isDarkColor(color) {
|
|
117
|
+
const c = this.parseColor(color);
|
|
118
|
+
if (!c) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
// Perceived luminance (ITU-R BT.601). Below the midpoint reads as "dark".
|
|
122
|
+
const luminance = (0.299 * c.r + 0.587 * c.g + 0.114 * c.b) / 255;
|
|
123
|
+
return luminance < 0.5;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Tag the host with the resolved theme. CSS keys all of its colors off this
|
|
128
|
+
* attribute, so updating it is enough to re-theme the whole shadow tree
|
|
129
|
+
* (panel, dialogs and all) without re-rendering or losing dialog state.
|
|
130
|
+
*/
|
|
131
|
+
applyTheme() {
|
|
132
|
+
const theme = this.detectPageTheme();
|
|
133
|
+
if (this.getAttribute('data-resolved-theme') !== theme) {
|
|
134
|
+
this.setAttribute('data-resolved-theme', theme);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
watchThemeChanges() {
|
|
139
|
+
// React to the user flipping their OS-level color-scheme preference.
|
|
140
|
+
if (typeof window.matchMedia === 'function') {
|
|
141
|
+
this._mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
142
|
+
this._onPreferenceChange = () => this.applyTheme();
|
|
143
|
+
this._mediaQuery.addEventListener('change', this._onPreferenceChange);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// React to the page re-theming itself at runtime — e.g. a theme toggle
|
|
147
|
+
// flipping data-theme/class/style on <html> or <body>.
|
|
148
|
+
if (typeof MutationObserver === 'function') {
|
|
149
|
+
this._pageObserver = new MutationObserver(() => this.applyTheme());
|
|
150
|
+
const observeOptions = { attributes: true, attributeFilter: ['data-theme', 'class', 'style'] };
|
|
151
|
+
this._pageObserver.observe(document.documentElement, observeOptions);
|
|
152
|
+
if (document.body) {
|
|
153
|
+
this._pageObserver.observe(document.body, observeOptions);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
41
158
|
async fetchUser() {
|
|
42
159
|
try {
|
|
43
160
|
const res = await fetch(`${this.basePath}/api/me`);
|
|
@@ -214,8 +331,8 @@ class PowerStrip extends HTMLElement {
|
|
|
214
331
|
|
|
215
332
|
const avatarContent = this.user.profile.picture
|
|
216
333
|
? `<img src="${this.user.profile.picture}" alt="${this.user.profile.name}" title="${this.user.profile.name}" class="avatar" width="16" height="16" />`
|
|
217
|
-
: `<div class="avatar placeholder"
|
|
218
|
-
<svg viewBox="0 0 24 24" style="width: 12px; height: 12px;
|
|
334
|
+
: `<div class="avatar placeholder">
|
|
335
|
+
<svg viewBox="0 0 24 24" style="width: 12px; height: 12px;">
|
|
219
336
|
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
|
|
220
337
|
</svg>
|
|
221
338
|
</div>`;
|
|
@@ -251,6 +368,108 @@ class PowerStrip extends HTMLElement {
|
|
|
251
368
|
:host {
|
|
252
369
|
display: block;
|
|
253
370
|
font-family: system-ui, -apple-system, sans-serif;
|
|
371
|
+
|
|
372
|
+
/* Light theme (the default). Every color in the strip is keyed off
|
|
373
|
+
these custom properties so the whole shadow tree can be re-themed
|
|
374
|
+
by flipping a single set of variables. The dark overrides live in
|
|
375
|
+
the rules below — one driven by the user's OS preference (used as a
|
|
376
|
+
flash-free fallback) and one driven by the [data-resolved-theme]
|
|
377
|
+
attribute the component measures and sets at runtime. */
|
|
378
|
+
--ps-panel-bg: rgba(255, 255, 255, 0.85);
|
|
379
|
+
--ps-panel-border: rgba(0, 0, 0, 0.15);
|
|
380
|
+
--ps-panel-shadow: 0 0.0625rem 0.25rem rgba(0, 0, 0, 0.25);
|
|
381
|
+
--ps-text: #333;
|
|
382
|
+
--ps-text-muted: #444;
|
|
383
|
+
--ps-accent: #1a73e8;
|
|
384
|
+
--ps-danger: #d93025;
|
|
385
|
+
--ps-warning: #c77700;
|
|
386
|
+
--ps-hover-bg: rgba(0, 0, 0, 0.06);
|
|
387
|
+
--ps-avatar-bg: #eee;
|
|
388
|
+
--ps-avatar-fg: #999;
|
|
389
|
+
|
|
390
|
+
--ps-dialog-bg: #fff;
|
|
391
|
+
--ps-dialog-text: #333;
|
|
392
|
+
--ps-dialog-shadow: 0 0.625rem 1.5625rem rgba(0, 0, 0, 0.25);
|
|
393
|
+
--ps-dialog-muted: #999;
|
|
394
|
+
--ps-dialog-hover-bg: #f0f0f0;
|
|
395
|
+
--ps-surface-bg: #fff;
|
|
396
|
+
--ps-surface-border: #ddd;
|
|
397
|
+
--ps-surface-soft-border: #eee;
|
|
398
|
+
--ps-surface-hover-bg: #f5f5f5;
|
|
399
|
+
--ps-neutral-btn-bg: #fff;
|
|
400
|
+
--ps-neutral-btn-text: #3c4043;
|
|
401
|
+
--ps-neutral-btn-border: #dadce0;
|
|
402
|
+
--ps-neutral-btn-hover-bg: #f8f9fa;
|
|
403
|
+
--ps-active-bg: #e8f0fe;
|
|
404
|
+
|
|
405
|
+
color-scheme: light;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* Shared dark palette. Applied either when the page has been measured
|
|
409
|
+
as dark, or — before/without measurement — when the user's OS asks
|
|
410
|
+
for dark and the page hasn't been explicitly resolved to light. */
|
|
411
|
+
:host([data-resolved-theme='dark']) {
|
|
412
|
+
--ps-panel-bg: rgba(32, 33, 36, 0.92);
|
|
413
|
+
--ps-panel-border: rgba(255, 255, 255, 0.22);
|
|
414
|
+
--ps-panel-shadow: 0 0.0625rem 0.3125rem rgba(0, 0, 0, 0.65);
|
|
415
|
+
--ps-text: #e8eaed;
|
|
416
|
+
--ps-text-muted: #dadce0;
|
|
417
|
+
--ps-accent: #8ab4f8;
|
|
418
|
+
--ps-danger: #f28b82;
|
|
419
|
+
--ps-warning: #fdd663;
|
|
420
|
+
--ps-hover-bg: rgba(255, 255, 255, 0.12);
|
|
421
|
+
--ps-avatar-bg: #5f6368;
|
|
422
|
+
--ps-avatar-fg: #dadce0;
|
|
423
|
+
|
|
424
|
+
--ps-dialog-bg: #2a2b2e;
|
|
425
|
+
--ps-dialog-text: #e8eaed;
|
|
426
|
+
--ps-dialog-shadow: 0 0.625rem 1.5625rem rgba(0, 0, 0, 0.7);
|
|
427
|
+
--ps-dialog-muted: #9aa0a6;
|
|
428
|
+
--ps-dialog-hover-bg: #3c4043;
|
|
429
|
+
--ps-surface-bg: #303134;
|
|
430
|
+
--ps-surface-border: #5f6368;
|
|
431
|
+
--ps-surface-soft-border: #3c4043;
|
|
432
|
+
--ps-surface-hover-bg: #3c4043;
|
|
433
|
+
--ps-neutral-btn-bg: #303134;
|
|
434
|
+
--ps-neutral-btn-text: #e8eaed;
|
|
435
|
+
--ps-neutral-btn-border: #5f6368;
|
|
436
|
+
--ps-neutral-btn-hover-bg: #3c4043;
|
|
437
|
+
--ps-active-bg: #283142;
|
|
438
|
+
|
|
439
|
+
color-scheme: dark;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
@media (prefers-color-scheme: dark) {
|
|
443
|
+
:host(:not([data-resolved-theme='light'])) {
|
|
444
|
+
--ps-panel-bg: rgba(32, 33, 36, 0.92);
|
|
445
|
+
--ps-panel-border: rgba(255, 255, 255, 0.22);
|
|
446
|
+
--ps-panel-shadow: 0 0.0625rem 0.3125rem rgba(0, 0, 0, 0.65);
|
|
447
|
+
--ps-text: #e8eaed;
|
|
448
|
+
--ps-text-muted: #dadce0;
|
|
449
|
+
--ps-accent: #8ab4f8;
|
|
450
|
+
--ps-danger: #f28b82;
|
|
451
|
+
--ps-warning: #fdd663;
|
|
452
|
+
--ps-hover-bg: rgba(255, 255, 255, 0.12);
|
|
453
|
+
--ps-avatar-bg: #5f6368;
|
|
454
|
+
--ps-avatar-fg: #dadce0;
|
|
455
|
+
|
|
456
|
+
--ps-dialog-bg: #2a2b2e;
|
|
457
|
+
--ps-dialog-text: #e8eaed;
|
|
458
|
+
--ps-dialog-shadow: 0 0.625rem 1.5625rem rgba(0, 0, 0, 0.7);
|
|
459
|
+
--ps-dialog-muted: #9aa0a6;
|
|
460
|
+
--ps-dialog-hover-bg: #3c4043;
|
|
461
|
+
--ps-surface-bg: #303134;
|
|
462
|
+
--ps-surface-border: #5f6368;
|
|
463
|
+
--ps-surface-soft-border: #3c4043;
|
|
464
|
+
--ps-surface-hover-bg: #3c4043;
|
|
465
|
+
--ps-neutral-btn-bg: #303134;
|
|
466
|
+
--ps-neutral-btn-text: #e8eaed;
|
|
467
|
+
--ps-neutral-btn-border: #5f6368;
|
|
468
|
+
--ps-neutral-btn-hover-bg: #3c4043;
|
|
469
|
+
--ps-active-bg: #283142;
|
|
470
|
+
|
|
471
|
+
color-scheme: dark;
|
|
472
|
+
}
|
|
254
473
|
}
|
|
255
474
|
|
|
256
475
|
/* Honor the native [hidden] attribute so authors can load the script
|
|
@@ -272,10 +491,16 @@ class PowerStrip extends HTMLElement {
|
|
|
272
491
|
height: 1.3rem;
|
|
273
492
|
padding: 0.0625rem;
|
|
274
493
|
animation: fadeIn 0.4s ease-out;
|
|
275
|
-
background-color:
|
|
494
|
+
background-color: var(--ps-panel-bg);
|
|
495
|
+
/* A contrasting border keeps the chip distinguishable even when its
|
|
496
|
+
panel color happens to be close to the page background. */
|
|
497
|
+
border: 0.0625rem solid var(--ps-panel-border);
|
|
498
|
+
border-top: none;
|
|
499
|
+
border-right: none;
|
|
276
500
|
border-radius: 0 0 0 0.3rem;
|
|
277
|
-
box-shadow:
|
|
501
|
+
box-shadow: var(--ps-panel-shadow);
|
|
278
502
|
font-size: 1rem;
|
|
503
|
+
backdrop-filter: blur(0.25rem);
|
|
279
504
|
}
|
|
280
505
|
|
|
281
506
|
.trigger {
|
|
@@ -285,7 +510,7 @@ class PowerStrip extends HTMLElement {
|
|
|
285
510
|
border-radius: 0.25rem;
|
|
286
511
|
font-size: 0.8rem;
|
|
287
512
|
font-weight: 500;
|
|
288
|
-
color:
|
|
513
|
+
color: var(--ps-text-muted);
|
|
289
514
|
text-decoration: none;
|
|
290
515
|
border: none;
|
|
291
516
|
background: transparent;
|
|
@@ -293,19 +518,19 @@ class PowerStrip extends HTMLElement {
|
|
|
293
518
|
}
|
|
294
519
|
|
|
295
520
|
.trigger:hover {
|
|
296
|
-
background-color:
|
|
521
|
+
background-color: var(--ps-hover-bg);
|
|
297
522
|
text-decoration: underline;
|
|
298
|
-
color:
|
|
523
|
+
color: var(--ps-accent);
|
|
299
524
|
}
|
|
300
|
-
|
|
525
|
+
|
|
301
526
|
.switch-btn {
|
|
302
|
-
color:
|
|
527
|
+
color: var(--ps-accent);
|
|
303
528
|
}
|
|
304
529
|
|
|
305
530
|
svg.bolt, ::slotted(svg) {
|
|
306
531
|
width: 1rem !important;
|
|
307
532
|
height: 1rem !important;
|
|
308
|
-
fill: #ffcc00 !important;
|
|
533
|
+
fill: #ffcc00 !important;
|
|
309
534
|
filter: drop-shadow(0.0625rem 0.0625rem 0.0625rem rgba(0, 0, 0, 0.5));
|
|
310
535
|
flex-shrink: 0;
|
|
311
536
|
}
|
|
@@ -330,6 +555,17 @@ class PowerStrip extends HTMLElement {
|
|
|
330
555
|
object-fit: cover;
|
|
331
556
|
}
|
|
332
557
|
|
|
558
|
+
.avatar.placeholder {
|
|
559
|
+
background: var(--ps-avatar-bg);
|
|
560
|
+
display: flex;
|
|
561
|
+
align-items: center;
|
|
562
|
+
justify-content: center;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.avatar.placeholder svg {
|
|
566
|
+
fill: var(--ps-avatar-fg);
|
|
567
|
+
}
|
|
568
|
+
|
|
333
569
|
.provider-badge {
|
|
334
570
|
position: absolute;
|
|
335
571
|
bottom: -0.0625rem;
|
|
@@ -340,7 +576,7 @@ class PowerStrip extends HTMLElement {
|
|
|
340
576
|
align-items: center;
|
|
341
577
|
justify-content: center;
|
|
342
578
|
}
|
|
343
|
-
|
|
579
|
+
|
|
344
580
|
.provider-badge svg {
|
|
345
581
|
width: 0.5rem;
|
|
346
582
|
height: 0.5rem;
|
|
@@ -365,7 +601,7 @@ class PowerStrip extends HTMLElement {
|
|
|
365
601
|
|
|
366
602
|
.user-name {
|
|
367
603
|
font-size: 0.8rem;
|
|
368
|
-
color:
|
|
604
|
+
color: var(--ps-text);
|
|
369
605
|
max-width: 10rem;
|
|
370
606
|
white-space: nowrap;
|
|
371
607
|
overflow: hidden;
|
|
@@ -377,12 +613,12 @@ class PowerStrip extends HTMLElement {
|
|
|
377
613
|
|
|
378
614
|
.user-name:hover {
|
|
379
615
|
text-decoration: underline;
|
|
380
|
-
color:
|
|
616
|
+
color: var(--ps-accent);
|
|
381
617
|
}
|
|
382
|
-
|
|
618
|
+
|
|
383
619
|
.account-label {
|
|
384
620
|
font-size: 0.8rem;
|
|
385
|
-
color:
|
|
621
|
+
color: var(--ps-accent);
|
|
386
622
|
max-width: 10rem;
|
|
387
623
|
white-space: nowrap;
|
|
388
624
|
overflow: hidden;
|
|
@@ -400,11 +636,11 @@ class PowerStrip extends HTMLElement {
|
|
|
400
636
|
}
|
|
401
637
|
|
|
402
638
|
.admin-btn {
|
|
403
|
-
color:
|
|
639
|
+
color: var(--ps-danger) !important;
|
|
404
640
|
}
|
|
405
641
|
|
|
406
642
|
.stop-impersonation-btn {
|
|
407
|
-
color:
|
|
643
|
+
color: var(--ps-warning) !important;
|
|
408
644
|
font-weight: bold;
|
|
409
645
|
}
|
|
410
646
|
|
|
@@ -419,9 +655,9 @@ class PowerStrip extends HTMLElement {
|
|
|
419
655
|
border: none;
|
|
420
656
|
border-radius: 0.75rem;
|
|
421
657
|
padding: 0;
|
|
422
|
-
box-shadow:
|
|
423
|
-
background:
|
|
424
|
-
color:
|
|
658
|
+
box-shadow: var(--ps-dialog-shadow);
|
|
659
|
+
background: var(--ps-dialog-bg);
|
|
660
|
+
color: var(--ps-dialog-text);
|
|
425
661
|
max-width: 20rem;
|
|
426
662
|
width: 90%;
|
|
427
663
|
overflow: hidden;
|
|
@@ -442,7 +678,7 @@ class PowerStrip extends HTMLElement {
|
|
|
442
678
|
align-items: center;
|
|
443
679
|
margin-bottom: 1.25rem;
|
|
444
680
|
}
|
|
445
|
-
|
|
681
|
+
|
|
446
682
|
.dialog-title {
|
|
447
683
|
font-weight: 700;
|
|
448
684
|
font-size: 1.25rem;
|
|
@@ -454,7 +690,7 @@ class PowerStrip extends HTMLElement {
|
|
|
454
690
|
border: none;
|
|
455
691
|
cursor: pointer;
|
|
456
692
|
font-size: 1.5rem;
|
|
457
|
-
color:
|
|
693
|
+
color: var(--ps-dialog-muted);
|
|
458
694
|
padding: 0;
|
|
459
695
|
line-height: 1;
|
|
460
696
|
display: flex;
|
|
@@ -467,8 +703,8 @@ class PowerStrip extends HTMLElement {
|
|
|
467
703
|
}
|
|
468
704
|
|
|
469
705
|
.close-btn:hover {
|
|
470
|
-
background-color:
|
|
471
|
-
color:
|
|
706
|
+
background-color: var(--ps-dialog-hover-bg);
|
|
707
|
+
color: var(--ps-dialog-text);
|
|
472
708
|
}
|
|
473
709
|
|
|
474
710
|
.auth-buttons {
|
|
@@ -479,7 +715,7 @@ class PowerStrip extends HTMLElement {
|
|
|
479
715
|
|
|
480
716
|
.auth-btn {
|
|
481
717
|
padding: 0.75rem 1rem;
|
|
482
|
-
border: 1px solid
|
|
718
|
+
border: 1px solid var(--ps-surface-border);
|
|
483
719
|
border-radius: 0.375rem;
|
|
484
720
|
cursor: pointer;
|
|
485
721
|
display: flex;
|
|
@@ -491,7 +727,7 @@ class PowerStrip extends HTMLElement {
|
|
|
491
727
|
transition: all 0.2s ease;
|
|
492
728
|
text-decoration: none;
|
|
493
729
|
color: inherit;
|
|
494
|
-
background-color:
|
|
730
|
+
background-color: var(--ps-neutral-btn-bg);
|
|
495
731
|
}
|
|
496
732
|
|
|
497
733
|
.auth-btn:hover {
|
|
@@ -509,12 +745,12 @@ class PowerStrip extends HTMLElement {
|
|
|
509
745
|
}
|
|
510
746
|
|
|
511
747
|
.auth-btn.google {
|
|
512
|
-
color:
|
|
513
|
-
|
|
748
|
+
color: var(--ps-neutral-btn-text);
|
|
749
|
+
background-color: var(--ps-neutral-btn-bg);
|
|
750
|
+
border-color: var(--ps-neutral-btn-border);
|
|
514
751
|
}
|
|
515
752
|
.auth-btn.google:hover {
|
|
516
|
-
background-color:
|
|
517
|
-
border-color: #d2e3fc;
|
|
753
|
+
background-color: var(--ps-neutral-btn-hover-bg);
|
|
518
754
|
}
|
|
519
755
|
|
|
520
756
|
.auth-btn.twitch {
|
|
@@ -543,12 +779,13 @@ class PowerStrip extends HTMLElement {
|
|
|
543
779
|
flex-direction: column;
|
|
544
780
|
gap: 0.5rem;
|
|
545
781
|
}
|
|
546
|
-
|
|
782
|
+
|
|
547
783
|
.account-item {
|
|
548
784
|
padding: 0.75rem;
|
|
549
|
-
border: 1px solid
|
|
785
|
+
border: 1px solid var(--ps-surface-soft-border);
|
|
550
786
|
border-radius: 0.375rem;
|
|
551
|
-
background:
|
|
787
|
+
background: var(--ps-surface-bg);
|
|
788
|
+
color: var(--ps-dialog-text);
|
|
552
789
|
text-align: left;
|
|
553
790
|
cursor: pointer;
|
|
554
791
|
display: flex;
|
|
@@ -558,25 +795,25 @@ class PowerStrip extends HTMLElement {
|
|
|
558
795
|
font-size: 1rem;
|
|
559
796
|
gap: 1rem;
|
|
560
797
|
}
|
|
561
|
-
|
|
798
|
+
|
|
562
799
|
.account-item:hover {
|
|
563
|
-
background-color:
|
|
800
|
+
background-color: var(--ps-surface-hover-bg);
|
|
564
801
|
}
|
|
565
|
-
|
|
802
|
+
|
|
566
803
|
.account-item.active {
|
|
567
|
-
border-color:
|
|
568
|
-
background-color:
|
|
804
|
+
border-color: var(--ps-accent);
|
|
805
|
+
background-color: var(--ps-active-bg);
|
|
569
806
|
}
|
|
570
|
-
|
|
807
|
+
|
|
571
808
|
.current-badge {
|
|
572
809
|
font-size: 0.75rem;
|
|
573
|
-
background:
|
|
810
|
+
background: var(--ps-accent);
|
|
574
811
|
color: white;
|
|
575
812
|
padding: 0.125rem 0.375rem;
|
|
576
813
|
border-radius: 0.75rem;
|
|
577
814
|
}
|
|
578
815
|
</style>
|
|
579
|
-
|
|
816
|
+
|
|
580
817
|
<div class="container">
|
|
581
818
|
${content}
|
|
582
819
|
<slot></slot>
|
|
@@ -593,7 +830,7 @@ class PowerStrip extends HTMLElement {
|
|
|
593
830
|
</div>
|
|
594
831
|
</div>
|
|
595
832
|
</dialog>
|
|
596
|
-
|
|
833
|
+
|
|
597
834
|
${accountSwitcher}
|
|
598
835
|
`;
|
|
599
836
|
}
|
|
@@ -7,10 +7,7 @@
|
|
|
7
7
|
<link rel="stylesheet" href="/users/style.css" />
|
|
8
8
|
</head>
|
|
9
9
|
<body data-ssr-profile="{{ssr:profile_json}}" data-ssr-credentials="{{ssr:credentials_json}}">
|
|
10
|
-
<power-strip
|
|
11
|
-
providers="{{ssr:providers}}"
|
|
12
|
-
style="position: absolute; top: 0; right: 0; z-index: 9999; padding: 0.1rem; border-radius: 0 0 0 0.3rem"
|
|
13
|
-
>
|
|
10
|
+
<power-strip providers="{{ssr:providers}}" style="position: absolute; top: 0; right: 0; z-index: 9999; border-radius: 0 0 0 0.3rem">
|
|
14
11
|
<svg viewBox="0 0 24 24" style="width: 1rem; height: 1rem"><path d="M7 2v11h3v9l7-12h-4l4-8z" fill="#ffcc00" /></svg>
|
|
15
12
|
</power-strip>
|
|
16
13
|
<script src="/users/power-strip.js" async></script>
|
|
@@ -56,7 +53,7 @@
|
|
|
56
53
|
<div
|
|
57
54
|
id="profile-avatar-placeholder"
|
|
58
55
|
class="avatar-large"
|
|
59
|
-
style="background:
|
|
56
|
+
style="background: var(--surface-alt); {{ssr:profile_placeholder_display}} align-items: center; justify-content: center; color: var(--muted-badge-text)"
|
|
60
57
|
>
|
|
61
58
|
<svg
|
|
62
59
|
viewBox="0 0 24 24"
|
|
@@ -99,7 +96,7 @@
|
|
|
99
96
|
</button>
|
|
100
97
|
</div>
|
|
101
98
|
<div>
|
|
102
|
-
<p id="display-email" style="margin: 0.25rem 0 0 0; color:
|
|
99
|
+
<p id="display-email" style="margin: 0.25rem 0 0 0; color: var(--text-faint)">{{ssr:profile_email}}</p>
|
|
103
100
|
</div>
|
|
104
101
|
</div>
|
|
105
102
|
|
|
@@ -125,7 +122,7 @@
|
|
|
125
122
|
|
|
126
123
|
<section>
|
|
127
124
|
<h2>Login Credentials</h2>
|
|
128
|
-
<p style="color:
|
|
125
|
+
<p style="color: var(--text-faint); font-size: 0.9rem; margin-bottom: 1.5rem">Manage the login methods linked to your account.</p>
|
|
129
126
|
|
|
130
127
|
<div id="credentials-list" style="margin-bottom: 2rem">{{ssr:credentials_list_html}}</div>
|
|
131
128
|
|
|
@@ -383,7 +380,7 @@
|
|
|
383
380
|
${c.provider.charAt(0).toUpperCase() + c.provider.slice(1)}
|
|
384
381
|
${isCurrent ? '<span class="current-badge">logged in</span>' : ''}
|
|
385
382
|
</div>
|
|
386
|
-
<div style="font-size: 0.8rem; color:
|
|
383
|
+
<div style="font-size: 0.8rem; color: var(--text-faint);">${c.email || c.subject_id}</div>
|
|
387
384
|
</div>
|
|
388
385
|
</div>
|
|
389
386
|
<button class="remove-btn" onclick="removeCredential('${c.provider}')" ${isCurrent || credentials.length === 1 ? 'disabled title="' + (isCurrent ? 'Cannot remove the method you are currently logged in with' : 'Cannot remove your last login method') + '"' : ''}>
|