From c8ff510832f0728924b15f79b289a09b85c982a3 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Tue, 6 Jan 2026 02:39:34 +0000 Subject: [PATCH] feat(dees-screensaver): improve screensaver activation, visuals, and dismissal animations --- changelog.md | 10 ++ ts_web/00_commitinfo_data.ts | 4 +- .../dees-screensaver/dees-screensaver.ts | 147 ++++++++++++++++-- 3 files changed, 149 insertions(+), 12 deletions(-) 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; + } } }