// v42 compatibility helpers: Chrome/Edge/Firefox/Safari on Windows/macOS/iOS/Android
if (typeof window.structuredClone !== 'function') {
  window.structuredClone = function(obj){ return JSON.parse(JSON.stringify(obj)); };
}
function fsElement(){ return document.fullscreenElement || document.webkitFullscreenElement || document.msFullscreenElement || null; }
function isFullscreenActive(){ return !!fsElement() || document.body.classList.contains('fullscreen-active'); }
function updateViewportHeight(){
  const h = (window.visualViewport && window.visualViewport.height) ? window.visualViewport.height : window.innerHeight;
  document.documentElement.style.setProperty('--app-height', `${h}px`);
}
function applyFullscreenLock(){
  updateViewportHeight();
  document.documentElement.style.overflow = 'hidden';
  document.body.style.overflow = 'hidden';
  document.body.style.position = 'fixed';
  document.body.style.inset = '0';
  document.body.style.width = '100%';
  document.body.style.height = 'var(--app-height)';
}
function clearFullscreenLock(){
  document.documentElement.style.overflow = '';
  document.body.style.overflow = '';
  document.body.style.position = '';
  document.body.style.inset = '';
  document.body.style.width = '';
  document.body.style.height = '';
}
function requestAppFullscreen(){
  const el = document.documentElement;
  const fn = el.requestFullscreen || el.webkitRequestFullscreen || el.msRequestFullscreen;
  if (fn) {
    try { return fn.call(el); } catch(e) { console.warn('Fullscreen fallback aktiv:', e); return Promise.reject(e); }
  }
  return Promise.reject(new Error('Native Fullscreen API nicht verfügbar. CSS-Fallback aktiv.'));
}
function exitAppFullscreen(){
  const cleanup = () => {
    delete document.body.dataset.pseudoFullscreen;
    document.body.classList.remove('fullscreen-dark','fullscreen-light','fullscreen-active');
    clearFullscreenLock();
    hideMobileFullscreenControls?.();
  };
  const fn = document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen;
  if (fn && fsElement()) {
    try {
      const p = fn.call(document);
      Promise.resolve(p).finally(cleanup);
      return p;
    } catch(e) { console.warn('Fullscreen exit fallback aktiv:', e); }
  }
  cleanup();
  return Promise.resolve();
}
function handleFullscreenChange(){
  if(fsElement()){
    delete document.body.dataset.pseudoFullscreen;
    applyFullscreenLock();
  }
  if(!fsElement() && document.body.classList.contains('fullscreen-active') && !document.body.dataset.pseudoFullscreen){
    document.body.classList.remove('fullscreen-dark','fullscreen-light','fullscreen-active');
    clearFullscreenLock();
  }
  updateMobileFullscreenControls?.();
  if(!isFullscreenActive()) hideMobileFullscreenControls?.();
}
const defaultFlow={rundenpause:'erste_halbzeit',erste_halbzeit:'halbzeitpause',halbzeitpause:'zweite_halbzeit',zweite_halbzeit:'spielpause',spielpause:'erste_halbzeit',verlaengerung_1:'verlaengerung_pause',verlaengerung_pause:'verlaengerung_2'};
const trainingFlow={rundenpause:'erste_halbzeit',erste_halbzeit:'halbzeitpause',halbzeitpause:'zweite_halbzeit'};
const defaultConfig={last_template:'training',times:{rundenpause:'00:15',erste_halbzeit:'05:00',halbzeitpause:'00:30',zweite_halbzeit:'05:00',spielpause:'01:50',verlaengerung_1:'02:00',verlaengerung_2:'02:00',verlaengerung_pause:'00:20'},flow:{...trainingFlow},sequence:['rundenpause','erste_halbzeit','halbzeitpause','zweite_halbzeit','spielpause'],sounds:{platten_fertig:'sounds/legacy/fertig.wav',dreissig:'sounds/legacy/30.wav',sechzig:'sounds/legacy/60.wav',fuenf_min_start:'sounds/legacy/300.wav',pfeife:'sounds/legacy/aus.wav',pfeife_halbzeit:'sounds/legacy/Halbzeit.wav',letzte_minute:'sounds/legacy/letzte.wav',zeit_laeuft:'sounds/legacy/zeit.wav',abpfiff:'sounds/legacy/aus.wav'},music:{enabled:false,file:'sounds/music/minimal-loop.mp3',normal_volume:35,ducked_volume:12},branding:{enabled:true,logo:'logos/DTKV.png',text:'DTKV',fullscreen_theme:'dark',logo_bg_enabled:true,logo_bg_color:'#ffffff',theme_color:'#3d7b42'},ui:{tooltips:false}};
const labels={rundenpause:['stopwatch','Rundenpause'],erste_halbzeit:['play','1. Halbzeit'],halbzeitpause:['pauseCircle','Halbzeitpause'],zweite_halbzeit:['play','2. Halbzeit'],spielpause:['reset','Spielpause'],verlaengerung_1:['play','1. HZ Verlängerung'],verlaengerung_pause:['pauseCircle','Verlängerungspause'],verlaengerung_2:['play','2. HZ Verlängerung']};
const soundLabels={fuenf_min_start:'Noch 5 Minuten',sechzig:'60 Sekunden',dreissig:'30 Sekunden',platten_fertig:'Platten fertig',zeit_laeuft:'Zeit läuft',pfeife_halbzeit:'Halbzeit',letzte_minute:'Letzte Minute',abpfiff:'Abpfiff Ende'};
const soundTemplates={legacy:{platten_fertig:'sounds/legacy/fertig.wav',dreissig:'sounds/legacy/30.wav',sechzig:'sounds/legacy/60.wav',fuenf_min_start:'sounds/legacy/300.wav',pfeife:'sounds/legacy/aus.wav',pfeife_halbzeit:'sounds/legacy/Halbzeit.wav',letzte_minute:'sounds/legacy/letzte.wav',zeit_laeuft:'sounds/legacy/zeit.wav',abpfiff:'sounds/legacy/aus.wav'},computer:{platten_fertig:'soundpacks/Computer_01/platten_fertig.mp3',dreissig:'soundpacks/Computer_01/dreissig.mp3',sechzig:'soundpacks/Computer_01/sechzig.mp3',fuenf_min_start:'soundpacks/Computer_01/fuenf_min_start.mp3',pfeife:'soundpacks/DTKV_Standard/pfeife.wav',pfeife_halbzeit:'soundpacks/Computer_01/pfeife_halbzeit.mp3',letzte_minute:'soundpacks/Computer_01/letzte_minute.mp3',zeit_laeuft:'soundpacks/Computer_01/zeit_laeuft.mp3',abpfiff:'soundpacks/DTKV_Standard/abpfiff.wav'}};

