diff --git a/changelog.md b/changelog.md
index 4ae65c1..28b196c 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,15 @@
# Changelog
+## 2026-01-06 - 3.34.0 - feat(dees-screensaver)
+improve screensaver activation, visuals, and dismissal animations
+
+- Add a safe demo flow that ensures only one screensaver instance: append an inactive element to document.body, store static instance, and activate after a 2s delay.
+- Introduce static instance management and destroy flow so multiple demo calls don't leak elements.
+- Rework DOM/CSS structure: add .screensaver-content container and .vignette element, separate backdrop, and cache element references for animations.
+- Adjust transitions and timings (overlay opacity, vignette transform, time opacity) for smoother entrance and staged reveal.
+- Implement a masked circular reveal dismissal: on click/touch, compute coordinates, animate a radial mask expanding from the pointer, dispatch screensaver-click with {x,y}, then remove the screensaver.
+- Reset mask on activation to support repeated activations without leftover masking styles.
+
## 2026-01-06 - 3.33.0 - feat(dees-statsgrid)
add multiPercentage tile type to stats grid
diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts
index cf844ac..190c57c 100644
--- a/ts_web/00_commitinfo_data.ts
+++ b/ts_web/00_commitinfo_data.ts
@@ -2,7 +2,7 @@
* autocreated commitinfo by @push.rocks/commitinfo
*/
export const commitinfo = {
- name: '@design.estate/dees-catalog',
- version: '3.33.0',
+ name: '@ecobridge.xyz/catalog',
+ version: '3.34.0',
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
}
diff --git a/ts_web/elements/dees-screensaver/dees-screensaver.ts b/ts_web/elements/dees-screensaver/dees-screensaver.ts
index 0444741..d1cdaf7 100644
--- a/ts_web/elements/dees-screensaver/dees-screensaver.ts
+++ b/ts_web/elements/dees-screensaver/dees-screensaver.ts
@@ -28,7 +28,27 @@ const colors = [
@customElement('dees-screensaver')
export class DeesScreensaver extends DeesElement {
- public static demo = () => html``;
+ public static demo = () => {
+ // Clean up any existing instance first
+ if (DeesScreensaver.instance) {
+ DeesScreensaver.instance.remove();
+ DeesScreensaver.instance = null;
+ }
+
+ // Create screensaver element immediately but inactive
+ const screensaver = document.createElement('dees-screensaver') as DeesScreensaver;
+ document.body.appendChild(screensaver);
+ DeesScreensaver.instance = screensaver;
+
+ // Activate after 2 seconds to show the animation
+ setTimeout(() => {
+ if (DeesScreensaver.instance === screensaver) {
+ screensaver.active = true;
+ }
+ }, 2000);
+
+ return html`
Screensaver will activate in 2 seconds...
`;
+ };
// Instance management
private static instance: DeesScreensaver | null = null;
@@ -72,7 +92,7 @@ export class DeesScreensaver extends DeesElement {
z-index: ${zIndexLayers.overlay.screensaver};
pointer-events: none;
opacity: 0;
- transition: opacity 0.8s ease;
+ transition: opacity 0.3s ease;
}
:host([active]) {
@@ -87,6 +107,48 @@ export class DeesScreensaver extends DeesElement {
width: 100%;
height: 100%;
background: hsl(240 10% 4%);
+ opacity: 0;
+ transition: opacity 1.5s ease 0.5s;
+ }
+
+ :host([active]) .backdrop {
+ opacity: 1;
+ }
+
+ .vignette {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: radial-gradient(
+ circle at center,
+ transparent 0%,
+ transparent 5%,
+ hsla(240 10% 4% / 0.2) 20%,
+ hsla(240 10% 4% / 0.6) 40%,
+ hsla(240 10% 4% / 0.9) 60%,
+ hsl(240 10% 4%) 80%
+ );
+ transform: scale(3);
+ opacity: 0;
+ transition:
+ transform 2s cubic-bezier(0.4, 0, 0.2, 1),
+ opacity 0.5s ease;
+ }
+
+ :host([active]) .vignette {
+ transform: scale(1);
+ opacity: 1;
+ }
+
+ /* Container for all screensaver visuals - gets masked on dismiss */
+ .screensaver-content {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
}
.time-container {
@@ -100,6 +162,14 @@ export class DeesScreensaver extends DeesElement {
user-select: none;
white-space: nowrap;
will-change: transform;
+ opacity: 0;
+ transition: opacity 0.8s ease;
+ transition-delay: 0s;
+ }
+
+ :host([active]) .time-container {
+ opacity: 1;
+ transition-delay: 1.2s;
}
.time {
@@ -158,6 +228,8 @@ export class DeesScreensaver extends DeesElement {
private elementHeight = 140;
private hasBounced = false;
private timeContainerEl: HTMLElement | null = null;
+ private vignetteEl: HTMLElement | null = null;
+ private contentEl: HTMLElement | null = null;
constructor() {
super();
@@ -166,16 +238,21 @@ export class DeesScreensaver extends DeesElement {
public render(): TemplateResult {
return html`
-
-
-
${this.currentTime}
-
${this.currentDate}
+
+
+
+
+ ${this.currentTime}
+ ${this.currentDate}
+
`;
}
public firstUpdated(): void {
this.timeContainerEl = this.shadowRoot?.querySelector('.time-container') as HTMLElement;
+ this.vignetteEl = this.shadowRoot?.querySelector('.vignette') as HTMLElement;
+ this.contentEl = this.shadowRoot?.querySelector('.screensaver-content') as HTMLElement;
}
async connectedCallback(): Promise
{
@@ -194,6 +271,11 @@ export class DeesScreensaver extends DeesElement {
super.updated(changedProperties);
if (changedProperties.has('active')) {
if (this.active) {
+ // Reset mask for fresh activation
+ if (this.contentEl) {
+ this.contentEl.style.maskImage = '';
+ this.contentEl.style.webkitMaskImage = '';
+ }
this.startAnimation();
this.startTimeUpdate();
} else {
@@ -311,9 +393,54 @@ export class DeesScreensaver extends DeesElement {
}
}
- private handleClick(): void {
- this.dispatchEvent(new CustomEvent('screensaver-click'));
- // Optionally hide on click
- this.active = false;
+ private handleClick(event: MouseEvent | TouchEvent): void {
+ // Get click/touch position
+ let x: number, y: number;
+
+ if (event instanceof TouchEvent && event.changedTouches.length > 0) {
+ x = event.changedTouches[0].clientX;
+ y = event.changedTouches[0].clientY;
+ } else if (event instanceof MouseEvent) {
+ x = event.clientX;
+ y = event.clientY;
+ } else {
+ // Fallback to center
+ x = window.innerWidth / 2;
+ y = window.innerHeight / 2;
+ }
+
+ this.dispatchEvent(new CustomEvent('screensaver-click', { detail: { x, y } }));
+
+ // Animate circle reveal from click position
+ if (this.contentEl) {
+ const duration = 700; // ms - slower for dramatic effect
+ const startTime = performance.now();
+ const maxSize = Math.max(window.innerWidth, window.innerHeight) * 1.5;
+ const startSize = 20; // Start with small visible circle
+
+ const animate = (currentTime: number) => {
+ const elapsed = currentTime - startTime;
+ const progress = Math.min(elapsed / duration, 1);
+
+ // Ease-in curve: starts slow, picks up speed
+ const eased = progress * progress * progress;
+ const size = startSize + eased * (maxSize - startSize);
+
+ this.contentEl!.style.maskImage = `radial-gradient(circle ${size}px at ${x}px ${y}px, transparent 100%, black 100%)`;
+ this.contentEl!.style.webkitMaskImage = `radial-gradient(circle ${size}px at ${x}px ${y}px, transparent 100%, black 100%)`;
+
+ if (progress < 1) {
+ requestAnimationFrame(animate);
+ } else {
+ // Animation complete - remove screensaver
+ this.active = false;
+ DeesScreensaver.destroy();
+ }
+ };
+
+ requestAnimationFrame(animate);
+ } else {
+ this.active = false;
+ }
}
}