sdv2500/progettojava
0
1<!DOCTYPE html>2<html lang="it">3 4<head>5 <meta charset="UTF-8">6 <meta name="viewport" content="width=device-width, initial-scale=1.0">7 <title>HLS Zenith - Playlist Builder</title>8 <link rel="icon" type="image/x-icon" href="/favicon.ico">9 <style>10 @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');11 @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap');12 13 :root {14 /* Dark Theme Colors */15 --bg-primary: #0f0f23;16 --bg-secondary: #1a1a2e;17 --bg-tertiary: #16213e;18 --bg-card: rgba(26, 26, 46, 0.8);19 --surface: rgba(255, 255, 255, 0.05);20 --surface-glass: rgba(255, 255, 255, 0.08);21 --surface-elevated: rgba(255, 255, 255, 0.12);22 23 /* Modern Dark Gradients */24 --primary-gradient: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);25 --secondary-gradient: linear-gradient(135deg, #06b6d4 0%, #3b82f6 100%);26 --accent-gradient: linear-gradient(135deg, #f59e0b 0%, #f97316 100%);27 --success-gradient: linear-gradient(135deg, #10b981 0%, #059669 100%);28 --danger-gradient: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);29 --neon-gradient: linear-gradient(90deg, #00f5ff, #bf00ff, #ffff00, #bf00ff, #00f5ff);30 31 /* Accent Colors */32 --accent-primary: #6366f1;33 --accent-secondary: #8b5cf6;34 --accent-success: #10b981;35 --accent-warning: #f59e0b;36 --accent-error: #ef4444;37 --accent-info: #06b6d4;38 --neon-blue: #00f5ff;39 --neon-purple: #bf00ff;40 --neon-green: #39ff14;41 42 /* Text Colors */43 --text-primary: #ffffff;44 --text-secondary: #a1a1aa;45 --text-muted: #71717a;46 --text-accent: #e4e4e7;47 --text-inverse: #0f0f23;48 49 /* Input & Form Colors */50 --input-bg: rgba(255, 255, 255, 0.05);51 --input-border: rgba(255, 255, 255, 0.1);52 --input-border-focus: rgba(99, 102, 241, 0.5);53 --input-placeholder: #71717a;54 55 /* Border & Shadow */56 --border-primary: rgba(255, 255, 255, 0.1);57 --border-accent: rgba(255, 255, 255, 0.2);58 --glow-primary: 0 0 20px rgba(99, 102, 241, 0.3);59 --glow-secondary: 0 0 30px rgba(139, 92, 246, 0.4);60 --glow-success: 0 0 20px rgba(16, 185, 129, 0.4);61 --glow-neon: 0 0 40px rgba(0, 245, 255, 0.6);62 --shadow-dark: 0 25px 50px -12px rgba(0, 0, 0, 0.8);63 --shadow-card: 0 8px 32px rgba(0, 0, 0, 0.3);64 65 /* Animation */66 --transition-fast: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);67 --transition-normal: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);68 --transition-slow: all 0.6s cubic-bezier(0.4, 0, 0.2, 1);69 70 /* Measurements */71 --border-radius: 12px;72 --border-radius-large: 20px;73 --spacing-xs: 0.25rem;74 --spacing-sm: 0.5rem;75 --spacing-md: 1rem;76 --spacing-lg: 1.5rem;77 --spacing-xl: 2rem;78 --spacing-2xl: 3rem;79 }80 81 * {82 margin: 0;83 padding: 0;84 box-sizing: border-box;85 }86 87 body {88 font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;89 background: var(--bg-primary);90 color: var(--text-primary);91 min-height: 100vh;92 overflow-x: hidden;93 position: relative;94 }95 96 /* Animated Background */97 body::before {98 content: '';99 position: fixed;100 top: 0;101 left: 0;102 width: 100%;103 height: 100%;104 background:105 radial-gradient(circle at 20% 50%, rgba(99, 102, 241, 0.15) 0%, transparent 50%),106 radial-gradient(circle at 80% 20%, rgba(139, 92, 246, 0.15) 0%, transparent 50%),107 radial-gradient(circle at 40% 80%, rgba(6, 182, 212, 0.15) 0%, transparent 50%);108 animation: backgroundFloat 25s ease-in-out infinite;109 z-index: -1;110 }111 112 @keyframes backgroundFloat {113 114 0%,115 100% {116 transform: translateY(0) rotate(0deg) scale(1);117 }118 119 33% {120 transform: translateY(-20px) rotate(1deg) scale(1.02);121 }122 123 66% {124 transform: translateY(20px) rotate(-1deg) scale(0.98);125 }126 }127 128 .container {129 max-width: 1400px;130 margin: 0 auto;131 padding: var(--spacing-lg);132 min-height: 100vh;133 }134 135 .header {136 text-align: center;137 margin-bottom: var(--spacing-2xl);138 padding: var(--spacing-xl) 0;139 }140 141 .main-title {142 font-size: clamp(2rem, 5vw, 3.5rem);143 font-weight: 700;144 background: var(--neon-gradient);145 background-size: 300% 300%;146 -webkit-background-clip: text;147 -webkit-text-fill-color: transparent;148 background-clip: text;149 margin-bottom: var(--spacing-md);150 animation: gradientShift 4s ease-in-out infinite;151 }152 153 @keyframes gradientShift {154 155 0%,156 100% {157 background-position: 0% 50%;158 }159 160 50% {161 background-position: 100% 50%;162 }163 }164 165 .main-subtitle {166 font-size: 1.125rem;167 color: var(--text-secondary);168 font-weight: 400;169 max-width: 800px;170 margin: 0 auto;171 line-height: 1.6;172 }173 174 .main-content {175 display: grid;176 grid-template-columns: 1fr 1fr;177 gap: var(--spacing-2xl);178 align-items: start;179 }180 181 .left-panel,182 .right-panel {183 background: var(--bg-card);184 border: 1px solid var(--border-primary);185 border-radius: var(--border-radius-large);186 padding: var(--spacing-xl);187 backdrop-filter: blur(20px);188 box-shadow: var(--shadow-card);189 transition: var(--transition-normal);190 }191 192 .left-panel:hover,193 .right-panel:hover {194 border-color: var(--border-accent);195 box-shadow: var(--shadow-card), var(--glow-primary);196 }197 198 .section-title {199 font-size: 1.5rem;200 font-weight: 600;201 margin-bottom: var(--spacing-lg);202 color: var(--text-primary);203 display: flex;204 align-items: center;205 gap: var(--spacing-sm);206 }207 208 .section-icon {209 padding: var(--spacing-sm);210 border-radius: var(--spacing-sm);211 background: var(--primary-gradient);212 font-size: 1rem;213 }214 215 .form-group {216 margin-bottom: var(--spacing-lg);217 }218 219 .form-label {220 display: block;221 font-weight: 500;222 margin-bottom: var(--spacing-sm);223 color: var(--text-secondary);224 font-size: 0.9rem;225 text-transform: uppercase;226 letter-spacing: 0.05em;227 }228 229 .input-group {230 position: relative;231 display: flex;232 gap: var(--spacing-sm);233 }234 235 .form-input {236 flex: 1;237 padding: var(--spacing-md);238 background: var(--input-bg);239 border: 1px solid var(--input-border);240 border-radius: var(--border-radius);241 color: var(--text-primary);242 font-size: 1rem;243 transition: var(--transition-normal);244 backdrop-filter: blur(10px);245 }246 247 .form-input:focus {248 outline: none;249 border-color: var(--input-border-focus);250 box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1), var(--glow-primary);251 background: var(--surface-elevated);252 }253 254 .form-input::placeholder {255 color: var(--input-placeholder);256 }257 258 .btn {259 display: inline-flex;260 align-items: center;261 justify-content: center;262 gap: var(--spacing-sm);263 padding: var(--spacing-md) var(--spacing-lg);264 border: none;265 border-radius: var(--border-radius);266 font-weight: 500;267 font-size: 0.95rem;268 cursor: pointer;269 transition: var(--transition-normal);270 position: relative;271 overflow: hidden;272 text-decoration: none;273 min-width: 120px;274 }275 276 .btn::before {277 content: '';278 position: absolute;279 top: 0;280 left: -100%;281 width: 100%;282 height: 100%;283 background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);284 transition: var(--transition-fast);285 }286 287 .btn:hover::before {288 left: 100%;289 }290 291 .btn-primary {292 background: var(--primary-gradient);293 color: white;294 box-shadow: var(--glow-primary);295 }296 297 .btn-primary:hover {298 transform: translateY(-2px);299 box-shadow: var(--glow-secondary);300 }301 302 .btn-success {303 background: var(--success-gradient);304 color: white;305 box-shadow: var(--glow-success);306 }307 308 .btn-success:hover {309 transform: translateY(-2px);310 box-shadow: 0 0 30px rgba(16, 185, 129, 0.5);311 }312 313 .btn-secondary {314 background: var(--surface-glass);315 color: var(--text-primary);316 border: 1px solid var(--border-primary);317 backdrop-filter: blur(20px);318 }319 320 .btn-secondary:hover {321 background: var(--surface-elevated);322 border-color: var(--border-accent);323 transform: translateY(-2px);324 }325 326 .btn-icon-only {327 min-width: auto;328 width: 44px;329 height: 44px;330 padding: 0;331 border-radius: var(--spacing-sm);332 }333 334 .playlist-item {335 background: var(--surface);336 border: 1px solid var(--border-primary);337 border-radius: var(--border-radius);338 padding: var(--spacing-md);339 margin-bottom: var(--spacing-sm);340 display: flex;341 align-items: center;342 justify-content: space-between;343 transition: var(--transition-normal);344 animation: slideInLeft 0.4s ease-out;345 }346 347 .playlist-item:hover {348 background: var(--surface-elevated);349 border-color: var(--border-accent);350 transform: translateX(4px);351 }352 353 @keyframes slideInLeft {354 from {355 opacity: 0;356 transform: translateX(-20px);357 }358 359 to {360 opacity: 1;361 transform: translateX(0);362 }363 }364 365 .playlist-url {366 font-family: 'JetBrains Mono', monospace;367 font-size: 0.85rem;368 color: var(--text-secondary);369 word-break: break-all;370 margin-right: var(--spacing-sm);371 }372 373 .features-section {374 margin-bottom: var(--spacing-2xl);375 }376 377 .features-grid {378 display: grid;379 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));380 gap: var(--spacing-md);381 margin-bottom: var(--spacing-xl);382 }383 384 .feature-badge {385 background: var(--surface-glass);386 border: 1px solid var(--border-primary);387 border-radius: var(--border-radius);388 padding: var(--spacing-sm) var(--spacing-md);389 text-align: center;390 font-size: 0.85rem;391 color: var(--text-secondary);392 backdrop-filter: blur(10px);393 transition: var(--transition-normal);394 }395 396 .feature-badge:hover {397 background: var(--surface-elevated);398 color: var(--text-primary);399 transform: scale(1.02);400 }401 402 .examples-section {403 margin-top: var(--spacing-xl);404 }405 406 .examples-list {407 background: var(--surface);408 border: 1px solid var(--border-primary);409 border-radius: var(--border-radius);410 padding: var(--spacing-lg);411 margin-top: var(--spacing-md);412 }413 414 .example-item {415 display: flex;416 align-items: center;417 padding: var(--spacing-sm) 0;418 border-bottom: 1px solid var(--border-primary);419 color: var(--text-secondary);420 font-size: 0.9rem;421 }422 423 .example-item:last-child {424 border-bottom: none;425 }426 427 .example-bullet {428 color: var(--accent-primary);429 margin-right: var(--spacing-sm);430 font-weight: bold;431 }432 433 .output-section {434 position: relative;435 }436 437 .output-content {438 background: var(--bg-tertiary);439 border: 1px solid var(--border-primary);440 border-radius: var(--border-radius);441 padding: 0;442 /* Removed padding to let children control it */443 font-family: 'JetBrains Mono', monospace;444 font-size: 0.9rem;445 color: var(--text-secondary);446 white-space: pre-wrap;447 word-wrap: break-word;448 overflow-y: auto;449 position: relative;450 display: flex;451 flex-direction: column;452 justify-content: center;453 }454 455 .output-placeholder {456 color: var(--text-muted);457 font-style: italic;458 text-align: center;459 padding: var(--spacing-lg);460 display: flex;461 flex-direction: column;462 align-items: center;463 gap: var(--spacing-sm);464 }465 466 .output-placeholder-icon {467 font-size: 2rem;468 opacity: 0.5;469 }470 471 .loading-spinner {472 display: none;473 width: 40px;474 height: 40px;475 border: 3px solid var(--surface);476 border-top: 3px solid var(--accent-primary);477 border-radius: 50%;478 animation: spin 1s linear infinite;479 margin: var(--spacing-xl) auto;480 }481 482 @keyframes spin {483 0% {484 transform: rotate(0deg);485 }486 487 100% {488 transform: rotate(360deg);489 }490 }491 492 .copy-notification {493 position: fixed;494 top: 50%;495 left: 50%;496 transform: translate(-50%, -50%) scale(0);497 background: var(--success-gradient);498 color: white;499 padding: var(--spacing-lg) var(--spacing-xl);500 border-radius: var(--border-radius);501 font-weight: 500;502 box-shadow: var(--shadow-dark);503 z-index: 1000;504 transition: var(--transition-normal);505 }506 507 .copy-notification.show {508 transform: translate(-50%, -50%) scale(1);509 }510 511 .generated-link {512 padding: var(--spacing-md);513 display: flex;514 align-items: center;515 gap: var(--spacing-sm);516 }517 518 .link-text {519 font-family: 'JetBrains Mono', monospace;520 font-size: 0.85rem;521 color: var(--text-primary);522 word-break: break-all;523 flex: 1;524 background: rgba(0, 0, 0, 0.2);525 padding: var(--spacing-sm);526 border-radius: 4px;527 border: 1px solid var(--border-primary);528 }529 530 .link-label {531 color: var(--accent-success);532 font-weight: 600;533 font-size: 0.85rem;534 text-transform: uppercase;535 letter-spacing: 0.05em;536 margin-bottom: var(--spacing-xs);537 }538 539 /* Responsive Design */540 @media (max-width: 1024px) {541 .main-content {542 grid-template-columns: 1fr;543 gap: var(--spacing-xl);544 }545 }546 547 @media (max-width: 768px) {548 .container {549 padding: var(--spacing-md);550 }551 552 .features-grid {553 grid-template-columns: 1fr;554 }555 556 .input-group {557 flex-direction: column;558 }559 560 .btn {561 width: 100%;562 justify-content: center;563 }564 565 .generated-link {566 flex-direction: column;567 align-items: stretch;568 }569 }570 571 /* Scrollbar Styling */572 ::-webkit-scrollbar {573 width: 8px;574 }575 576 ::-webkit-scrollbar-track {577 background: var(--bg-secondary);578 border-radius: 4px;579 }580 581 ::-webkit-scrollbar-thumb {582 background: var(--surface-elevated);583 border-radius: 4px;584 }585 586 ::-webkit-scrollbar-thumb:hover {587 background: var(--border-accent);588 }589 590 /* Custom animations */591 .fade-in {592 animation: fadeIn 0.5s ease-out;593 }594 595 @keyframes fadeIn {596 from {597 opacity: 0;598 }599 600 to {601 opacity: 1;602 }603 }604 605 .pulse-success {606 animation: pulseSuccess 0.6s ease-in-out;607 }608 609 @keyframes pulseSuccess {610 0% {611 transform: scale(1);612 }613 614 50% {615 transform: scale(1.05);616 box-shadow: var(--glow-success);617 }618 619 100% {620 transform: scale(1);621 }622 }623 </style>624</head>625 626<body>627 <div class="container">628 <div class="header">629 <h1 class="main-title">Playlist Builder</h1>630 <p class="main-subtitle">Combina multiple playlist IPTV generando un link unificato per l'endpoint /playlist631 </p>632 </div>633 634 <div class="features-section">635 <div class="features-grid">636 <div class="feature-badge">🔗 Link unificato /playlist</div>637 <div class="feature-badge">🔧 Separazione automatica con ;</div>638 <div class="feature-badge">📺 Formato compatibile con server</div>639 <div class="feature-badge">🎯 Generazione istantanea</div>640 <div class="feature-badge">🌐 Supporto URL multipli</div>641 <div class="feature-badge">📋 Copia con un click</div>642 </div>643 </div>644 645 <div class="main-content">646 <div class="left-panel">647 <h2 class="section-title">648 <span class="section-icon">📝</span>649 Configurazione Playlist650 </h2>651 652 <form id="playlistForm">653 <div class="form-group">654 <label class="form-label">URL Playlist</label>655 <div class="input-group">656 <input type="url" id="urlInput" class="form-input"657 placeholder="https://provider.com/playlist.m3u8" required>658 <button type="button" id="addUrl" class="btn btn-primary btn-icon-only">659 ➕660 </button>661 </div>662 </div>663 664 <div class="form-group">665 <label class="form-label">API Password (Opzionale)</label>666 <div class="input-group">667 <input type="password" id="apiPasswordInput" class="form-input"668 placeholder="Inserisci la password API se richiesta dal server">669 </div>670 <small style="color: var(--text-muted); display: block; margin-top: 5px;">671 Questa password verrà inclusa nel link generato e usata per autenticare le richieste.672 </small>673 </div>674 </form>675 676 <div class="form-group">677 <label class="form-label">Playlist Aggiunte</label>678 <div id="playlistList">679 <div class="output-placeholder">680 <div class="output-placeholder-icon">📋</div>681 <p>Nessuna playlist aggiunta</p>682 <small>Inserisci gli URL delle playlist e clicca ➕ per aggiungerli</small>683 </div>684 </div>685 </div>686 687 <div class="form-group">688 <button id="clearBtn" class="btn btn-secondary" style="width: 100%;">689 <span>🗑️</span>690 <span>Pulisci Lista</span>691 </button>692 </div>693 </div>694 695 <div class="right-panel">696 <div class="output-section">697 <h2 class="section-title">698 <span class="section-icon">🔗</span>699 Link Generato700 <button id="copyBtn" class="btn btn-secondary btn-icon-only" style="margin-left: auto;"701 disabled>702 📋703 </button>704 </h2>705 706 <div class="loading-spinner" id="loadingSpinner"></div>707 708 <div class="output-content" id="outputContent">709 <div class="output-placeholder">710 <div class="output-placeholder-icon">🔗</div>711 <p>Il link combinato apparirà qui</p>712 <small>Inserisci gli URL delle playlist per creare il link</small>713 </div>714 </div>715 </div>716 </div>717 </div>718 </div>719 720 <div class="copy-notification" id="copyNotification">721 ✅ Link copiato negli appunti!722 </div>723 724 <script>725 class PlaylistBuilder {726 constructor() {727 this.playlists = [];728 this.initializeElements();729 this.attachEventListeners();730 }731 732 initializeElements() {733 this.urlInput = document.getElementById('urlInput');734 this.apiPasswordInput = document.getElementById('apiPasswordInput');735 this.addUrlBtn = document.getElementById('addUrl');736 this.playlistList = document.getElementById('playlistList');737 this.clearBtn = document.getElementById('clearBtn');738 this.outputContent = document.getElementById('outputContent');739 this.copyBtn = document.getElementById('copyBtn');740 this.loadingSpinner = document.getElementById('loadingSpinner');741 this.copyNotification = document.getElementById('copyNotification');742 }743 744 attachEventListeners() {745 this.addUrlBtn.addEventListener('click', () => this.addPlaylist());746 this.urlInput.addEventListener('keypress', (e) => {747 if (e.key === 'Enter') {748 e.preventDefault();749 this.addPlaylist();750 }751 });752 this.apiPasswordInput.addEventListener('input', () => this.generatePlaylistLink());753 this.clearBtn.addEventListener('click', () => this.clearPlaylists());754 this.copyBtn.addEventListener('click', () => this.copyToClipboard());755 }756 757 addPlaylist() {758 const url = this.urlInput.value.trim();759 if (!url) return;760 761 if (!this.isValidUrl(url)) {762 this.showNotification('❌ URL non valido', 'error');763 return;764 }765 766 // Check if URL already exists (check the url property of objects)767 if (this.playlists.some(p => p.url === url)) {768 this.showNotification('⚠️ URL già presente nella lista', 'warning');769 return;770 }771 772 this.playlists.push({773 url: url,774 sort: false,775 noProxy: false776 });777 778 this.urlInput.value = '';779 this.updatePlaylistList();780 this.generatePlaylistLink();781 782 // Animazione di successo783 this.addUrlBtn.classList.add('pulse-success');784 setTimeout(() => {785 this.addUrlBtn.classList.remove('pulse-success');786 }, 600);787 }788 789 isValidUrl(string) {790 try {791 const url = new URL(string);792 return url.protocol === 'http:' || url.protocol === 'https:';793 } catch (_) {794 return false;795 }796 }797 798 updatePlaylistList() {799 if (this.playlists.length === 0) {800 this.playlistList.innerHTML = `801 <div class="output-placeholder">802 <div class="output-placeholder-icon">📋</div>803 <p>Nessuna playlist aggiunta</p>804 <small>Inserisci gli URL delle playlist e clicca ➕ per aggiungerli</small>805 </div>806 `;807 return;808 }809 810 this.playlistList.innerHTML = this.playlists.map((item, index) => `811 <div class="playlist-item">812 <div style="flex: 1; min-width: 0;">813 <div class="playlist-url" title="${item.url}">${item.url}</div>814 <div style="display: flex; gap: 10px; margin-top: 8px;">815 <label style="display: flex; align-items: center; gap: 4px; font-size: 0.85rem; color: var(--text-secondary); cursor: pointer;">816 <input type="checkbox" ${item.sort ? 'checked' : ''} onchange="playlistBuilder.toggleSort(${index})">817 Sort A-Z818 </label>819 <label style="display: flex; align-items: center; gap: 4px; font-size: 0.85rem; color: var(--text-secondary); cursor: pointer;">820 <input type="checkbox" ${item.noProxy ? 'checked' : ''} onchange="playlistBuilder.toggleNoProxy(${index})">821 No Zenith822 </label>823 </div>824 </div>825 <button class="btn btn-secondary btn-icon-only" onclick="playlistBuilder.removePlaylist(${index})" style="margin-left: 10px;">826 ❌827 </button>828 </div>829 `).join('');830 }831 832 toggleSort(index) {833 this.playlists[index].sort = !this.playlists[index].sort;834 this.generatePlaylistLink();835 }836 837 toggleNoProxy(index) {838 this.playlists[index].noProxy = !this.playlists[index].noProxy;839 this.generatePlaylistLink();840 }841 842 removePlaylist(index) {843 this.playlists.splice(index, 1);844 this.updatePlaylistList();845 this.generatePlaylistLink();846 }847 848 generatePlaylistLink() {849 if (this.playlists.length === 0) {850 this.resetOutput();851 return;852 }853 854 const generatedLink = this.createPlaylistLink();855 this.displayResult(generatedLink);856 this.copyBtn.disabled = false;857 }858 859 async simulateLoading() {860 // Breve pausa per UX861 return new Promise(resolve => {862 setTimeout(resolve, 800);863 });864 }865 866 createPlaylistLink() {867 // Crea il link nel formato /playlist?url=URL1|sort=true|noproxy=false;URL2...868 const encodedDefinitions = this.playlists.map(p => {869 let def = p.url;870 if (p.sort) def += '|sort=true';871 if (p.noProxy) def += '|noproxy=true';872 return encodeURIComponent(def);873 });874 const urlString = encodedDefinitions.join(';');875 const baseUrl = window.location.origin;876 877 let finalUrl = `${baseUrl}/playlist?url=${urlString}`;878 879 // Aggiungi api_password se presente880 const apiPassword = this.apiPasswordInput.value.trim();881 if (apiPassword) {882 finalUrl += `&api_password=${encodeURIComponent(apiPassword)}`;883 }884 885 return finalUrl;886 }887 888 displayResult(link) {889 this.outputContent.innerHTML = `890 <div class="generated-link fade-in">891 <div class="link-text">${link}</div>892 </div>893 `;894 }895 896 setLoading(loading) {897 this.loadingSpinner.style.display = loading ? 'block' : 'none';898 if (loading) {899 this.outputContent.innerHTML = '<div class="output-placeholder"><div class="output-placeholder-icon">⏳</div><p>Generazione link in corso...</p></div>';900 }901 }902 903 async copyToClipboard() {904 const linkElement = document.querySelector('.link-text');905 if (!linkElement) return;906 907 const link = linkElement.textContent;908 909 try {910 await navigator.clipboard.writeText(link);911 this.showCopyNotification();912 913 // Animazione del bottone914 this.copyBtn.classList.add('pulse-success');915 setTimeout(() => {916 this.copyBtn.classList.remove('pulse-success');917 }, 600);918 } catch (err) {919 // Fallback per browser che non supportano clipboard API920 this.fallbackCopyTextToClipboard(link);921 }922 }923 924 fallbackCopyTextToClipboard(text) {925 const textArea = document.createElement('textarea');926 textArea.value = text;927 textArea.style.position = 'fixed';928 textArea.style.left = '-999999px';929 textArea.style.top = '-999999px';930 document.body.appendChild(textArea);931 textArea.focus();932 textArea.select();933 934 try {935 document.execCommand('copy');936 this.showCopyNotification();937 } catch (err) {938 this.showNotification('❌ Impossibile copiare negli appunti', 'error');939 }940 941 document.body.removeChild(textArea);942 }943 944 showCopyNotification() {945 this.copyNotification.classList.add('show');946 setTimeout(() => {947 this.copyNotification.classList.remove('show');948 }, 2000);949 }950 951 clearPlaylists() {952 this.playlists = [];953 this.updatePlaylistList();954 this.resetOutput();955 this.showNotification('🗑️ Lista playlist pulita', 'info');956 }957 958 resetOutput() {959 this.outputContent.innerHTML = `960 <div class="output-placeholder">961 <div class="output-placeholder-icon">🔗</div>962 <p>Il link combinato apparirà qui</p>963 <small>Inserisci gli URL delle playlist e clicca "Genera Link" per creare il link unificato</small>964 </div>965 `;966 this.copyBtn.disabled = true;967 }968 969 showError(message) {970 this.outputContent.innerHTML = `971 <div class="output-placeholder">972 <div class="output-placeholder-icon">❌</div>973 <p>Errore</p>974 <small>${message}</small>975 </div>976 `;977 this.showNotification('❌ ' + message, 'error');978 }979 980 showNotification(message, type = 'info') {981 // Implementazione semplificata per ora982 console.log(`${type.toUpperCase()}: ${message}`);983 }984 }985 986 // Inizializzazione987 const playlistBuilder = new PlaylistBuilder();988 989 // Effetti di interattività aggiuntivi990 document.addEventListener('DOMContentLoaded', function () {991 // Effetto hover sulle card992 const cards = document.querySelectorAll('.left-panel, .right-panel');993 994 cards.forEach(card => {995 card.addEventListener('mouseenter', function () {996 this.style.transform = 'translateY(-4px)';997 });998 999 card.addEventListener('mouseleave', function () {1000 this.style.transform = 'translateY(0)';1001 });1002 });1003 1004 // Animazione dei badge delle feature1005 const badges = document.querySelectorAll('.feature-badge');1006 badges.forEach((badge, index) => {1007 badge.style.animationDelay = `${index * 0.1}s`;1008 badge.classList.add('fade-in');1009 });1010 });1011 </script>1012</body>1013 1014</html>1015 