@social.io/catalog 2.1.0 → 2.1.2

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.
@@ -11,6 +11,10 @@ export interface ISioLogoRenderOptions {
11
11
  light: boolean;
12
12
  }
13
13
 
14
+ export type TSioLogoRenderContext =
15
+ | CanvasRenderingContext2D
16
+ | OffscreenCanvasRenderingContext2D;
17
+
14
18
  interface ISioLogoPoint {
15
19
  x: number;
16
20
  y: number;
@@ -159,9 +163,26 @@ const easeInBack = (progress: number): number => {
159
163
  return (coefficient + 1) * progress * progress * progress - coefficient * progress * progress;
160
164
  };
161
165
 
166
+ type TSioLogoFrameHandle = number | ReturnType<typeof setTimeout>;
167
+
168
+ const requestFrame = (callback: FrameRequestCallback): TSioLogoFrameHandle => {
169
+ if (typeof globalThis.requestAnimationFrame === 'function') {
170
+ return globalThis.requestAnimationFrame(callback);
171
+ }
172
+ return setTimeout(() => callback(performance.now()), 1000 / 60);
173
+ };
174
+
175
+ const cancelFrame = (handle: TSioLogoFrameHandle): void => {
176
+ if (typeof globalThis.cancelAnimationFrame === 'function' && typeof handle === 'number') {
177
+ globalThis.cancelAnimationFrame(handle);
178
+ return;
179
+ }
180
+ clearTimeout(handle);
181
+ };
182
+
162
183
  export class SioLogoRenderer {
163
184
  private options: ISioLogoRenderOptions;
164
- private frameHandle: number | null = null;
185
+ private frameHandle: TSioLogoFrameHandle | null = null;
165
186
  private phase: TSioLogoAnimationPhase | null = null;
166
187
  private startedAt = 0;
167
188
  private stagger = 70;
@@ -178,7 +199,7 @@ export class SioLogoRenderer {
178
199
  private opacities: number[] = [];
179
200
 
180
201
  constructor(
181
- private readonly context: CanvasRenderingContext2D,
202
+ private readonly context: TSioLogoRenderContext,
182
203
  options: ISioLogoRenderOptions,
183
204
  ) {
184
205
  this.options = normalizeOptions(options);
@@ -220,14 +241,14 @@ export class SioLogoRenderer {
220
241
  this.settle();
221
242
  return;
222
243
  }
223
- this.frameHandle = requestAnimationFrame(drawFrame);
244
+ this.frameHandle = requestFrame(drawFrame);
224
245
  };
225
- this.frameHandle = requestAnimationFrame(drawFrame);
246
+ this.frameHandle = requestFrame(drawFrame);
226
247
  }
227
248
 
228
249
  public stop(): void {
229
250
  if (this.frameHandle !== null) {
230
- cancelAnimationFrame(this.frameHandle);
251
+ cancelFrame(this.frameHandle);
231
252
  this.frameHandle = null;
232
253
  }
233
254
  this.phase = null;
@@ -0,0 +1,67 @@
1
+ import {
2
+ SioLogoRenderer,
3
+ type ISioLogoRenderOptions,
4
+ type TSioLogoAnimationPhase,
5
+ } from './sio-logo-render.js';
6
+
7
+ interface ISioLogoWorkerInitMessage {
8
+ type: 'init';
9
+ canvas: OffscreenCanvas;
10
+ options: ISioLogoRenderOptions;
11
+ phase: TSioLogoAnimationPhase | null;
12
+ }
13
+
14
+ interface ISioLogoWorkerOptionsMessage {
15
+ type: 'options';
16
+ options: ISioLogoRenderOptions;
17
+ }
18
+
19
+ interface ISioLogoWorkerPhaseMessage {
20
+ type: 'phase';
21
+ phase: TSioLogoAnimationPhase | 'stop' | 'clear';
22
+ }
23
+
24
+ type TSioLogoWorkerMessage =
25
+ | ISioLogoWorkerInitMessage
26
+ | ISioLogoWorkerOptionsMessage
27
+ | ISioLogoWorkerPhaseMessage;
28
+
29
+ interface ISioLogoWorkerScope {
30
+ onmessage: ((event: MessageEvent<TSioLogoWorkerMessage>) => void) | null;
31
+ postMessage: (message: { type: 'ready' | 'initialized' }) => void;
32
+ }
33
+
34
+ const workerScope = globalThis as unknown as ISioLogoWorkerScope;
35
+ let renderer: SioLogoRenderer | null = null;
36
+
37
+ workerScope.onmessage = (event): void => {
38
+ const message = event.data;
39
+ if (message.type === 'init') {
40
+ const context = message.canvas.getContext('2d');
41
+ if (!context) throw new Error('Unable to create the sio-logo worker canvas context.');
42
+ renderer = new SioLogoRenderer(context, message.options);
43
+ if (message.phase) renderer.start(message.phase);
44
+ else renderer.drawStatic();
45
+ workerScope.postMessage({ type: 'initialized' });
46
+ return;
47
+ }
48
+
49
+ if (!renderer) return;
50
+ if (message.type === 'options') {
51
+ renderer.setOptions(message.options);
52
+ return;
53
+ }
54
+
55
+ if (message.phase === 'stop') {
56
+ renderer.stop();
57
+ renderer.drawStatic();
58
+ } else if (message.phase === 'clear') {
59
+ renderer.clear();
60
+ } else {
61
+ renderer.start(message.phase);
62
+ }
63
+ };
64
+
65
+ // The canvas is transferred only after the module and its renderer dependency
66
+ // have loaded, leaving the HTMLCanvasElement intact for pre-transfer fallback.
67
+ workerScope.postMessage({ type: 'ready' });
@@ -2,15 +2,31 @@ import {
2
2
  DeesElement,
3
3
  css,
4
4
  customElement,
5
+ directives,
5
6
  html,
6
7
  property,
8
+ state,
7
9
  type TemplateResult,
8
10
  } from '@design.estate/dees-element';
9
- import { SioLogoRenderer, sioLogoAspect } from './sio-logo-render.js';
11
+ import {
12
+ SioLogoRenderer,
13
+ sioLogoAspect,
14
+ type ISioLogoRenderOptions,
15
+ type TSioLogoAnimationPhase,
16
+ } from './sio-logo-render.js';
10
17
  import { sioElementStyles } from './tokens.js';
11
18
 
12
19
  export type TSioLogoTheme = 'auto' | 'light' | 'dark';
13
20
 
21
+ type TSioLogoBackendState =
22
+ | 'idle'
23
+ | 'worker-loading'
24
+ | 'worker-transferred'
25
+ | 'worker'
26
+ | 'main';
27
+
28
+ type TSioLogoPhaseCommand = TSioLogoAnimationPhase | 'stop' | 'clear';
29
+
14
30
  declare global {
15
31
  interface HTMLElementTagNameMap {
16
32
  'sio-logo': SioLogo;
@@ -20,9 +36,9 @@ declare global {
20
36
  /**
21
37
  * The social.io faceted S brand mark from the v8 design prototype.
22
38
  *
23
- * The canvas renderer preserves the exact eight-facet geometry and supports
24
- * entrance, hover-wave, dismiss, and loading animations. Motion is disabled
25
- * when the visitor requests reduced motion.
39
+ * Geometry and animation run on an OffscreenCanvas in a module worker when the
40
+ * browser and consumer bundle support it. The same renderer is used on the
41
+ * main thread as a safe fallback.
26
42
  */
27
43
  @customElement('sio-logo')
28
44
  export class SioLogo extends DeesElement {
@@ -71,6 +87,9 @@ export class SioLogo extends DeesElement {
71
87
  @property({ type: Boolean, attribute: 'no-hover' })
72
88
  public accessor noHover = false;
73
89
 
90
+ @state()
91
+ private accessor canvasGeneration = 0;
92
+
74
93
  public static styles = [
75
94
  ...sioElementStyles,
76
95
  css`
@@ -85,29 +104,43 @@ export class SioLogo extends DeesElement {
85
104
  `,
86
105
  ];
87
106
 
107
+ private backendState: TSioLogoBackendState = 'idle';
108
+ private worker: Worker | null = null;
88
109
  private renderer: SioLogoRenderer | null = null;
110
+ private canvasTransferred = false;
111
+ private forceMainThread = false;
112
+ private initializationTimeout: ReturnType<typeof setTimeout> | null = null;
113
+ private initializationGeneration = 0;
114
+ private lifecycleGeneration = 0;
115
+ private initialUpdateHandled = false;
116
+ private queuedOptions = false;
117
+ private queuedPhase: TSioLogoPhaseCommand | null = null;
89
118
  private themeObserver: MutationObserver | null = null;
90
- private colorSchemeMedia: MediaQueryList | null = null;
91
119
  private reducedMotionMedia: MediaQueryList | null = null;
92
120
  private lastDevicePixelRatio = 0;
93
- private lifecycleGeneration = 0;
121
+ private lastLight: boolean | null = null;
94
122
 
95
123
  private readonly handlePointerEnter = (): void => {
96
124
  if (!this.noHover && !this.loading && !this.prefersReducedMotion()) this.wave();
97
125
  };
98
126
 
99
127
  private readonly handleAmbientChange = (): void => {
100
- this.syncRenderer(this.loading ? 'loading' : null);
128
+ if (this.theme !== 'auto') return;
129
+ const light = this.isLightBackdrop();
130
+ if (light === this.lastLight) return;
131
+ this.lastLight = light;
132
+ this.syncRendererOptions();
101
133
  };
102
134
 
103
135
  private readonly handleReducedMotionChange = (): void => {
104
- this.syncRenderer(this.loading ? 'loading' : null);
136
+ if (this.prefersReducedMotion()) this.sendPhase('stop');
137
+ else if (this.loading) this.sendPhase('loading');
105
138
  };
106
139
 
107
140
  private readonly handleResize = (): void => {
108
- const currentDevicePixelRatio = window.devicePixelRatio || 1;
109
- if (currentDevicePixelRatio === this.lastDevicePixelRatio) return;
110
- this.syncRenderer(this.loading ? 'loading' : null);
141
+ const devicePixelRatio = window.devicePixelRatio || 1;
142
+ if (devicePixelRatio === this.lastDevicePixelRatio) return;
143
+ this.syncRendererOptions();
111
144
  };
112
145
 
113
146
  public async connectedCallback(): Promise<void> {
@@ -118,106 +151,258 @@ export class SioLogo extends DeesElement {
118
151
  window.addEventListener('resize', this.handleResize);
119
152
  this.observeReducedMotion();
120
153
  await connection;
154
+ await this.updateComplete;
121
155
  if (!this.isConnected || generation !== this.lifecycleGeneration) return;
122
- if (this.hasUpdated) {
123
- this.observeAmbientTheme();
124
- this.syncRenderer(this.loading ? 'loading' : null);
125
- }
156
+ this.observeAmbientTheme();
157
+ if (this.hasUpdated && this.backendState === 'idle') this.initializeCanvas();
126
158
  }
127
159
 
128
160
  public async disconnectedCallback(): Promise<void> {
129
161
  const generation = ++this.lifecycleGeneration;
130
- this.cleanupOwnedResources();
162
+ this.cleanupOwnedResources(true);
131
163
  await super.disconnectedCallback();
132
164
  if (!this.isConnected && generation === this.lifecycleGeneration) {
133
- this.cleanupOwnedResources();
165
+ this.cleanupOwnedResources(true);
134
166
  }
135
167
  }
136
168
 
137
- private cleanupOwnedResources(): void {
169
+ private cleanupOwnedResources(replaceTransferredCanvas: boolean): void {
138
170
  this.removeEventListener('mouseenter', this.handlePointerEnter);
139
171
  window.removeEventListener('resize', this.handleResize);
140
172
  this.stopAmbientThemeObservation();
141
173
  this.stopReducedMotionObservation();
142
- this.renderer?.stop();
174
+ this.destroyBackend(replaceTransferredCanvas);
143
175
  }
144
176
 
145
177
  public render(): TemplateResult {
146
178
  const height = this.normalizedSize;
147
179
  const width = this.square ? height : Math.round(height * sioLogoAspect);
148
- return html`<canvas aria-hidden="true" style="width:${width}px;height:${height}px"></canvas>`;
180
+ return html`${directives.keyed(
181
+ this.canvasGeneration,
182
+ html`<canvas aria-hidden="true" style="width:${width}px;height:${height}px"></canvas>`,
183
+ )}`;
149
184
  }
150
185
 
151
186
  public firstUpdated(changedProperties: Map<PropertyKey, unknown>): void {
152
187
  super.firstUpdated(changedProperties);
153
- const canvas = this.shadowRoot?.querySelector<HTMLCanvasElement>('canvas');
154
- const context = canvas?.getContext('2d');
155
- if (!context) return;
156
- this.renderer = new SioLogoRenderer(context, this.getRendererOptions());
157
- if (!this.isConnected) {
158
- this.renderer.drawStatic();
159
- return;
160
- }
161
188
  this.observeAmbientTheme();
162
- if (this.loading && !this.prefersReducedMotion()) this.renderer.start('loading');
163
- else if (!this.noEntrance && !this.prefersReducedMotion()) this.renderer.start('in');
164
- else this.renderer.drawStatic();
189
+ this.initializeCanvas();
165
190
  }
166
191
 
167
192
  public updated(changedProperties: Map<PropertyKey, unknown>): void {
168
- if (!this.isConnected) {
169
- this.stopAmbientThemeObservation();
170
- this.renderer?.stop();
193
+ if (!this.initialUpdateHandled) {
194
+ this.initialUpdateHandled = true;
171
195
  return;
172
196
  }
173
- const redrawProperties: PropertyKey[] = [
197
+ if (!this.isConnected) return;
198
+ if (changedProperties.has('canvasGeneration')) {
199
+ this.initializeCanvas();
200
+ return;
201
+ }
202
+
203
+ const shapeProperties: PropertyKey[] = [
174
204
  'size',
175
205
  'gap',
176
206
  'radius',
177
207
  'square',
178
208
  'background',
179
209
  'mono',
180
- 'theme',
181
210
  ];
182
- if (redrawProperties.some((propertyName) => changedProperties.has(propertyName))) {
183
- this.syncRenderer(this.loading ? 'loading' : null);
211
+ if (shapeProperties.some((propertyName) => changedProperties.has(propertyName))) {
212
+ this.syncRendererOptions();
184
213
  }
185
214
  if (changedProperties.has('theme')) {
186
215
  this.stopAmbientThemeObservation();
187
216
  this.observeAmbientTheme();
217
+ this.syncRendererOptions();
188
218
  }
189
219
  if (changedProperties.has('loading')) {
190
- this.syncRenderer(this.loading ? 'loading' : null);
220
+ this.sendPhase(
221
+ this.loading && !this.prefersReducedMotion() ? 'loading' : 'stop',
222
+ );
191
223
  }
192
224
  }
193
225
 
194
226
  protected themeChanged(): void {
195
- this.syncRenderer(this.loading ? 'loading' : null);
227
+ this.handleAmbientChange();
196
228
  }
197
229
 
198
230
  public play(): void {
199
- if (!this.renderer || !this.isConnected) return;
200
- if (this.prefersReducedMotion()) this.renderer.drawStatic();
201
- else this.renderer.start('in');
231
+ if (!this.isConnected) return;
232
+ this.sendPhase(this.prefersReducedMotion() ? 'stop' : 'in');
202
233
  }
203
234
 
204
235
  public wave(): void {
205
- if (!this.renderer || !this.isConnected) return;
206
- if (this.prefersReducedMotion()) this.renderer.drawStatic();
207
- else this.renderer.start('wave');
236
+ if (!this.isConnected) return;
237
+ this.sendPhase(this.prefersReducedMotion() ? 'stop' : 'wave');
208
238
  }
209
239
 
210
240
  public dismiss(): void {
211
- if (!this.renderer || !this.isConnected) return;
212
- if (this.prefersReducedMotion()) this.renderer.clear();
213
- else this.renderer.start('out');
241
+ if (!this.isConnected) return;
242
+ this.sendPhase(this.prefersReducedMotion() ? 'clear' : 'out');
243
+ }
244
+
245
+ private initializeCanvas(): void {
246
+ if (!this.isConnected || this.backendState !== 'idle') return;
247
+ const canvas = this.shadowRoot?.querySelector<HTMLCanvasElement>('canvas');
248
+ if (!canvas) return;
249
+ const generation = ++this.initializationGeneration;
250
+ const supportsWorker =
251
+ !this.forceMainThread &&
252
+ typeof Worker === 'function' &&
253
+ typeof canvas.transferControlToOffscreen === 'function';
254
+
255
+ if (!supportsWorker) {
256
+ this.initializeMainThread(canvas, generation);
257
+ return;
258
+ }
259
+
260
+ try {
261
+ const worker = new Worker(new URL('./sio-logo-worker.js', import.meta.url), {
262
+ type: 'module',
263
+ name: 'sio-logo',
264
+ });
265
+ this.worker = worker;
266
+ this.backendState = 'worker-loading';
267
+ worker.onmessage = (event: MessageEvent<{ type?: string }>): void => {
268
+ if (event.data?.type === 'ready') {
269
+ this.handleWorkerReady(worker, canvas, generation);
270
+ } else if (event.data?.type === 'initialized') {
271
+ this.handleWorkerInitialized(worker, generation);
272
+ }
273
+ };
274
+ worker.onerror = (): void => this.handleWorkerFailure(worker, canvas, generation);
275
+ worker.onmessageerror = (): void => this.handleWorkerFailure(worker, canvas, generation);
276
+ this.armInitializationTimeout(worker, canvas, generation);
277
+ } catch {
278
+ this.forceMainThread = true;
279
+ this.initializeMainThread(canvas, generation);
280
+ }
281
+ }
282
+
283
+ private handleWorkerReady(
284
+ worker: Worker,
285
+ canvas: HTMLCanvasElement,
286
+ generation: number,
287
+ ): void {
288
+ if (!this.isCurrentWorker(worker, generation) || this.backendState !== 'worker-loading') return;
289
+ this.clearInitializationTimeout();
290
+ try {
291
+ const offscreenCanvas = canvas.transferControlToOffscreen();
292
+ this.canvasTransferred = true;
293
+ this.backendState = 'worker-transferred';
294
+ this.lastLight = this.isLightBackdrop();
295
+ worker.postMessage(
296
+ {
297
+ type: 'init',
298
+ canvas: offscreenCanvas,
299
+ options: this.getRendererOptions(),
300
+ phase: this.getInitialPhase(),
301
+ },
302
+ [offscreenCanvas],
303
+ );
304
+ this.queuedOptions = false;
305
+ this.armInitializationTimeout(worker, canvas, generation);
306
+ } catch {
307
+ this.handleWorkerFailure(worker, canvas, generation);
308
+ }
309
+ }
310
+
311
+ private handleWorkerInitialized(worker: Worker, generation: number): void {
312
+ if (!this.isCurrentWorker(worker, generation) || this.backendState !== 'worker-transferred') return;
313
+ this.clearInitializationTimeout();
314
+ this.backendState = 'worker';
315
+ if (this.queuedOptions) {
316
+ this.queuedOptions = false;
317
+ this.syncRendererOptions();
318
+ }
319
+ if (this.queuedPhase) {
320
+ const phase = this.queuedPhase;
321
+ this.queuedPhase = null;
322
+ this.sendPhase(phase);
323
+ }
324
+ }
325
+
326
+ private handleWorkerFailure(
327
+ worker: Worker,
328
+ canvas: HTMLCanvasElement,
329
+ generation: number,
330
+ ): void {
331
+ if (!this.isCurrentWorker(worker, generation)) return;
332
+ const requiresNewCanvas = this.canvasTransferred;
333
+ this.forceMainThread = true;
334
+ this.destroyBackend(requiresNewCanvas);
335
+ if (!requiresNewCanvas && this.isConnected) {
336
+ this.initializeMainThread(canvas, generation);
337
+ }
338
+ }
339
+
340
+ private initializeMainThread(canvas: HTMLCanvasElement, generation: number): void {
341
+ if (!this.isConnected || generation !== this.initializationGeneration) return;
342
+ const context = canvas.getContext('2d');
343
+ if (!context) return;
344
+ this.backendState = 'main';
345
+ const options = this.getRendererOptions();
346
+ this.lastLight = options.light;
347
+ this.renderer = new SioLogoRenderer(context, options);
348
+ const phase = this.getInitialPhase();
349
+ if (phase) this.renderer.start(phase);
350
+ else this.renderer.drawStatic();
351
+ this.queuedOptions = false;
352
+ if (this.queuedPhase) {
353
+ const queuedPhase = this.queuedPhase;
354
+ this.queuedPhase = null;
355
+ this.sendPhase(queuedPhase);
356
+ }
357
+ }
358
+
359
+ private armInitializationTimeout(
360
+ worker: Worker,
361
+ canvas: HTMLCanvasElement,
362
+ generation: number,
363
+ ): void {
364
+ this.clearInitializationTimeout();
365
+ this.initializationTimeout = setTimeout(() => {
366
+ this.handleWorkerFailure(worker, canvas, generation);
367
+ }, 2000);
368
+ }
369
+
370
+ private clearInitializationTimeout(): void {
371
+ if (this.initializationTimeout === null) return;
372
+ clearTimeout(this.initializationTimeout);
373
+ this.initializationTimeout = null;
374
+ }
375
+
376
+ private isCurrentWorker(worker: Worker, generation: number): boolean {
377
+ return this.isConnected &&
378
+ this.worker === worker &&
379
+ generation === this.initializationGeneration;
380
+ }
381
+
382
+ private destroyBackend(replaceTransferredCanvas: boolean): void {
383
+ const transferred = this.canvasTransferred;
384
+ this.clearInitializationTimeout();
385
+ if (this.worker) {
386
+ this.worker.onmessage = null;
387
+ this.worker.onerror = null;
388
+ this.worker.onmessageerror = null;
389
+ this.worker.terminate();
390
+ }
391
+ this.worker = null;
392
+ this.renderer?.stop();
393
+ this.renderer = null;
394
+ this.backendState = 'idle';
395
+ this.canvasTransferred = false;
396
+ this.queuedOptions = false;
397
+ this.queuedPhase = null;
398
+ if (replaceTransferredCanvas && transferred) this.canvasGeneration += 1;
214
399
  }
215
400
 
216
401
  private get normalizedSize(): number {
217
402
  return Number.isFinite(this.size) ? Math.max(1, this.size) : 40;
218
403
  }
219
404
 
220
- private getRendererOptions() {
405
+ private getRendererOptions(): ISioLogoRenderOptions {
221
406
  this.lastDevicePixelRatio = window.devicePixelRatio || 1;
222
407
  return {
223
408
  size: this.normalizedSize,
@@ -231,14 +416,41 @@ export class SioLogo extends DeesElement {
231
416
  };
232
417
  }
233
418
 
234
- private syncRenderer(animation: 'loading' | null = null): void {
235
- if (!this.renderer) return;
236
- this.renderer.stop();
237
- this.renderer.setOptions(this.getRendererOptions());
238
- if (animation && this.isConnected && !this.prefersReducedMotion()) {
239
- this.renderer.start(animation);
419
+ private getInitialPhase(): TSioLogoAnimationPhase | null {
420
+ if (!this.isConnected || this.prefersReducedMotion()) return null;
421
+ if (this.loading) return 'loading';
422
+ return this.noEntrance ? null : 'in';
423
+ }
424
+
425
+ private syncRendererOptions(): void {
426
+ const options = this.getRendererOptions();
427
+ this.lastLight = options.light;
428
+ if (this.backendState === 'worker') {
429
+ this.worker?.postMessage({ type: 'options', options });
430
+ } else if (this.backendState === 'main') {
431
+ this.renderer?.setOptions(options);
432
+ } else {
433
+ this.queuedOptions = true;
240
434
  }
241
- else this.renderer.drawStatic();
435
+ }
436
+
437
+ private sendPhase(phase: TSioLogoPhaseCommand): void {
438
+ if (this.backendState === 'worker') {
439
+ this.worker?.postMessage({ type: 'phase', phase });
440
+ return;
441
+ }
442
+ if (this.backendState === 'main' && this.renderer) {
443
+ if (phase === 'stop') {
444
+ this.renderer.stop();
445
+ this.renderer.drawStatic();
446
+ } else if (phase === 'clear') {
447
+ this.renderer.clear();
448
+ } else {
449
+ this.renderer.start(phase);
450
+ }
451
+ return;
452
+ }
453
+ this.queuedPhase = phase;
242
454
  }
243
455
 
244
456
  private prefersReducedMotion(): boolean {
@@ -253,6 +465,13 @@ export class SioLogo extends DeesElement {
253
465
 
254
466
  let element: Element | null = this;
255
467
  while (element) {
468
+ if (element === document.body || element === document.documentElement) {
469
+ const pageTheme =
470
+ document.documentElement.getAttribute('data-sio-theme') ||
471
+ document.documentElement.getAttribute('data-theme');
472
+ if (pageTheme === 'light') return true;
473
+ if (pageTheme === 'dark') return false;
474
+ }
256
475
  const color = getComputedStyle(element).backgroundColor;
257
476
  const match = color.match(
258
477
  /rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:\s*[,/]\s*([\d.]+))?\s*\)/,
@@ -271,7 +490,8 @@ export class SioLogo extends DeesElement {
271
490
  const root = element.getRootNode();
272
491
  element = element.parentElement || (root instanceof ShadowRoot ? root.host : null);
273
492
  }
274
- return window.matchMedia?.('(prefers-color-scheme: light)').matches ?? false;
493
+
494
+ return false;
275
495
  }
276
496
 
277
497
  private observeAmbientTheme(): void {
@@ -281,15 +501,11 @@ export class SioLogo extends DeesElement {
281
501
  attributes: true,
282
502
  attributeFilter: ['data-sio-theme', 'data-theme', 'class', 'style'],
283
503
  });
284
- this.colorSchemeMedia = window.matchMedia?.('(prefers-color-scheme: light)') ?? null;
285
- this.colorSchemeMedia?.addEventListener('change', this.handleAmbientChange);
286
504
  }
287
505
 
288
506
  private stopAmbientThemeObservation(): void {
289
507
  this.themeObserver?.disconnect();
290
508
  this.themeObserver = null;
291
- this.colorSchemeMedia?.removeEventListener('change', this.handleAmbientChange);
292
- this.colorSchemeMedia = null;
293
509
  }
294
510
 
295
511
  private observeReducedMotion(): void {