function getCustomTemplates(kind){try{return JSON.parse(localStorage.getItem(kind)||'{}')}catch(e){return {}}}
function setCustomTemplates(kind,obj){localStorage.setItem(kind,JSON.stringify(obj||{}))}
const builtinTimeTemplates={
  training:{name:'Training',times:{rundenpause:'00:15',erste_halbzeit:'05:00',halbzeitpause:'00:30',zweite_halbzeit:'05:00',spielpause:'01:50',verlaengerung_1:'02:00',verlaengerung_2:'02:00',verlaengerung_pause:'00:20'},flow:{...trainingFlow}},
  turniermodus:{name:'Turniermodus',times:{rundenpause:'10:00',erste_halbzeit:'05:00',halbzeitpause:'00:20',zweite_halbzeit:'05:00',spielpause:'01:50',verlaengerung_1:'02:00',verlaengerung_2:'02:00',verlaengerung_pause:'00:20'},flow:{...defaultFlow}}
};
function populateTimeTemplates(){const sel=$('timeTemplate'); if(!sel)return; sel.innerHTML=''; Object.entries(builtinTimeTemplates).forEach(([k,v])=>sel.add(new Option(v.name,k))); Object.keys(getCustomTemplates('tk_time_templates')).forEach(k=>sel.add(new Option(k,'custom:'+k))); if(config?.last_template && builtinTimeTemplates[config.last_template]) sel.value=config.last_template;}
function populateSoundTemplates(){const sel=$('soundTemplate'); if(!sel)return; sel.innerHTML=''; sel.add(new Option('DTKV Standard','legacy')); sel.add(new Option('Computer 01','computer')); Object.keys(getCustomTemplates('tk_sound_templates')).forEach(k=>sel.add(new Option(k,'custom:'+k)));}

const svg={
  play:'<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7-11-7z"/></svg>',
  pause:'<svg viewBox="0 0 24 24"><path d="M8 5v14M16 5v14"/></svg>',
  reset:'<svg viewBox="0 0 24 24"><path d="M20 11a8 8 0 1 0-2.34 5.66"/><path d="M20 4v7h-7"/></svg>',
  screen:'<svg viewBox="0 0 24 24"><path d="M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M8 21H5a2 2 0 0 1-2-2v-3M16 21h3a2 2 0 0 0 2-2v-3"/></svg>',
  music:'<svg viewBox="0 0 24 24"><path d="M4 14v-4"/><path d="M8 17V7"/><path d="M12 20V4"/><path d="M16 17V7"/><path d="M20 14v-4"/></svg>',
  speaker:'<svg viewBox="0 0 24 24"><path d="M11 5 6 9H3v6h3l5 4V5z"/><path d="M15 9.5a4 4 0 0 1 0 5"/><path d="M17.5 7a8 8 0 0 1 0 10"/></svg>',
  stopwatch:'<svg viewBox="0 0 24 24"><path d="M9 2h6"/><path d="M12 6v6l3 2"/><circle cx="12" cy="14" r="8"/></svg>',
  hourglass:'<svg viewBox="0 0 24 24"><path d="M6 3h12M6 21h12M8 3c0 5 8 5 8 10s-8 5-8 10M16 3c0 5-8 5-8 10s8 5 8 10"/></svg>',
  timer:'<svg viewBox="0 0 24 24"><path d="M10 2h4"/><path d="M12 8v5l3 2"/><circle cx="12" cy="14" r="8"/></svg>',
  coffee:'<svg viewBox="0 0 24 24"><path d="M5 8h10v6a4 4 0 0 1-4 4H9a4 4 0 0 1-4-4V8z"/><path d="M15 10h2a2.5 2.5 0 0 1 0 5h-2"/></svg>',
  break:'<svg viewBox="0 0 24 24"><path d="M8 5v14M16 5v14"/></svg>',
  pauseCircle:'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M10 8v8M14 8v8"/></svg>'
};
function icon(name){return svg[name]||''}

