diff --git a/public/script.ts b/public/script.ts index bfc3a1d..aa04c43 100644 --- a/public/script.ts +++ b/public/script.ts @@ -1026,7 +1026,7 @@ class QuixoticApp { // Remove existing message if any const existingMessage = document.querySelector('.tg-status-message'); if (existingMessage) { - existingMessage.remove(); + this.hideMessage(existingMessage as HTMLElement); } // Create message element @@ -1034,15 +1034,37 @@ class QuixoticApp { messageEl.className = `tg-status-message tg-status-message--${type}`; messageEl.textContent = message; + // Add click handler to dismiss + let hideTimeout: NodeJS.Timeout; + + const hideHandler = () => { + clearTimeout(hideTimeout); + this.hideMessage(messageEl); + }; + + messageEl.addEventListener('click', hideHandler); + // Add to body (fixed position, won't affect layout) document.body.appendChild(messageEl); - // Auto-remove after 5 seconds + // Auto-remove after 3 seconds (было 5) + hideTimeout = setTimeout(() => { + if (messageEl.parentNode) { + this.hideMessage(messageEl); + } + }, 3000); + } + + private hideMessage(messageEl: HTMLElement): void { + // Add hiding class for fade-out animation + messageEl.classList.add('tg-status-message--hiding'); + + // Remove after animation completes (300ms) setTimeout(() => { if (messageEl.parentNode) { messageEl.remove(); } - }, 5000); + }, 300); } private escapeHtml(text: string): string { @@ -1133,13 +1155,27 @@ class QuixoticApp { this.stopAudioPreview(); }); - // Play audio - await this.currentAudio.play(); - this.triggerHaptic('light'); + // Play audio with autoplay error handling + try { + await this.currentAudio.play(); + this.triggerHaptic('light'); + } catch (playError: any) { + console.error('Autoplay error:', playError); + + // If autoplay is blocked, show message and clean up + if (playError.name === 'NotAllowedError') { + this.stopAudioPreview(); + item.classList.remove('tg-list-item--loading-preview'); + this.showMessage('🔇 Воспроизведение заблокировано. Нажмите еще раз.', 'warning'); + return; + } + throw playError; + } } catch (error) { console.error('Preview error:', error); item.classList.remove('tg-list-item--loading-preview'); + this.stopAudioPreview(); this.showMessage('❌ Не удалось воспроизвести превью', 'error'); } } diff --git a/public/style.css b/public/style.css index 129f42b..29e0a32 100644 --- a/public/style.css +++ b/public/style.css @@ -666,6 +666,16 @@ body { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2), 0 2px 6px rgba(0, 0, 0, 0.12); max-width: 320px; margin: 0 auto; + cursor: pointer; + transition: opacity 0.3s ease, transform 0.3s ease; +} + +.tg-status-message:active { + transform: scale(0.98); +} + +.tg-status-message--hiding { + animation: tg-fade-out 0.3s ease-out forwards; } .tg-status-message--success { @@ -686,6 +696,12 @@ body { color: #ffffff; } +.tg-status-message--warning { + background: #ff9500; + border: 1px solid #e68500; + color: #ffffff; +} + @keyframes tg-slide-down { from { opacity: 0; @@ -698,6 +714,18 @@ body { } } +@keyframes tg-fade-out { + from { + opacity: 1; + transform: translateY(0); + } + + to { + opacity: 0; + transform: translateY(-12px); + } +} + /* Update notification */ .tg-update-notification { position: fixed; diff --git a/src/server.ts b/src/server.ts index 6625992..10425aa 100644 --- a/src/server.ts +++ b/src/server.ts @@ -29,6 +29,7 @@ app.use((req: Request, res: Response, next) => { 'style-src \'self\' \'unsafe-inline\'; ' + 'img-src \'self\' data: https:; ' + 'font-src \'self\'; ' + + 'media-src \'self\' blob: data:; ' + 'connect-src \'self\' https://telegram.org; ' + 'frame-ancestors \'self\'; ' + 'base-uri \'self\'; ' +