feat(dees-screensaver): improve screensaver activation, visuals, and dismissal animations
This commit is contained in:
10
changelog.md
10
changelog.md
@@ -1,5 +1,15 @@
|
|||||||
# Changelog
|
# 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)
|
## 2026-01-06 - 3.33.0 - feat(dees-statsgrid)
|
||||||
add multiPercentage tile type to stats grid
|
add multiPercentage tile type to stats grid
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* autocreated commitinfo by @push.rocks/commitinfo
|
* autocreated commitinfo by @push.rocks/commitinfo
|
||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@design.estate/dees-catalog',
|
name: '@ecobridge.xyz/catalog',
|
||||||
version: '3.33.0',
|
version: '3.34.0',
|
||||||
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,27 @@ const colors = [
|
|||||||
|
|
||||||
@customElement('dees-screensaver')
|
@customElement('dees-screensaver')
|
||||||
export class DeesScreensaver extends DeesElement {
|
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
|
// Instance management
|
||||||
private static instance: DeesScreensaver | null = null;
|
private static instance: DeesScreensaver | null = null;
|
||||||
@@ -72,7 +92,7 @@ export class DeesScreensaver extends DeesElement {
|
|||||||
z-index: ${zIndexLayers.overlay.screensaver};
|
z-index: ${zIndexLayers.overlay.screensaver};
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.8s ease;
|
transition: opacity 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([active]) {
|
:host([active]) {
|
||||||
@@ -87,6 +107,48 @@ export class DeesScreensaver extends DeesElement {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: hsl(240 10% 4%);
|
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 {
|
.time-container {
|
||||||
@@ -100,6 +162,14 @@ export class DeesScreensaver extends DeesElement {
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
will-change: transform;
|
will-change: transform;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.8s ease;
|
||||||
|
transition-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([active]) .time-container {
|
||||||
|
opacity: 1;
|
||||||
|
transition-delay: 1.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time {
|
.time {
|
||||||
@@ -158,6 +228,8 @@ export class DeesScreensaver extends DeesElement {
|
|||||||
private elementHeight = 140;
|
private elementHeight = 140;
|
||||||
private hasBounced = false;
|
private hasBounced = false;
|
||||||
private timeContainerEl: HTMLElement | null = null;
|
private timeContainerEl: HTMLElement | null = null;
|
||||||
|
private vignetteEl: HTMLElement | null = null;
|
||||||
|
private contentEl: HTMLElement | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
@@ -166,16 +238,21 @@ export class DeesScreensaver extends DeesElement {
|
|||||||
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<div class="backdrop" @click=${this.handleClick}></div>
|
<div class="screensaver-content" @click=${this.handleClick}>
|
||||||
|
<div class="backdrop"></div>
|
||||||
|
<div class="vignette"></div>
|
||||||
<div class="time-container">
|
<div class="time-container">
|
||||||
<span class="time" style="color: ${this.currentColor};">${this.currentTime}</span>
|
<span class="time" style="color: ${this.currentColor};">${this.currentTime}</span>
|
||||||
<span class="date" style="color: ${this.currentColor};">${this.currentDate}</span>
|
<span class="date" style="color: ${this.currentColor};">${this.currentDate}</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public firstUpdated(): void {
|
public firstUpdated(): void {
|
||||||
this.timeContainerEl = this.shadowRoot?.querySelector('.time-container') as HTMLElement;
|
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> {
|
async connectedCallback(): Promise<void> {
|
||||||
@@ -194,6 +271,11 @@ export class DeesScreensaver extends DeesElement {
|
|||||||
super.updated(changedProperties);
|
super.updated(changedProperties);
|
||||||
if (changedProperties.has('active')) {
|
if (changedProperties.has('active')) {
|
||||||
if (this.active) {
|
if (this.active) {
|
||||||
|
// Reset mask for fresh activation
|
||||||
|
if (this.contentEl) {
|
||||||
|
this.contentEl.style.maskImage = '';
|
||||||
|
this.contentEl.style.webkitMaskImage = '';
|
||||||
|
}
|
||||||
this.startAnimation();
|
this.startAnimation();
|
||||||
this.startTimeUpdate();
|
this.startTimeUpdate();
|
||||||
} else {
|
} else {
|
||||||
@@ -311,9 +393,54 @@ export class DeesScreensaver extends DeesElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleClick(): void {
|
private handleClick(event: MouseEvent | TouchEvent): void {
|
||||||
this.dispatchEvent(new CustomEvent('screensaver-click'));
|
// Get click/touch position
|
||||||
// Optionally hide on click
|
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;
|
this.active = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user