let config=JSON.parse(localStorage.getItem('tk_config')||'null')||structuredClone(defaultConfig); config.ui=config.ui||{tooltips:false};
let current='rundenpause',remaining=parseTime(config.times[current]),running=false,timer=null,lastTick=0,wakelock=null,audioUnlocked=false,countdownActive=false,countdownTarget=null;
const pausePhases=new Set(['rundenpause','halbzeitpause','spielpause','verlaengerung_pause']);
const musicPhases=new Set(['rundenpause','spielpause']);
const playPhases=new Set(['erste_halbzeit','zweite_halbzeit','verlaengerung_1','verlaengerung_2']);
const announcementPhases=new Set(['rundenpause','spielpause']);
const music=new Audio(config.music.file||''); music.loop=false;
let activeSounds=[];
music.addEventListener('ended',()=>{ const b=$('previewMusic'); if(b)b.textContent='▶ Musik testen'; });
function shouldPauseMusicPlay(){ return !!(config.music.enabled && running && musicPhases.has(current) && remaining>30); }
function $(id){return document.getElementById(id)}
function parseTime(s){const [m,sec]=(s||'00:00').split(':').map(Number);return (m||0)*60+(sec||0)}
function fmt(t){t=Math.max(0,Math.ceil(t));return `${String(Math.floor(t/60)).padStart(2,'0')}:${String(t%60).padStart(2,'0')}`}
function save(){localStorage.setItem('tk_config',JSON.stringify(config))}
function toast(title='Gespeichert',msg='Einstellungen gespeichert.'){
  const t=$('toast');
  // Wichtig: Der Toast wird direkt an <body> gehängt und per Inline-Styles zentriert.
  // Dadurch wird er nicht mehr von scrollbaren Panels, alten CSS-Regeln oder Layout-Containern beeinflusst.
  if(t.parentElement!==document.body) document.body.appendChild(t);
  t.querySelector('strong').textContent=title;
  t.querySelector('span').textContent=msg;
  t.hidden=false;
  Object.assign(t.style,{
    position:'fixed', left:'50%', top:'50%', right:'auto', bottom:'auto',
    transform:'translate(-50%, -50%)', margin:'0', width:'min(340px, 88vw)', height:'auto',
    minHeight:'0', maxHeight:'none', padding:'18px 20px', zIndex:'2147483647',
    display:'grid', gridTemplateColumns:'42px 1fr', gridTemplateRows:'auto auto auto',
    gap:'7px 14px', alignItems:'center', background:'#fff', color:'var(--text)',
    borderRadius:'14px', boxShadow:'0 18px 60px rgba(0,0,0,.28)', overflow:'visible'
  });
  clearTimeout(window.__toastTimer);
  window.__toastTimer=setTimeout(()=>{t.hidden=true;},2200);
}
$('toast').querySelector('button').onclick=()=>{clearTimeout(window.__toastTimer);$('toast').hidden=true;};
function setPhase(k,autoStart=false){countdownActive=false;countdownTarget=null;current=k;remaining=parseTime(config.times[k]);running=false;clearInterval(timer);timer=null;played={};update(); updatePauseMusic(); if(autoStart) setTimeout(start,350);}
function update(){
  $('timeDisplay').textContent=countdownActive?String(Math.max(0,Math.ceil(remaining))):fmt(remaining);
  $('phaseTitle').textContent=countdownActive?'Start in …':(labels[current]?.[1]||current);
  document.querySelectorAll('.phase-btn').forEach(b=>b.classList.toggle('active',!countdownActive&&b.dataset.phase===current));
  const isActive=(running||countdownActive);
  const startPause=$('startPause');
  startPause.querySelector('.control-icon').innerHTML=icon(isActive?'pause':'play');
  const startPauseLabel=startPause.querySelector('span:last-child');
  if(startPauseLabel) startPauseLabel.textContent=isActive?'Pause':'Start';
  updateMobileFullscreenControls?.();
}
function playSound(k){ if(!config.sounds[k])return; const a=new Audio(config.sounds[k]); a.volume=(Number($('volume')?.value||100))/100; activeSounds.push(a); a.addEventListener('ended',()=>{activeSounds=activeSounds.filter(x=>x!==a)}); a.play().catch(()=>console.warn('Audio konnte nicht abgespielt werden:',k)); }
function unlockAudio(){ if(audioUnlocked)return; audioUnlocked=true; try{ const C=window.AudioContext||window.webkitAudioContext; if(C){ const ctx=new C(); const o=ctx.createOscillator(); const g=ctx.createGain(); g.gain.value=0.0001; o.connect(g); g.connect(ctx.destination); o.start(); o.stop(ctx.currentTime+0.03); setTimeout(()=>ctx.close?.(),120); }}catch(e){} }
function start(){unlockAudio(); if(running)return; clearInterval(timer); running=true; lastTick=performance.now(); if(playPhases.has(current)) playSound('zeit_laeuft'); if(current==='halbzeitpause'||current==='verlaengerung_pause') playSound('pfeife_halbzeit'); timer=setInterval(tick,200); requestWakeLock(); update(); updatePauseMusic(); }
function pause(){ if(countdownActive){countdownActive=false;countdownTarget=null;} running=false; if(timer){clearInterval(timer); timer=null;} releaseWakeLock(); update(); updatePauseMusic();}
function toggleRun(){ running ? pause() : start(); }
function tick(){ const now=performance.now(); remaining-=(now-lastTick)/1000; lastTick=now; const r=Math.ceil(remaining);
  if(announcementPhases.has(current)){
    if(r===300) playOnce('fuenf_min_start',r);
    if(r===60) playOnce('sechzig',r);
    if(r===30) playOnce('dreissig',r);
  }
  if((current==='rundenpause'||current==='spielpause'||current==='verlaengerung_pause') && r===3) playOnce('platten_fertig',3);
  if(current==='halbzeitpause' && r===3) playOnce('platten_fertig',3);
  if(musicPhases.has(current) && r<=30) updatePauseMusic();
  if((current==='zweite_halbzeit'||current==='verlaengerung_2') && r===60) playOnce('letzte_minute',999);
  if(remaining<=0){remaining=0; const finished=current; pause(); if(playPhases.has(finished)) playSound('abpfiff'); autoNext(finished);}
  update(); }
let played={}; function playOnce(k,r){const key=current+'-'+r+'-'+k;if(!played[key]){played[key]=1;playSound(k)}}
function autoNext(finished=current){
  const flow=config.flow||defaultFlow;
  const next=flow[finished];
  if(next) setTimeout(()=>setPhase(next,true),800);
}

function startCountdown(){
  unlockAudio();
  clearInterval(timer);
  releaseWakeLock();
  running=false;
  countdownTarget=playPhases.has(current)?current:'erste_halbzeit';
  countdownActive=true;
  remaining=10;
  played={};
  lastTick=performance.now();
  requestWakeLock();
  updatePauseMusic();
  update();
  timer=setInterval(countdownTick,120);
}
function countdownTick(){
  const now=performance.now();
  remaining-=(now-lastTick)/1000;
  lastTick=now;
  const r=Math.ceil(remaining);
  if(r===3 && !played.countdown_platten_fertig){
    played.countdown_platten_fertig=1;
    playSound('platten_fertig');
  }
  if(remaining<=0){
    const target=countdownTarget||'erste_halbzeit';
    clearInterval(timer);
    timer=null;
    countdownActive=false;
    countdownTarget=null;
    setPhase(target,false);
    start();
    return;
  }
  update();
}

