const WEBAPP_URL="https://script.google.com/macros/s/AKfycbwUTuU1uMI0ExHkjJ50QYKuq6_hhcpMqo3w-0ia_6GtQGIVJ7bRFJ-_IUwvG3Ecf85t6Q/exec"; const PAYPAL_ME_HANDLE="francguitaro"; const RAW="What’s Up?|4 Non Blondes;A Horse with No Name|America;Valerie|Amy Winehouse;House of the Rising Sun|The Animals;The Thrill Is Gone|B.B. King;Get Back|The Beatles;Hey Jude|The Beatles|499;Let It Be|The Beatles;Norwegian Wood (This Bird Has Flown)|The Beatles;While My Guitar Gently Weeps|The Beatles;Yesterday|The Beatles;Stand by Me|Ben E. King;Ain’t No Sunshine|Bill Withers;Sweet Home Chicago|The Blues Brothers;Bo Diddley|Bo Diddley;All Along the Watchtower|Bob Dylan;Blowin’ in the Wind|Bob Dylan;Knockin’ on Heaven’s Door|Bob Dylan;Three Little Birds|Bob Marley & The Wailers;I’m on Fire|Bruce Springsteen;Should I Stay or Should I Go|The Clash;Zombie|The Cranberries;Bad Moon Rising|Creedence Clearwater Revival;Have You Ever Seen the Rain|Creedence Clearwater Revival;Proud Mary|Creedence Clearwater Revival;Smoke on the Water|Deep Purple|499;Riders on the Storm|The Doors;Roadhouse Blues|The Doors;Black Velvet Band|The Dubliners;Dirty Old Town|The Dubliners;Dublin in the Rare Old Times|The Dubliners;Molly Malone|The Dubliners;Seven Drunken Nights|The Dubliners;The Wild Rover|The Dubliners;Whiskey in the Jar|The Dubliners;Hotel California|Eagles|499;Blue Suede Shoes|Elvis Presley;Hound Dog|Elvis Presley;Bye Bye Love|The Everly Brothers|499;8 and 1|FrancGuitarO;Burning For…|FrancGuitarO;Dark Thoughts|FrancGuitarO;Emotional Rollercoaster|FrancGuitarO;Fine Girl|FrancGuitarO;Good Morning Blues|FrancGuitarO;Is White|FrancGuitarO;Let the Motor Run|FrancGuitarO;Longing|FrancGuitarO;Screw It Tight|FrancGuitarO;Take My Advice|FrancGuitarO;The Bend|FrancGuitarO;The First Day of My Life|FrancGuitarO;Trouble|FrancGuitarO;Where Has My Baby Gone|FrancGuitarO;My Way|Frank Sinatra|499;Mercedes Benz|Janis Joplin;Hallelujah|Jeff Buckley|499;Locomotive Breath|Jethro Tull;Hey Joe|Jimi Hendrix;Leaving on a Jet Plane|John Denver;Take Me Home, Country Roads|John Denver|499;Folsom Prison Blues|Johnny Cash;Ring of Fire|Johnny Cash;Dust in the Wind|Kansas|499;Sweet Home Alabama|Lynyrd Skynyrd|999;Nothing Else Matters|Metallica|999;Sweet Caroline|Neil Diamond|10000;Wonderwall|Oasis|10000;Wish You Were Here|Pink Floyd;Purple Rain|Prince|499;Creep|Radiohead|499;Hit the Road Jack|Ray Charles;Californication|Red Hot Chili Peppers;La Bamba|Ritchie Valens|999;(I Can’t Get No) Satisfaction|The Rolling Stones;As Tears Go By|The Rolling Stones|499;Dead Flowers|The Rolling Stones;Honky Tonk Women|The Rolling Stones;Paint It Black|The Rolling Stones;You Gotta Move|The Rolling Stones;Born to Be Wild|Steppenwolf|499;The Joker|Steve Miller Band;Mary Had a Little Lamb|Stevie Ray Vaughan;After Dark|Tito & Tarantula;Drunken Sailor|Traditional;Guantanamera|Traditional|499;The Fields of Athenry|Traditional;The Foggy Dew|Traditional;The Star of the County Down|Traditional;When the Saints Go Marching In|Traditional;Will Ye Go, Lassie, Go|Traditional;Tush|ZZ Top"; const songDatabase=RAW.split(";").map(function(s){ const p=s.split("|"); return{ title:p[0], artist:p[1], price:(p[2]||199)/100 }; }); let selectedSong=null; let reservedFiles=new Set(); let eventDate=""; const isBarMode= new URLSearchParams(window.location.search) .get("mode")==="bar"; function slug(v){ return String(v||"") .normalize("NFD") .replace(/[\u0300-\u036f]/g,"") .toLowerCase() .replace(/[^a-z0-9]+/g,"-") .replace(/^-+|-+$/g,""); } function reservationStem(s){ return eventDate+ "__"+ slug(s.title)+ "__"+ slug(s.artist); } function isReserved(s){ if(!eventDate){ return false; } return reservedFiles.has( reservationStem(s)+".req" ); } function showStatus(message,type){ const e= document.getElementById( "statusMessage" ); e.innerText=message; e.className= "status-msg status-"+type; e.style.display="block"; } function hideStatus(){ document.getElementById( "statusMessage" ).style.display="none"; } function jsonp(params){ return new Promise(function(resolve,reject){ const cb= "hlm_"+ Date.now()+ "_"+ Math.floor( Math.random()*1000000 ); const script= document.createElement("script"); const timer= setTimeout(function(){ cleanup(); reject( new Error("Timeout") ); },15000); function cleanup(){ clearTimeout(timer); if(script.parentNode){ script.parentNode.removeChild(script); } try{ delete window[cb]; }catch(e){ window[cb]=undefined; } } window[cb]=function(data){ cleanup(); resolve(data); }; params.callback=cb; params._=Date.now(); script.onerror=function(){ cleanup(); reject( new Error( "Verbindung fehlgeschlagen" ) ); }; script.src= WEBAPP_URL+ "?"+ new URLSearchParams(params).toString(); document.head.appendChild(script); }); } function renderCurrentSongs(){ const q= document.getElementById( "searchInput" ).value.toLowerCase().trim(); const songs= q ?songDatabase.filter(function(s){ return( s.title.toLowerCase().indexOf(q)>-1 || s.artist.toLowerCase().indexOf(q)>-1 ); }) :songDatabase; renderSongs(songs); } function renderSongs(songs){ const c= document.getElementById( "songList" ); c.innerHTML=""; songs.forEach(function(s){ const r=isReserved(s); const item= document.createElement("div"); item.className= r ?"song-item reserved" :"song-item"; if(!r){ item.onclick=function(){ selectSong(s,item); }; } const right= r ?"BEREITS
GEWÜNSCHT" :(isBarMode ?"– €"+s.price.toFixed(2) :""); item.innerHTML= ''+ ''+s.title+''+ '– '+s.artist+''+ '
'+ ''+ right+ ''; c.appendChild(item); }); } function filterSongs(){ renderCurrentSongs(); } function selectSong(s,item){ if(isReserved(s)){ return; } hideStatus(); document .querySelectorAll(".song-item") .forEach(function(e){ e.classList.remove("selected"); }); item.classList.add("selected"); selectedSong=s; document.getElementById( "selectionSummary" ).style.display="flex"; document.getElementById( "selectedSongTitle" ).innerText= s.title+" – "+s.artist; const button= document.getElementById( "submitBtn" ); button.style.display="block"; button.disabled=false; if(isBarMode){ document.getElementById( "selectedSongPrice" ).innerText= "– €"+s.price.toFixed(2); button.innerText= "Mit PayPal bezahlen (€"+ s.price.toFixed(2)+ ")"; button.className= "btn-submit"; }else{ document.getElementById( "selectedSongPrice" ).innerText= "Kostenlos"; button.innerText= "Song jetzt wünschen"; button.className= "btn-submit free"; } } async function refreshReservations(){ try{ const data= await jsonp({ action:"list" }); if( !data || !data.ok || !Array.isArray(data.files) ){ throw new Error( "Reservierungen konnten nicht geladen werden." ); } eventDate=data.eventDate; reservedFiles= new Set(data.files); renderCurrentSongs(); return true; }catch(e){ console.log(e); return false; } } async function submitRequest(){ if(!selectedSong){ return; } const guest= document.getElementById( "nameInput" ).value.trim(); if(!guest){ showStatus( "Bitte deinen Namen eingeben.", "error" ); document.getElementById( "nameInput" ).focus(); return; } const song=selectedSong; const button= document.getElementById( "submitBtn" ); button.disabled=true; showStatus( "Song wird reserviert...", "success" ); try{ const data= await jsonp({ action:"request", song:song.title, artist:song.artist, guest:guest, price:song.price.toFixed(2), mode: isBarMode ?"bar" :"free" }); if( data && data.alreadyReserved ){ if(data.file){ reservedFiles.add( data.file ); } renderCurrentSongs(); showStatus( "Dieser Song wurde heute bereits gewünscht.", "error" ); button.disabled=false; return; } if( !data || !data.ok || !data.file ){ throw new Error( "Request fehlgeschlagen" ); } eventDate= data.eventDate; reservedFiles.add( data.file ); renderCurrentSongs(); if(isBarMode){ const payUrl= "https://paypal.me/"+ PAYPAL_ME_HANDLE+ "/"+ song.price.toFixed(2)+ "EUR"; showStatus( "Reserviert – PayPal wird geöffnet...", "success" ); window.location.href= payUrl; }else{ showStatus( "Vielen Dank! Dein Songwunsch wurde übermittelt.", "success" ); button.disabled=false; } }catch(e){ console.log(e); showStatus( "Song konnte nicht reserviert werden. Bitte noch einmal versuchen.", "error" ); button.disabled=false; } } function notifyMusician( songTitle, guestName, paymentInfo ){ if( String(songTitle) .indexOf( "SUGGESTION:" )!==0 ){ return; } const payload= JSON.stringify({ action:"suggestion", song:songTitle, guest:guestName, paymentInfo:paymentInfo }); try{ const blob= new Blob( [payload], { type: "text/plain;charset=UTF-8" } ); if( navigator.sendBeacon && navigator.sendBeacon( WEBAPP_URL, blob ) ){ return; } }catch(e){} fetch( WEBAPP_URL, { method:"POST", mode:"no-cors", headers:{ "Content-Type": "text/plain;charset=UTF-8" }, body:payload, keepalive:true } ).catch(function(){}); } document.getElementById( "nameInput" ).addEventListener( "keydown", function(e){ if(e.key==="Enter"){ e.preventDefault(); submitRequest(); } } ); renderCurrentSongs(); refreshReservations(); (function(){ function requestPage(){ return location.hash .toLowerCase() .indexOf('#request')===0; } function mobile(){ return window .matchMedia('(max-width:600px)') .matches; } function cleanText(el){ return ( el.innerText || el.textContent || '' ) .replace(/\s+/g,'') .toLowerCase(); } function isTopJunkText(text){ if(text==='hirelivemusic'){ return true; } if( text && /^[.…·•]+$/.test(text) ){ return true; } return false; } /* HIRELIVEMUSIC + PUNKTE + ALLE ELEMENTE OBERHALB DES REQUEST-FORMULARS ENTFERNEN */ function removeMobileTop(root){ if( !mobile() || !requestPage() || !root ){ return; } var rootRect= root.getBoundingClientRect(); var all= Array.prototype.slice.call( document.body.querySelectorAll('*') ); /* HIRELIVEMUSIC UND PUNKTE GEZIELT */ all.forEach(function(el){ if( el===root || root.contains(el) || el.contains(root) ){ return; } var text= cleanText(el); if(!isTopJunkText(text)){ return; } var node=el; while( node.parentElement && node.parentElement!==document.body && !node.parentElement.contains(root) && isTopJunkText( cleanText(node.parentElement) ) ){ node= node.parentElement; } node.classList.add( 'hlm-mobile-remove' ); }); /* ALLES SICHTBARE, DAS KOMPLETT OBERHALB DES REQUEST-BLOCKS LIEGT */ rootRect= root.getBoundingClientRect(); all.forEach(function(el){ if( el===root || root.contains(el) || el.contains(root) ){ return; } if( el.tagName==='SCRIPT' || el.tagName==='STYLE' || el.tagName==='LINK' || el.tagName==='META' ){ return; } var rect= el.getBoundingClientRect(); if( rect.width<=0 || rect.height<=0 ){ return; } if( rect.bottom <= rootRect.top + 2 && rect.top >= -10 ){ el.classList.add( 'hlm-mobile-remove' ); } }); } /* MEHRERE DURCHLÄUFE, WEIL CARRD NACH DEM AUSBLENDEN NACHRUTSCHT */ function cleanMobileTop(root,count){ if(count<=0){ return; } removeMobileTop(root); requestAnimationFrame(function(){ cleanMobileTop( root, count-1 ); }); } /* AUFBAU */ function setup(){ document.documentElement .classList.toggle( 'hlm-request', requestPage() ); if(!requestPage()){ return; } var root= document.querySelector( '.song-request-container' ); var search= document.getElementById( 'searchInput' ); var name= document.getElementById( 'nameInput' ); if( !root || !search ){ return; } cleanMobileTop(root,4); /* TITEL */ if( !document.getElementById( 'hlmRequestTitle' ) ){ var title= document.createElement( 'div' ); title.id= 'hlmRequestTitle'; title.textContent= 'LIVE SONG REQUEST'; search.parentNode.insertBefore( title, search ); } /* SUCHÜBERSCHRIFT */ if( !document.getElementById( 'hlmSearchLabel' ) ){ var searchLabel= document.createElement( 'label' ); searchLabel.id= 'hlmSearchLabel'; searchLabel.className= 'hlmLabel'; searchLabel.htmlFor= 'searchInput'; searchLabel.textContent= 'SEARCH AVAILABLE SONGS'; search.parentNode.insertBefore( searchLabel, search ); } /* NAME */ if( name && !document.getElementById( 'hlmNameLabel' ) ){ var nameLabel= document.createElement( 'label' ); nameLabel.id= 'hlmNameLabel'; nameLabel.className= 'hlmLabel'; nameLabel.htmlFor= 'nameInput'; nameLabel.textContent= 'YOUR NAME'; name.parentNode.insertBefore( nameLabel, name ); } search.placeholder= 'Search song or artist...'; search.autocomplete= 'off'; if(name){ name.placeholder= 'Your name...'; } buildSuggestions(root); installMobileSearch(search); setTimeout(function(){ cleanMobileTop(root,4); },250); setTimeout(function(){ cleanMobileTop(root,4); },800); } /* MOBILE SUCHE */ function installMobileSearch(search){ if( search.dataset.hlmMobileSearch==='1' ){ return; } search.dataset.hlmMobileSearch='1'; var list= document.getElementById( 'songList' ); if(!list){ return; } function alignAndFit(){ if( !mobile() || document.activeElement!==search ){ return; } var label= document.getElementById( 'hlmSearchLabel' ); if(label){ label.scrollIntoView({ behavior:'auto', block:'start', inline:'nearest' }); } requestAnimationFrame(function(){ var vv= window.visualViewport; var viewportTop= vv ? vv.offsetTop : 0; var viewportBottom= vv ? vv.offsetTop + vv.height : window.innerHeight; var listRect= list.getBoundingClientRect(); var available= Math.floor( viewportBottom - Math.max( listRect.top, viewportTop ) - 6 ); available= Math.max( 70, Math.min( 340, available ) ); list.style.setProperty( 'max-height', available+'px', 'important' ); }); } function startSearch(){ if(!mobile()){ return; } document.documentElement .classList.add( 'hlm-searching' ); requestAnimationFrame( alignAndFit ); setTimeout( alignAndFit, 100 ); setTimeout( alignAndFit, 250 ); setTimeout( alignAndFit, 450 ); } function stopSearch(){ setTimeout(function(){ if( document.activeElement!==search ){ document.documentElement .classList.remove( 'hlm-searching' ); list.style.removeProperty( 'max-height' ); } },150); } search.addEventListener( 'focus', startSearch ); search.addEventListener( 'input', alignAndFit ); search.addEventListener( 'blur', stopSearch ); if(window.visualViewport){ window.visualViewport .addEventListener( 'resize', alignAndFit ); window.visualViewport .addEventListener( 'scroll', alignAndFit ); } else{ window.addEventListener( 'resize', alignAndFit ); } } /* SUGGESTIONS */ function buildSuggestions(root){ var section= document.getElementById( 'hlmSuggestionSection' ); if( section && section.parentNode!==root ){ section.remove(); section=null; } if(section){ return; } section= document.createElement( 'section' ); section.id= 'hlmSuggestionSection'; var title= document.createElement( 'div' ); title.id= 'hlmSuggestionTitle'; title.textContent= 'SUGGESTIONS FOR FUTURE SONGS'; var p1= document.createElement( 'p' ); p1.textContent= 'Your song isn’t on the list? Feel free to suggest it here.'; var p2= document.createElement( 'p' ); p2.textContent= 'I’m a real musician, not a computer, so I can’t play every song on demand. But I’m always adding new songs and will try to include popular suggestions in the future.'; var p3= document.createElement( 'p' ); p3.textContent= 'Thanks for understanding!'; var input= document.createElement( 'input' ); input.id= 'hlmSuggestionInput'; input.type= 'text'; input.placeholder= 'Song title — artist'; input.autocomplete= 'off'; var button= document.createElement( 'button' ); button.id= 'hlmSuggestionButton'; button.type= 'button'; button.textContent= 'SEND SUGGESTION'; var status= document.createElement( 'div' ); status.id= 'hlmSuggestionStatus'; section.appendChild(title); section.appendChild(p1); section.appendChild(p2); section.appendChild(p3); section.appendChild(input); section.appendChild(button); section.appendChild(status); root.appendChild(section); button.addEventListener( 'click', sendSuggestion ); input.addEventListener( 'keydown', function(e){ if(e.key==='Enter'){ e.preventDefault(); sendSuggestion(); } } ); } /* STATUS */ function suggestionStatus( text, error ){ var box= document.getElementById( 'hlmSuggestionStatus' ); if(!box){ return; } box.textContent= text; box.style.setProperty( 'display', 'block', 'important' ); box.style.borderColor= error ? '#c0392b' : '#c5a059'; } /* SENDEN */ function sendSuggestion(){ var input= document.getElementById( 'hlmSuggestionInput' ); if(!input){ return; } var value= input.value.trim(); if(!value){ suggestionStatus( 'Please enter a song title and artist.', true ); input.focus(); return; } if( typeof notifyMusician!== 'function' ){ suggestionStatus( 'Suggestion could not be sent.', true ); return; } var name= document.getElementById( 'nameInput' ); var guest= name && name.value.trim() ? name.value.trim() : 'Anonymous'; notifyMusician( 'SUGGESTION: '+value, guest, '💡 Status: Suggestion for future songs' ); input.value=''; suggestionStatus( 'Thank you — your suggestion has been sent.', false ); } /* START */ if( document.readyState==='loading' ){ document.addEventListener( 'DOMContentLoaded', setup, {once:true} ); } else{ setup(); } window.addEventListener( 'load', setup ); window.addEventListener( 'hashchange', setup ); setTimeout( setup, 250 ); setTimeout( setup, 800 ); })();