feat(dees-screensaver): improve screensaver activation, visuals, and dismissal animations

This commit is contained in:
2026-01-06 02:39:34 +00:00
parent edea37a856
commit c8ff510832
3 changed files with 149 additions and 12 deletions

View File

@@ -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

View File

@@ -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.'
}

View File

@@ -28,7 +28,27 @@ const colors = [
@customElement('dees-screensaver')
export class DeesScreensaver extends DeesElement {
public static demo = () => html`<dees-screensaver .active=${true}></dees-screensaver>`;
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`<div style="padding: 24px; color: #888;">Screensaver will activate in 2 seconds...</div>`;
};
// 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`
<div class="backdrop" @click=${this.handleClick}></div>
<div class="time-container">
<span class="time" style="color: ${this.currentColor};">${this.currentTime}</span>
<span class="date" style="color: ${this.currentColor};">${this.currentDate}</span>
<div class="screensaver-content" @click=${this.handleClick}>
<div class="backdrop"></div>
<div class="vignette"></div>
<div class="time-container">
<span class="time" style="color: ${this.currentColor};">${this.currentTime}</span>
<span class="date" style="color: ${this.currentColor};">${this.currentDate}</span>
</div>
</div>
`;
}
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<void> {
@@ -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;
}
}
}