async function requestWakeLock(){try{if('wakeLock'in navigator)wakelock=await navigator.wakeLock.request('screen')}catch(e){}}
function releaseWakeLock(){try{wakelock?.release()}catch(e){} wakelock=null}

const phaseTips={
  rundenpause:'Pause zwischen zwei Runden. Musik läuft bis 30 Sekunden vor Ende.' ,
  erste_halbzeit:'Startet die 1. Halbzeit.',
  halbzeitpause:'Pause zwischen 1. und 2. Halbzeit. Danach startet automatisch die 2. Halbzeit.',
  zweite_halbzeit:'Startet die 2. Halbzeit.',
  spielpause:'Kurze Pause zwischen einzelnen Spielen. Musik läuft bis 30 Sekunden vor Ende.' ,
  verlaengerung_1:'Startet die 1. Halbzeit der Verlängerung.',
  verlaengerung_pause:'Pause zwischen den Verlängerungshalbzeiten.',
  verlaengerung_2:'Startet die 2. Halbzeit der Verlängerung.'
};
function applyTooltips(){
  const tips={
    startPause:'Startet oder pausiert die aktuelle Uhr.',
    reset:'Setzt den aktuellen Abschnitt auf die gespeicherte Zeit zurück.',
    fullscreen:'Öffnet den Beamer-/Vollbildmodus.',
    musicToggle:'Startet oder stoppt die Pausenmusik manuell.',
    saveTimes:'Speichert die aktuell eingetragenen Zeiten.',
    resetDefaults:'Setzt alle Einstellungen auf den Standard zurück.',
    loadTimeTemplate:'Lädt ein gespeichertes Zeit-Template.',
    saveTimeTemplate:'Speichert die aktuellen Zeiten als neues Template.',
    loadSoundTemplate:'Lädt ein gespeichertes Sound-Template.',
    saveSoundTemplate:'Speichert die aktuelle Sound-Auswahl als Template.',
    previewMusic:'Spielt die ausgewählte Musik zum Testen ab.',
    saveMusic:'Speichert die Musikeinstellungen.',
    saveBranding:'Speichert Logo, Hintergrund und Vollbild-Einstellungen.',
    exportConfig:'Exportiert alle Einstellungen als Datei.',
    importConfig:'Importiert eine zuvor exportierte Konfiguration.'
  };
  Object.entries(tips).forEach(([id,tip])=>{const el=$(id); if(el) el.setAttribute('data-tip',tip);});
  document.querySelectorAll('.tab').forEach(t=>t.setAttribute('data-tip','Zum Bereich '+t.textContent.trim()+' wechseln.'));
}

function renderPhaseButtons(){
  const grid=$('phaseButtons');
  grid.innerHTML='';

  const wrap=document.createElement('div');
  wrap.className='phase-group phase-group-hauptspiel';

  const h=document.createElement('h3');
  h.textContent='Hauptspiel';
  wrap.appendChild(h);

  const mainInner=document.createElement('div');
  mainInner.className='phase-grid-inner';
  ['rundenpause','erste_halbzeit','halbzeitpause','zweite_halbzeit','spielpause'].forEach(k=>{
    const [ico,label]=labels[k];
    const b=document.createElement('button');
    b.className='phase-btn';
    b.dataset.phase=k;
    b.setAttribute('data-tip',phaseTips[k]||label);
    b.innerHTML=`<span class="ico">${icon(ico)}</span><span class="phase-label" data-mobile-label="${label}">${label}</span>`;
    b.onclick=()=>setPhase(k);
    mainInner.appendChild(b);
  });
  wrap.appendChild(mainInner);

  const extensionKeys=['verlaengerung_1','verlaengerung_pause','verlaengerung_2'];
  const extensionIsActive=extensionKeys.includes(current);
  const ext=document.createElement('div');
  ext.className='extension-block';

  const extInner=document.createElement('div');
  extInner.className='phase-grid-inner extension-grid-inner';
  extensionKeys.forEach(k=>{
    const [ico,label]=labels[k];
    const b=document.createElement('button');
    b.className='phase-btn';
    b.dataset.phase=k;
    b.setAttribute('data-tip',phaseTips[k]||label);
    const mobileLabels={verlaengerung_1:'1. HZ',verlaengerung_pause:'Pause',verlaengerung_2:'2. HZ'};
    b.innerHTML=`<span class="ico">${icon(ico)}</span><span class="phase-label" data-mobile-label="${mobileLabels[k]||label}">${label}</span>`;
    b.onclick=()=>setPhase(k);
    extInner.appendChild(b);
  });
  ext.appendChild(extInner);

  const toggle=document.createElement('button');
  toggle.type='button';
  toggle.className='extension-toggle';
  toggle.setAttribute('data-tip','Zeigt die Buttons für die Verlängerung an.');
  ext.appendChild(toggle);

  const setExtensionOpen=(open)=>{
    extInner.hidden=!open;
    ext.classList.toggle('is-open', open);
    toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
    toggle.setAttribute('aria-label', open ? 'Verlängerung ausblenden' : 'Verlängerung anzeigen');
    toggle.innerHTML=open ? '<span class="extension-plus">×</span>' : '<span class="extension-plus">+</span><span>Verlängerung</span>';
  };
  setExtensionOpen(extensionIsActive);

  toggle.onclick=()=>setExtensionOpen(extInner.hidden);

  grid.appendChild(wrap);
  grid.appendChild(ext);
}
function renderTimes(){const box=$('timeInputs');box.innerHTML=''; Object.keys(labels).forEach(k=>{const l=document.createElement('label');l.textContent=labels[k][1]; const inp=document.createElement('input');inp.value=config.times[k]||'00:00';inp.dataset.time=k;l.appendChild(inp);box.appendChild(l)})}
function shortPath(v){return String(v||'').startsWith('data:')?'eigene hochgeladene Datei':v}
function loadAudioFile(file,cb){if(!file)return; const r=new FileReader(); r.onload=()=>cb(r.result); r.readAsDataURL(file)}

function clamp(v,min,max){return Math.max(min,Math.min(max,v));}
function normalizeHexColor(value, fallback='#3d7b42'){
  const v=String(value||'').trim();
  if(/^#[0-9a-f]{6}$/i.test(v))return v.toLowerCase();
  if(/^#[0-9a-f]{3}$/i.test(v))return '#'+v.slice(1).split('').map(x=>x+x).join('').toLowerCase();
  return fallback;
}
function hexToRgb(hex){
  hex=normalizeHexColor(hex).slice(1);
  return {r:parseInt(hex.slice(0,2),16),g:parseInt(hex.slice(2,4),16),b:parseInt(hex.slice(4,6),16)};
}
function rgbToHex(r,g,b){
  return '#'+[r,g,b].map(v=>clamp(Math.round(v),0,255).toString(16).padStart(2,'0')).join('');
}
function mixHex(a,b,t){
  const x=hexToRgb(a), y=hexToRgb(b);
  return rgbToHex(x.r+(y.r-x.r)*t,x.g+(y.g-x.g)*t,x.b+(y.b-x.b)*t);
}
function applyThemeColor(color){
  const main=normalizeHexColor(color);
  const dark=mixHex(main,'#000000',0.18);
  const soft=mixHex(main,'#ffffff',0.90);
  const wash=mixHex(main,'#ffffff',0.96);
  const rgb=hexToRgb(main);
  document.documentElement.style.setProperty('--green',main);
  document.documentElement.style.setProperty('--green-dark',dark);
  document.documentElement.style.setProperty('--green-soft',soft);
  document.documentElement.style.setProperty('--theme-wash',wash);
  document.documentElement.style.setProperty('--theme-hover','rgba('+rgb.r+','+rgb.g+','+rgb.b+',0.08)');
  const meta=document.querySelector('meta[name="theme-color"]');
  if(meta)meta.setAttribute('content',main);
}
function getReadableLogoColor(r,g,b){
  const max=Math.max(r,g,b), min=Math.min(r,g,b);
  const lum=(0.2126*r+0.7152*g+0.0722*b)/255;
  if(max-min<28 || lum>0.88 || lum<0.16)return null;
  const boost=lum>0.72?0.30:0;
  return rgbToHex(r*(1-boost),g*(1-boost),b*(1-boost));
}
function extractThemeColorFromImage(src, cb){
  if(!src || typeof Image==='undefined' || !document.createElement('canvas').getContext){cb(null);return;}
  const img=new Image();
  img.onload=function(){
    try{
      const canvas=document.createElement('canvas');
      const size=64;
      canvas.width=size; canvas.height=size;
      const ctx=canvas.getContext('2d',{willReadFrequently:true}) || canvas.getContext('2d');
      ctx.clearRect(0,0,size,size);
      ctx.drawImage(img,0,0,size,size);
      const data=ctx.getImageData(0,0,size,size).data;
      const buckets={};
      for(let i=0;i<data.length;i+=16){
        const a=data[i+3]; if(a<90)continue;
        const r=data[i],g=data[i+1],b=data[i+2];
        const readable=getReadableLogoColor(r,g,b); if(!readable)continue;
        const rr=Math.round(r/32)*32, gg=Math.round(g/32)*32, bb=Math.round(b/32)*32;
        const key=rr+','+gg+','+bb;
        buckets[key]=(buckets[key]||0)+1;
      }
      let best=null,bestCount=0;
      Object.keys(buckets).forEach(k=>{if(buckets[k]>bestCount){best=k;bestCount=buckets[k];}});
      if(!best){cb(null);return;}
      const [r,g,b]=best.split(',').map(Number);
      cb(getReadableLogoColor(clamp(r,0,255),clamp(g,0,255),clamp(b,0,255)));
    }catch(e){cb(null);}
  };
  img.onerror=function(){cb(null);};
  img.src=src;
}
function setThemeColor(color, shouldSave){
  config.branding=config.branding||{};
  config.branding.theme_color=normalizeHexColor(color);
  if($('themeColor'))$('themeColor').value=config.branding.theme_color;
  applyThemeColor(config.branding.theme_color);
  if(shouldSave)save();
}

function updateLogoSelector(){const sel=$('brandLogoSelect'); if(!sel)return; const v=(config.branding&&config.branding.logo)||''; sel.value=(v==='logos/DTKV.png'||v==='logos/STKV.svg')?v:'custom'; updateBrandUploadVisibility();}
function updateBrandUploadVisibility(){const sel=$('brandLogoSelect'); const upload=document.querySelector('.brand-upload'); if(!sel||!upload)return; const isCustom=sel.value==='custom'; upload.classList.toggle('visible',isCustom); upload.style.display=isCustom?'block':'none';}
function setBrandLogo(path,text){$('brandLogo').value=path; if(text){$('brandText').value=text;} config.branding.logo=path; if(text){config.branding.text=text;} applyBranding();}
function renderSounds(){const g=$('soundGrid');g.innerHTML=''; Object.entries(soundLabels).forEach(([k,v])=>{const c=document.createElement('div');c.className='sound-card';c.innerHTML=`<h3>${v}</h3><p>${shortPath(config.sounds[k]||'')}</p><div class="sound-actions"><label class="upload">Datei wählen<input type="file" accept="audio/*"></label><button class="test-btn">${icon('speaker')} Test</button></div>`;c.querySelector('button').onclick=()=>playSound(k);c.querySelector('input').onchange=e=>loadAudioFile(e.target.files[0],url=>{config.sounds[k]=url;save();renderSounds();toast('Sound gespeichert','Eigener Sound wurde übernommen.')});g.appendChild(c)});}
function applyBranding(){
  const bgEnabled=!!config.branding.logo_bg_enabled;
  const bgColor=config.branding.logo_bg_color||'#ffffff';
  setThemeColor(config.branding.theme_color||'#3d7b42', false);
  $('logo').src=config.branding.logo;
  $('logo').style.display=config.branding.enabled?'inline-block':'none';
  $('logoPreview').src=config.branding.logo;
  $('brandLogo').value=config.branding.logo;
  $('brandText').value=config.branding.text||'';
  $('brandEnabled').checked=!!config.branding.enabled;
  $('brandBgEnabled').checked=bgEnabled;
  $('brandBgColor').value=bgColor;
  $('theme').value=config.branding.fullscreen_theme||'dark';
  if($('themeColor'))$('themeColor').value=normalizeHexColor(config.branding.theme_color||'#3d7b42');
  document.documentElement.style.setProperty('--logo-bg',bgColor);
  document.body.classList.toggle('logo-bg-enabled', bgEnabled);
  document.querySelector('.preview')?.classList.toggle('logo-bg-enabled',bgEnabled);

  // Wichtig: Fullscreen und normale Stoppuhr nutzen exakt dieselbe Logo-Hintergrund-Einstellung.
  // Wenn der Haken deaktiviert ist, werden Hintergrund, Padding, Rahmen und Schatten wirklich entfernt.
  $('logo').classList.toggle('logo-bg-pad', bgEnabled);
  if(bgEnabled){
    $('logo').style.background=bgColor;
    $('logo').style.padding='';
    $('logo').style.border='';
    $('logo').style.borderRadius='';
    $('logo').style.boxShadow='';
  }else{
    $('logo').style.background='transparent';
    $('logo').style.padding='0';
    $('logo').style.border='0';
    $('logo').style.borderRadius='0';
    $('logo').style.boxShadow='none';
    $('logo').style.marginBottom = '10px';
  }
}
function initMusic(){const sel=$('musicFile'); sel.innerHTML=''; ['sounds/music/minimal-loop.mp3','sounds/music/showdown.mp3','sounds/music/drum-loop.mp3','sounds/music/amen-break-loop.mp3','sounds/music/tense-drum-loop.mp3','sounds/music/60s-garage-rock-loop.mp3'].forEach(f=>{let o=new Option(f.replace('sounds/music/',''),f);sel.add(o)}); sel.value=config.music.file||sel.options[0].value; $('musicEnabled').checked=!!config.music.enabled; $('musicNormal').value=config.music.normal_volume||35; $('musicDucked').value=config.music.ducked_volume||12;}
function saveMusic(){config.music.enabled=$('musicEnabled').checked;config.music.file=$('musicFile').value;config.music.normal_volume=+$('musicNormal').value;config.music.ducked_volume=+$('musicDucked').value;music.src=config.music.file;music.volume=config.music.normal_volume/100;save();updatePauseMusic();toast()}
function previewMusic(){unlockAudio(); const src=$('musicFile').value||config.music.file; if(music.src.indexOf(src)===-1) music.src=src; music.loop=false; music.volume=(+$('musicNormal').value||config.music.normal_volume||35)/100; if(music.paused){ music.currentTime=0; music.play().catch(()=>{}); $('previewMusic').textContent='⏸ Musik stoppen'; } else { music.pause(); music.currentTime=0; $('previewMusic').textContent='▶ Musik testen'; }}
function updatePauseMusic(){ if(!shouldPauseMusicPlay()){ music.pause(); music.currentTime=0; return; } const src=config.music.file||$('musicFile')?.value; if(src && music.src.indexOf(src)===-1) music.src=src; music.loop=true; music.volume=(config.music.normal_volume||35)/100; music.play().catch(()=>console.warn('Pausenmusik konnte noch nicht abgespielt werden. Bitte einmal in die App klicken.')); }
function toggleMusic(){unlockAudio(); if(config.music.enabled){config.music.enabled=false; $('musicEnabled').checked=false; music.pause(); music.currentTime=0; save(); toast('Pausenmusik aus','Musik wurde gestoppt.'); return;} config.music.enabled=true; $('musicEnabled').checked=true; save(); updatePauseMusic(); toast('Pausenmusik aktiv','Musik läuft nur in Rundenpause und Spielpause.');}
function activateStopwatchTab(){
  const timeDisplay = $('timeDisplay');
  const timerPanel = timeDisplay ? timeDisplay.closest('.panel') : document.querySelector('.timer-panel');

  if(!timerPanel) return;

  document.querySelectorAll('.tab,.panel').forEach(x => x.classList.remove('active'));

  timerPanel.classList.add('active');

  if(timerPanel.id){
    const timerTab = document.querySelector(`.tab[data-tab="${timerPanel.id}"]`);
    if(timerTab) timerTab.classList.add('active');
  }
}

function fullscreen(){
  const active = isFullscreenActive();

  if(!active){
    activateStopwatchTab();
    unlockAudio();
    updateViewportHeight();

    const mode=(config.branding?.fullscreen_theme||'dark')==='light'?'light':'dark';

    document.body.classList.remove('fullscreen-dark','fullscreen-light');
    document.body.classList.add(mode==='light'?'fullscreen-light':'fullscreen-dark');
    document.body.classList.add('fullscreen-active');
    document.body.dataset.pseudoFullscreen = '1';
    applyFullscreenLock();

    const p = requestAppFullscreen();

    Promise.resolve(p).then(()=>{
      delete document.body.dataset.pseudoFullscreen;
      applyFullscreenLock();
      
    }).catch(()=>{
      document.body.dataset.pseudoFullscreen = '1';
      applyFullscreenLock();
      
    });

  } else {
    delete document.body.dataset.pseudoFullscreen;
    exitAppFullscreen();
  }
}

function init(){config.ui=config.ui||{tooltips:false}; document.body.classList.toggle('tooltips-enabled',!!config.ui.tooltips); populateTimeTemplates();populateSoundTemplates();renderPhaseButtons();renderTimes();renderSounds();initMusic();applyBranding();setPhase(current); document.querySelectorAll('.tab').forEach(t=>t.onclick=()=>{document.querySelectorAll('.tab,.panel').forEach(x=>x.classList.remove('active'));t.classList.add('active');$(t.dataset.tab).classList.add('active')}); $('startPause').addEventListener('click', (e)=>{e.preventDefault(); e.stopPropagation(); toggleRun();}); $('reset').onclick=()=>setPhase(current); $('fullscreen').onclick=fullscreen; if($('startCountdown'))$('startCountdown').onclick=startCountdown; $('musicToggle').onclick=toggleMusic; $('saveTimes').onclick=()=>{document.querySelectorAll('[data-time]').forEach(i=>config.times[i.dataset.time]=i.value);save();setPhase(current);toast()}; $('loadTimeTemplate').onclick=()=>{const key=$('timeTemplate').value; if(builtinTimeTemplates[key]){config.times=structuredClone(builtinTimeTemplates[key].times);config.flow={...(builtinTimeTemplates[key].flow||defaultFlow)};config.last_template=key;} else if(key.startsWith('custom:')){const name=key.slice(7); const t=getCustomTemplates('tk_time_templates')[name]; if(t){if(t.times){config.times={...t.times};config.flow={...(t.flow||defaultFlow)}}else{config.times={...t};config.flow={...defaultFlow}}}} save(); renderTimes(); setPhase(current); toast('Zeit-Template geladen','Die Zeiten wurden übernommen.')}; $('saveTimeTemplate').onclick=()=>{document.querySelectorAll('[data-time]').forEach(i=>config.times[i.dataset.time]=i.value); const name=prompt('Name für Zeit-Template:'); if(!name)return; const all=getCustomTemplates('tk_time_templates'); all[name]={times:{...config.times},flow:{...(config.flow||defaultFlow)}}; setCustomTemplates('tk_time_templates',all); populateTimeTemplates(); $('timeTemplate').value='custom:'+name; save(); toast('Zeit-Template gespeichert','Die aktuellen Zeiten wurden gespeichert.')}; $('resetDefaults').onclick=()=>{config=structuredClone(defaultConfig);save();location.reload()}; $('volume').oninput=e=>{const v=Number(e.target.value)/100; $('volumeLabel').textContent=e.target.value+'%'; activeSounds.forEach(a=>a.volume=v);}; $('musicNormal').oninput=e=>{config.music.normal_volume=+e.target.value; if(!music.paused)music.volume=(+e.target.value)/100; save();}; $('musicDucked').oninput=e=>{config.music.ducked_volume=+e.target.value; save();}; $('saveMusic').onclick=saveMusic; $('previewMusic').onclick=previewMusic; $('musicUpload').onchange=e=>loadAudioFile(e.target.files[0],url=>{const name=e.target.files[0]?.name||'Eigene Musik'; let opt=new Option(name,url); $('musicFile').add(opt); $('musicFile').value=url; config.music.file=url; music.src=url; save(); toast('Musik gespeichert','Eigene Musik wurde übernommen.')}); $('loadSoundTemplate').onclick=()=>{const key=$('soundTemplate').value;if(soundTemplates[key]){config.sounds={...soundTemplates[key]};save();renderSounds();toast('Sound-Template geladen','Die Sounds wurden übernommen.')}else if(key.startsWith('custom:')){const name=key.slice(7);const t=getCustomTemplates('tk_sound_templates')[name];if(t){config.sounds={...t};save();renderSounds();toast('Sound-Template geladen','Die Sounds wurden übernommen.')}}}; $('saveSoundTemplate').onclick=()=>{const name=prompt('Name für Sound-Template:'); if(!name)return; const all=getCustomTemplates('tk_sound_templates'); all[name]={...config.sounds}; setCustomTemplates('tk_sound_templates',all); populateSoundTemplates(); $('soundTemplate').value='custom:'+name; toast('Sound-Template gespeichert','Die aktuelle Sound-Auswahl wurde gespeichert.')}; if($('tooltipsEnabled')){ $('tooltipsEnabled').checked=!!config.ui.tooltips; $('tooltipsEnabled').onchange=e=>{config.ui.tooltips=e.target.checked; document.body.classList.toggle('tooltips-enabled',!!config.ui.tooltips); save(); toast('Hover-Hilfen','Die Beschreibungen wurden '+(config.ui.tooltips?'aktiviert.':'deaktiviert.'));}; } applyTooltips(); document.addEventListener('fullscreenchange',handleFullscreenChange);document.addEventListener('webkitfullscreenchange',handleFullscreenChange); if($('brandLogoSelect')){$('brandLogoSelect').onchange=e=>{const v=e.target.value;if(v==='logos/DTKV.png')setBrandLogo(v,'DTKV');else if(v==='logos/STKV.svg')setBrandLogo(v,'STKV');updateBrandUploadVisibility();};updateBrandUploadVisibility();} if($('brandUpload')){$('brandUpload').onchange=e=>{const f=e.target.files&&e.target.files[0];if(!f)return;loadAudioFile(f,url=>{if($('brandLogoSelect'))$('brandLogoSelect').value='custom';updateBrandUploadVisibility();setBrandLogo(url,f.name.replace(/\.[^.]+$/,''));toast('Logo geladen','Eigenes Logo wurde übernommen. Zum dauerhaften Speichern bitte speichern.'); extractThemeColorFromImage(url,color=>{if(color){setThemeColor(color,false);toast('Theme-Farbe erkannt','Die Farbe wurde aus dem Logo übernommen. Zum dauerhaften Speichern bitte speichern.');}});});};} $('saveBranding').onclick=()=>{config.branding.enabled=$('brandEnabled').checked;config.branding.logo=$('brandLogo').value;config.branding.text=$('brandText').value;config.branding.logo_bg_enabled=$('brandBgEnabled').checked;config.branding.logo_bg_color=$('brandBgColor').value;config.branding.theme_color=normalizeHexColor($('themeColor')?.value||config.branding.theme_color||'#3d7b42');config.branding.fullscreen_theme=$('theme').value;save();applyBranding();toast()}; if($('themeColor')){$('themeColor').oninput=e=>setThemeColor(e.target.value,false);$('themeColor').onchange=e=>setThemeColor(e.target.value,true);} if($('resetThemeColor')){$('resetThemeColor').onclick=()=>{setThemeColor('#3d7b42',false);toast('Theme-Farbe','Standard-Grün wurde gewählt. Zum dauerhaften Speichern bitte speichern.');};} if($('pickThemeFromLogo')){$('pickThemeFromLogo').onclick=()=>{extractThemeColorFromImage($('brandLogo').value||config.branding.logo,color=>{if(color){setThemeColor(color,false);toast('Theme-Farbe erkannt','Die Farbe wurde aus dem Logo übernommen. Zum dauerhaften Speichern bitte speichern.');}else{toast('Keine Farbe erkannt','Bitte wähle die Theme-Farbe manuell. Das ist bei SVG/PDF oder sehr hellen Logos normal.');}});};} $('exportConfig').onclick=()=>{const blob=new Blob([JSON.stringify(config,null,2)],{type:'application/json'});const a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='tippkick-stoppuhr-config.json';a.click()}; $('importConfig').onchange=e=>{const f=e.target.files[0];if(!f)return;f.text().then(txt=>{config=JSON.parse(txt);save();location.reload()})}; $('resetAllSettings').onclick=()=>{ if(confirm('Alle Einstellungen wirklich auf Standard zurücksetzen?')){ localStorage.removeItem('tk_config'); localStorage.removeItem('tk_time_templates'); localStorage.removeItem('tk_sound_templates'); config=structuredClone(defaultConfig); save(); location.reload(); } }; window.addEventListener('keydown',e=>{if(['INPUT','SELECT'].includes(document.activeElement.tagName))return; if(e.code==='Space'){e.preventDefault();toggleRun()} if(e.key.toLowerCase()==='r')setPhase(current); if(e.key.toLowerCase()==='f')fullscreen(); if(e.key.toLowerCase()==='m')toggleMusic();}); document.body.addEventListener('pointerdown',unlockAudio,{once:true}); initMobileFullscreenControls();}


// v31: Mobile fullscreen controls. Tap in fullscreen to reveal a small auto-hide bar.
let __mobileControlsTimer = null;
function ensureMobileFullscreenControls(){
  let bar = document.getElementById('mobileFullscreenControls');
  if(bar) return bar;
  bar = document.createElement('div');
  bar.id = 'mobileFullscreenControls';
  bar.className = 'mobile-fullscreen-controls';
  bar.setAttribute('aria-hidden','true');
  bar.innerHTML = `
  <button type="button" id="mobilePauseBtn" class="mobile-pause-btn">
    <svg viewBox="0 0 24 24" fill="none">
      <rect x="7" y="5" width="3" height="14" rx="1.5" fill="currentColor"/>
      <rect x="14" y="5" width="3" height="14" rx="1.5" fill="currentColor"/>
    </svg>
  </button>

  <button type="button" id="mobileExitFullscreenBtn" class="mobile-exit-btn">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.8" stroke-linecap="round">
      <path d="M7 7l10 10"/>
      <path d="M17 7L7 17"/>
    </svg>
  </button>
`;
  document.body.appendChild(bar);
  bar.addEventListener('pointerdown', e => e.stopPropagation());
  bar.addEventListener('click', e => e.stopPropagation());
  bar.querySelector('#mobilePauseBtn').addEventListener('click', e => {
    e.preventDefault();
    toggleRun();
    showMobileFullscreenControls();
  });
  bar.querySelector('#mobileExitFullscreenBtn').addEventListener('click', e => {
    e.preventDefault();
    exitAppFullscreen();
  });
  return bar;
}
function updateMobileFullscreenControls(){
  const bar = ensureMobileFullscreenControls();
  const pauseBtn = bar.querySelector('#mobilePauseBtn');
  if(pauseBtn) pauseBtn.innerHTML = running
  ? `
    <svg viewBox="0 0 24 24" fill="none">
      <rect x="7" y="5" width="3" height="14" rx="1.5" fill="currentColor"/>
      <rect x="14" y="5" width="3" height="14" rx="1.5" fill="currentColor"/>
    </svg>
  `
  : `
    <svg viewBox="0 0 24 24" fill="currentColor">
      <path d="M8 5v14l11-7z"/>
    </svg>
  `;
}
function showMobileFullscreenControls(){
  if(!isFullscreenActive()) return;
  const bar = ensureMobileFullscreenControls();
  updateMobileFullscreenControls();
  bar.classList.add('visible');
  bar.setAttribute('aria-hidden','false');
  clearTimeout(__mobileControlsTimer);
  __mobileControlsTimer = setTimeout(() => hideMobileFullscreenControls(), 3200);
}
function hideMobileFullscreenControls(){
  const bar = document.getElementById('mobileFullscreenControls');
  if(!bar) return;
  bar.classList.remove('visible');
  bar.setAttribute('aria-hidden','true');
}
function initMobileFullscreenControls(){
  ensureMobileFullscreenControls();
  document.addEventListener('pointerdown', () => {
    if(isFullscreenActive()) showMobileFullscreenControls();
  });
  ['fullscreenchange','webkitfullscreenchange','MSFullscreenChange'].forEach(evt => {
    document.addEventListener(evt, () => {
      updateMobileFullscreenControls();
      if(!isFullscreenActive()) hideMobileFullscreenControls();
    });
  });
}

// v47: Scroll-Lock-Sicherung. Manche mobilen Browser melden Fullscreen-Wechsel nicht zuverlässig.
// Wenn kein echter Fullscreen aktiv ist, werden alte Fullscreen-Klassen entfernt, damit die Seite wieder scrollt.
function ensureNormalScrollState(){
  updateViewportHeight();
  if(isFullscreenActive()){
    applyFullscreenLock();
    return;
  }
  if(!fsElement() && !document.body.dataset.pseudoFullscreen){
    document.body.classList.remove('fullscreen-active','fullscreen-dark','fullscreen-light');
    clearFullscreenLock();
  }
}
window.addEventListener('pageshow', ensureNormalScrollState);
window.addEventListener('resize', ensureNormalScrollState);
window.addEventListener('orientationchange', () => setTimeout(ensureNormalScrollState, 250));
if(window.visualViewport) window.visualViewport.addEventListener('resize', ensureNormalScrollState);
document.addEventListener('visibilitychange', () => {
  if(document.visibilityState === 'visible'){
    updateViewportHeight();
    if(running && !wakelock) requestWakeLock();
    if(shouldPauseMusicPlay()) updatePauseMusic();
  }
});

updateViewportHeight();
init();
