import { useState, useEffect, useRef, useCallback } from 'react';
import { api, S } from './lib.js';
import { TOOLS, getGroupedTools } from './toolRegistry.js';
import CalendarWidget, { CalendarSettings } from './calendar.jsx';
import { DateiSettings } from './tools/dateien.jsx';
import { useConfirm } from './confirm.jsx';
import { HomeIcon, AppsIcon, AdminIcon, MoreIcon, UserIcon, LogOutIcon, ChevronIcon, TrashIcon, MessageIcon } from './icons.jsx';
import { getOrCreateKeyPair, getPublicKeyJwk, getFingerprint, exportEncrypted, importEncrypted, generateNewKeyPair } from './crypto.js';

const CONST_ICONS = ['🔗','🌐','📊','📁','🛠','📧','🗓','💾','🖥','📡','🔒','📖','🎯','⚡','🏠','🔧','📱','🎮','🚀','💡'];

// Schlüsselpaar beim Login generieren und public key registrieren (fire & forget)
async function initNachrichtenKeys() {
  const LS_KEY = 'dd_msg_keypair';
  let pubJwk;
  const stored = localStorage.getItem(LS_KEY);
  if (stored) {
    try { pubJwk = JSON.parse(stored).pub; } catch { localStorage.removeItem(LS_KEY); }
  }
  if (!pubJwk) {
    const kp   = await crypto.subtle.generateKey({ name:'ECDH', namedCurve:'P-256' }, true, ['deriveKey']);
    pubJwk     = await crypto.subtle.exportKey('jwk', kp.publicKey);
    const priv = await crypto.subtle.exportKey('jwk', kp.privateKey);
    localStorage.setItem(LS_KEY, JSON.stringify({ pub: pubJwk, priv }));
  }
  try { await api('/tools/nachrichten/keys', { body: { public_key: JSON.stringify(pubJwk) } }); } catch {}
}

// ── Responsive Hook ───────────────────────────────────────────────────────────
function useIsMobile() {
  const [mobile, setMobile] = useState(window.innerWidth < 768);
  useEffect(() => {
    // Nur auf Breite reagieren, nicht auf Keyboard-Open (Höhenänderung)
    const fn = () => {
      const isMob = window.innerWidth < 768;
      setMobile(prev => prev === isMob ? prev : isMob);
    };
    window.addEventListener('resize', fn);
    return () => window.removeEventListener('resize', fn);
  }, []);
  return mobile;
}

// ── Toast ─────────────────────────────────────────────────────────────────────
function Toast({ msg, type }) {
  if (!msg) return null;
  const c = type === 'error' ? '#ff6b9d' : '#4ecdc4';
  return (
    <div style={{ position:'fixed', bottom:80, right:16, zIndex:9999, background:'#1a1a1e',
      border:`1px solid ${c}55`, borderRadius:10, padding:'10px 16px',
      color:c, fontFamily:'monospace', fontSize:12, animation:'fadeIn 0.2s ease',
      boxShadow:`0 4px 24px ${c}22`, maxWidth:'calc(100vw - 32px)' }}>
      {type==='error'?'✕ ':'✓ '}{msg}
    </div>
  );
}

// ── Modal ─────────────────────────────────────────────────────────────────────
function Modal({ title, onClose, children }) {
  return (
    <div style={{ position:'fixed', inset:0, background:'rgba(0,0,0,0.8)', zIndex:5000,
      display:'flex',
      alignItems: window.innerWidth>=768 ? 'center' : 'flex-end',
      justifyContent:'center', padding: window.innerWidth>=768 ? 24 : 0 }}
      onClick={e=>e.target===e.currentTarget&&onClose()}>
      <div style={{ background:'#1a1a1e', border:'1px solid rgba(255,255,255,0.15)',
        borderRadius: window.innerWidth>=768 ? 16 : '16px 16px 0 0',
        padding:'20px 20px 40px', width:'100%',
        maxWidth:600, maxHeight:'85vh', overflowY:'auto',
        boxShadow: window.innerWidth>=768 ? '0 24px 80px rgba(0,0,0,0.6)' : 'none' }}>
      {window.innerWidth < 768 && <div style={{ width:36, height:4, background:'rgba(255,255,255,0.2)', borderRadius:2, margin:'0 auto 20px' }}/>}
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:18 }}>
          <span style={{ color:'#fff', fontFamily:"'Space Mono',monospace", fontSize:14, fontWeight:700 }}>{title}</span>
          <button onClick={onClose} style={{ background:'transparent', border:'none', color:'rgba(255,255,255,0.4)', fontSize:20, cursor:'pointer', padding:'0 4px' }}>✕</button>
        </div>
        {children}
      </div>
    </div>
  );
}

// ── PWA Splash Screen ─────────────────────────────────────────────────────────
function SplashScreen({ onDone }) {
  const [fade, setFade] = useState(false);
  useEffect(() => {
    const t1 = setTimeout(() => setFade(true), 1200);
    const t2 = setTimeout(() => onDone(), 1700);
    return () => { clearTimeout(t1); clearTimeout(t2); };
  }, []);
  return (
    <div style={{
      position:'fixed', inset:0, background:'#0d0d0f',
      display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center',
      zIndex:99999, transition:'opacity 0.5s ease',
      opacity: fade ? 0 : 1, pointerEvents: fade ? 'none' : 'auto',
    }}>
      <div style={{ color:'#fff', fontFamily:"'Space Mono',monospace", fontSize:22, fontWeight:700, marginBottom:24, letterSpacing:2 }}>
        DICKEN<span style={{color:'#4ecdc4'}}>DOCK</span>
      </div>
      <div style={{
        width:72, height:72, background:'linear-gradient(135deg,#4ecdc4,#ff6b9d)',
        borderRadius:18, display:'flex', alignItems:'center', justifyContent:'center', fontSize:36,
      }}>⚒</div>
    </div>
  );
}

// ── Login ─────────────────────────────────────────────────────────────────────
function Login({ onLogin }) {
  const [u,setU]=useState(''); const [p,setP]=useState('');
  const [err,setErr]=useState(''); const [busy,setBusy]=useState(false);
  const go = async () => {
    setErr(''); setBusy(true);
    try { const d=await api('/auth/login',{body:{username:u,password:p}}); localStorage.setItem('sk_token',d.token); initNachrichtenKeys(); onLogin(d.user); }
    catch(e) { setErr(e.message); } finally { setBusy(false); }
  };
  return (
    <div style={{
      position:'fixed', inset:0,
      background:'#0d0d0f',
      display:'flex', flexDirection:'column',
      alignItems:'center', justifyContent:'center',
      padding:'24px 20px', overflow:'hidden',
    }}>
      <div style={{ width:'100%', maxWidth:340 }}>
        <div style={{ textAlign:'center', marginBottom:36 }}>
          <div style={{ width:56, height:56, background:'linear-gradient(135deg,#4ecdc4,#ff6b9d)',
            borderRadius:14, display:'flex', alignItems:'center', justifyContent:'center',
            fontSize:26, margin:'0 auto 14px' }}>⚒</div>
          <div style={{ color:'#fff', fontFamily:"'Space Mono',monospace", fontSize:20, fontWeight:700 }}>
            DICKEN<span style={{ color:'#4ecdc4' }}>DOCK</span>
          </div>
        </div>
        {[['BENUTZERNAME',u,setU,'text'],['PASSWORT',p,setP,'password']].map(([l,v,s,t])=>(
          <div key={l} style={{ marginBottom:14 }}>
            <label style={{ ...S.head, display:'block', marginBottom:5 }}>{l}</label>
            <input type={t} value={v} onChange={e=>s(e.target.value)}
              onKeyDown={e=>e.key==='Enter'&&go()}
              style={{ ...S.inp, fontSize:16, padding:'13px 14px', borderRadius:10,
                border:'1px solid rgba(255,255,255,0.12)' }}
              autoCapitalize="none" autoCorrect="off" />
          </div>
        ))}
        {err && <div style={{ color:'#ff6b9d', fontSize:12, fontFamily:'monospace', marginBottom:12, textAlign:'center' }}>✕ {err}</div>}
        <button onClick={go} disabled={busy} style={{
          width:'100%', background:'linear-gradient(135deg,#4ecdc4,#45b7d1)', border:'none',
          borderRadius:12, padding:'15px 0', color:'#0d0d0f',
          fontFamily:"'Space Mono',monospace", fontWeight:700, fontSize:15,
          cursor:busy?'default':'pointer', opacity:busy?0.7:1, marginTop:6,
        }}>{busy?'…':'Einloggen →'}</button>
      </div>
    </div>
  );
}

// ── Update Modal ──────────────────────────────────────────────────────────────
// ── Desktop Sidebar ───────────────────────────────────────────────────────────
function Sidebar({ active, setActive, user, onLogout, unreadMsgs=0 }) {
  const [appsOpen, setAppsOpen] = useState(true);
  const [collapsed, setCollapsed] = useState(false);

  const NavBtn = ({ item, sub=false }) => {
    const ic = active===item.id ? '#4ecdc4' : 'rgba(255,255,255,0.55)';
    return (
      <button onClick={()=>setActive(item.id)} title={collapsed ? item.label : undefined} style={{
        width:'100%', display:'flex', alignItems:'center', gap:9,
        padding: collapsed ? '8px 0' : '8px 12px',
        justifyContent: collapsed ? 'center' : 'flex-start',
        borderRadius:7, border:'none',
        background: active===item.id ? 'linear-gradient(90deg,rgba(78,205,196,0.14),rgba(78,205,196,0.02))' : 'transparent',
        cursor:'pointer', fontSize:12,
        fontFamily:"'Space Mono',monospace", textAlign:'left', marginBottom:1,
        borderLeft: active===item.id ? '2px solid #4ecdc4' : '2px solid transparent',
        position:'relative',
      }}>
        {!collapsed && sub && <span style={{ width:1, alignSelf:'stretch', background:'rgba(255,255,255,0.08)', marginRight:3, flexShrink:0 }}/>}
        <span style={{ display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0, position:'relative' }}>
          {item.Icon ? (() => { const I = item.Icon; return <I size={collapsed?18:15} color={ic}/>; })() : <span style={{color:ic,fontSize:collapsed?16:13}}>{item.icon}</span>}
          {collapsed && item.id==='nachrichten' && unreadMsgs>0 && (
            <span style={{ position:'absolute', top:-3, right:-3, width:7, height:7,
              borderRadius:'50%', background:'#4ecdc4', boxShadow:'0 0 5px #4ecdc4' }}/>
          )}
        </span>
        {!collapsed && <span style={{ flex:1, color:ic }}>{item.label}</span>}
        {!collapsed && item.id==='nachrichten' && unreadMsgs>0 && (
          <span style={{ background:'#4ecdc4', color:'#000', borderRadius:10, fontSize:9,
            fontFamily:'monospace', padding:'1px 6px', fontWeight:700, flexShrink:0 }}>{unreadMsgs}</span>
        )}
      </button>
    );
  };

  return (
    <aside style={{ width:collapsed?56:210, minHeight:'100vh', background:'#0d0d0f',
      borderRight:'1px solid rgba(255,255,255,0.06)', display:'flex', flexDirection:'column', flexShrink:0,
      transition:'width 0.2s ease', overflow:'hidden', position:'relative' }}>
      <div style={{ padding:'12px 8px', borderBottom:'1px solid rgba(255,255,255,0.06)', marginBottom:8 }}>
        {collapsed ? (
          <div style={{ display:'flex', flexDirection:'column', alignItems:'center', gap:8 }}>
            <div style={{ width:30, height:30, background:'linear-gradient(135deg,#4ecdc4,#ff6b9d)',
              borderRadius:7, display:'flex', alignItems:'center', justifyContent:'center', fontSize:14 }}>⚒</div>
            <button onClick={()=>setCollapsed(false)} style={{
              background:'rgba(78,205,196,0.2)', border:'1px solid rgba(78,205,196,0.5)',
              borderRadius:6, color:'#4ecdc4', cursor:'pointer', padding:'4px 6px',
              fontSize:14, lineHeight:1, fontWeight:700, width:'100%' }}>›</button>
          </div>
        ) : (
          <div style={{ display:'flex', alignItems:'center', gap:8 }}>
            <div style={{ width:30, height:30, background:'linear-gradient(135deg,#4ecdc4,#ff6b9d)',
              borderRadius:7, display:'flex', alignItems:'center', justifyContent:'center', fontSize:14, flexShrink:0 }}>⚒</div>
            <div style={{flex:1,minWidth:0}}>
              <div style={{ color:'#fff', fontFamily:"'Space Mono',monospace", fontSize:13, fontWeight:700, whiteSpace:'nowrap' }}>
                DICKEN<span style={{ color:'#4ecdc4' }}>DOCK</span>
              </div>
            </div>
            <button onClick={()=>setCollapsed(true)} style={{
              background:'rgba(78,205,196,0.15)', border:'1px solid rgba(78,205,196,0.4)',
              borderRadius:6, color:'#4ecdc4', cursor:'pointer', padding:'4px 8px', flexShrink:0,
              fontSize:14, lineHeight:1, fontWeight:700 }}>‹</button>
          </div>
        )}
      </div>
      <nav style={{ flex:1, padding:'0 8px' }}>
        <NavBtn item={{ id:'dashboard', Icon:HomeIcon, label:'Dashboard' }} />

        {/* Apps Sektion mit Gruppenüberschriften */}
        <button onClick={()=>setAppsOpen(v=>!v)} title={collapsed?'Apps':undefined} style={{
          width:'100%', display:'flex', alignItems:'center', gap:9,
          padding: collapsed ? '9px 0' : '9px 12px',
          justifyContent: collapsed ? 'center' : 'flex-start',
          borderRadius:7, border:'none', background:'transparent',
          cursor:'pointer', fontSize:12,
          fontFamily:"'Space Mono',monospace", textAlign:'left', marginBottom:1,
          borderLeft:'2px solid transparent',
        }}>
          <AppsIcon size={collapsed?18:15} color="rgba(255,255,255,0.6)"/>
          {!collapsed && <span style={{ flex:1, color:'rgba(255,255,255,0.65)' }}>Apps</span>}
          {!collapsed && <ChevronIcon size={13} color="rgba(255,255,255,0.35)" dir={appsOpen?'down':'right'}/>}
        </button>
        {appsOpen && getGroupedTools().map(([group, tools]) => (
          <div key={group}>
            {!collapsed && <div style={{ color:'rgba(78,205,196,0.85)', fontSize:9, fontFamily:'monospace',
              letterSpacing:2, padding:'8px 12px 3px 12px' }}>{group.toUpperCase()}</div>}
            {tools.map(t => <NavBtn key={t.id} item={t} sub />)}
          </div>
        ))}

        <NavBtn item={{ id:'admin', Icon:AdminIcon, label:'Einstellungen' }} />
      </nav>
      <div style={{ padding:'10px 12px 16px', borderTop:'1px solid rgba(255,255,255,0.06)' }}>
        {!collapsed && <><div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:8 }}>
          <div style={{ width:24, height:24, borderRadius:'50%', background:'linear-gradient(135deg,#ff6b9d,#4ecdc4)',
            display:'flex', alignItems:'center', justifyContent:'center', overflow:'hidden', flexShrink:0 }}>
            {localStorage.getItem('dd_avatar')
              ? <img src={localStorage.getItem('dd_avatar')} style={{width:'100%',height:'100%',objectFit:'cover'}} alt=""/>
              : <UserIcon size={14} color="#0d0d0f"/>}
          </div>
          <div style={{ color:'#fff', fontSize:11, fontFamily:'monospace' }}>{user?.username}</div>
        </div>
        <button onClick={onLogout} style={{ ...S.btn('#ff6b9d',true), width:'100%', textAlign:'center' }}>Ausloggen</button></>}
        {collapsed && <button onClick={onLogout} title="Ausloggen" style={{ background:'transparent', border:'none', cursor:'pointer', padding:'4px', width:'100%', display:'flex', justifyContent:'center' }}><LogOutIcon size={18} color="rgba(255,107,157,0.7)"/></button>}
      </div>
    </aside>
  );
}

// ── Mobile Bottom Navigation ──────────────────────────────────────────────────
function BottomNav({ active, setActive, user, onLogout, unreadMsgs=0 }) {
  const [menuOpen, setMenuOpen] = useState(false);
  const [appsOpen, setAppsOpen] = useState(false);
  const NAV_H = 56;

  const MI = ({Icon, icon, label, onClick, color='rgba(255,255,255,0.85)'}) => {
    const Ic = Icon;
    return (
      <button onClick={onClick} style={{ width:'100%', padding:'11px 20px', background:'transparent',
        border:'none', display:'flex', alignItems:'center', gap:12, cursor:'pointer' }}>
        <span style={{flexShrink:0, width:22, display:'flex', alignItems:'center', justifyContent:'center'}}>
          {Ic ? <Ic size={18} color={color}/> : <span style={{fontSize:17, color}}>{icon}</span>}
        </span>
        <span style={{color, fontFamily:'monospace', fontSize:13}}>{label}</span>
      </button>
    );
  };

  const Sheet = ({open, onClose, children}) => {
    if (!open) return null;
    const isDesktop = window.innerWidth >= 768;
    if (isDesktop) return (
      <div style={{ position:'fixed', inset:0, zIndex:4000, background:'rgba(0,0,0,0.6)',
        display:'flex', alignItems:'center', justifyContent:'center', padding:24 }}
        onClick={onClose}>
        <div style={{ background:'#1a1a1e', border:'1px solid rgba(255,255,255,0.15)',
          borderRadius:16, padding:'20px 0', width:'100%', maxWidth:380,
          boxShadow:'0 24px 80px rgba(0,0,0,0.6)' }}
          onClick={e=>e.stopPropagation()}>
          {children}
        </div>
      </div>
    );
    return (
      <div style={{ position:'fixed', inset:0, zIndex:4000, background:'rgba(0,0,0,0.6)' }}
        onClick={onClose}>
        <div style={{ position:'absolute', bottom:`calc(${NAV_H}px + env(safe-area-inset-bottom, 0px))`,
          left:0, right:0, background:'#1a1a1e',
          borderTop:'1px solid rgba(255,255,255,0.1)',
          borderRadius:'16px 16px 0 0', paddingTop:8 }}
          onClick={e=>e.stopPropagation()}>
          <div style={{ width:36, height:4, background:'rgba(255,255,255,0.15)', borderRadius:2, margin:'0 auto 10px' }}/>
          {children}
          <div style={{height:'env(safe-area-inset-bottom, 0px)'}}/>
        </div>
      </div>
    );
  };

  const mainNav = [
    { id:'dashboard', Icon:HomeIcon,  label:'Home' },
    { id:'_apps',     Icon:AppsIcon,  label:'Apps'  },
    { id:'admin', Icon:AdminIcon, label:'Einst.' },
    { id:'_mehr',     Icon:MoreIcon,  label:'Mehr'  },
  ];

  return (
    <>
      {/* Apps Sheet mit Gruppenüberschriften */}
      <Sheet open={appsOpen} onClose={()=>setAppsOpen(false)}>
        <div style={{ paddingBottom:4 }}>
          {getGroupedTools().map(([group, tools]) => (
            <div key={group}>
              <div style={{ color:'rgba(78,205,196,0.85)', fontSize:9, fontFamily:'monospace',
                letterSpacing:2, padding:'12px 20px 5px' }}>{group.toUpperCase()}</div>
              {tools.map(t => (
                <MI key={t.id} Icon={t.Icon} label={t.label}
                  onClick={() => { setActive(t.id); setAppsOpen(false); }} />
              ))}
            </div>
          ))}
        </div>
      </Sheet>

      {/* Mehr Sheet */}
      <Sheet open={menuOpen} onClose={()=>setMenuOpen(false)}>
        <div style={{height:1, background:'rgba(255,255,255,0.08)', margin:'4px 0'}}/>
        <MI Icon={UserIcon} label={user?.username||''} onClick={()=>{}} color="rgba(255,255,255,0.35)"/>
        <MI Icon={LogOutIcon} label="Ausloggen" onClick={()=>{onLogout();setMenuOpen(false);}} color="#ff6b9d"/>
      </Sheet>

      {/* Bottom Bar */}
      <nav style={{ position:'fixed', bottom:0, left:0, right:0, zIndex:3000,
        background:'#0d0d0f', borderTop:'1px solid rgba(255,255,255,0.12)' }}>
        <div style={{ display:'flex', height:NAV_H }}>
          {mainNav.map(item => {
            const isApps = item.id === '_apps';
            const isMehr = item.id === '_mehr';
            const isActive = isApps ? appsOpen : isMehr ? menuOpen : active === item.id;
            return (
              <button key={item.id}
                onClick={() => {
                  if (isApps) { setAppsOpen(v=>!v); setMenuOpen(false); }
                  else if (isMehr) { setMenuOpen(v=>!v); setAppsOpen(false); }
                  else { setActive(item.id); setAppsOpen(false); setMenuOpen(false); }
                }}
                style={{ flex:1, display:'flex', flexDirection:'column', alignItems:'center',
                  justifyContent:'center', gap:3, background:'transparent', border:'none',
                  cursor:'pointer', position:'relative', minWidth:0, padding:'0 4px' }}>
                {isApps && unreadMsgs>0 && (
                  <span style={{ position:'absolute', top:6, right:'calc(50% - 16px)',
                    width:8, height:8, borderRadius:'50%', background:'#4ecdc4',
                    boxShadow:'0 0 6px #4ecdc4', display:'inline-block' }} />
                )}
                {(() => { const I = item.Icon; return I ? <I size={20} color={isActive ? '#4ecdc4' : 'rgba(255,255,255,0.45)'}/> : null; })()}
                <span style={{ fontSize:9, fontFamily:'monospace', letterSpacing:0.3,
                  color: isActive ? '#4ecdc4' : 'rgba(255,255,255,0.4)' }}>{item.label}</span>
                {isActive && !isApps && !isMehr && (
                  <span style={{ position:'absolute', top:0, left:'50%', transform:'translateX(-50%)',
                    width:24, height:2, background:'#4ecdc4', borderRadius:'0 0 3px 3px' }} />
                )}
              </button>
            );
          })}
        </div>
        <div style={{ height:'env(safe-area-inset-bottom, 0px)', background:'#0d0d0f' }}/>
      </nav>
    </>
  );
}


// ── Dashboard Widgets ─────────────────────────────────────────────────────────
// ── QuickLinks Carousel (Mobile) ─────────────────────────────────────────────
function QuickLinksCarousel({ links, onFolderClick }) {
  const perRow = 4;
  const gap = 6;
  const pageSize = perRow * 2;
  const pageCount = Math.ceil(links.length / pageSize);

  const [page, setPage] = useState(0);
  const scrollRef = useRef(null);

  const onScroll = () => {
    if (!scrollRef.current) return;
    const p = Math.round(scrollRef.current.scrollLeft / scrollRef.current.offsetWidth);
    setPage(p);
  };

  const canLeft  = page > 0;
  const canRight = page < pageCount - 1;

  const scrollTo = (p) => {
    scrollRef.current?.scrollTo({ left: p * scrollRef.current.offsetWidth, behavior:'smooth' });
  };

  if (pageCount <= 1) return (
    <div style={{ display:'grid', gridTemplateColumns:`repeat(${perRow}, 1fr)`, gap }}>
      {links.map(l => <QuickLinkIcon key={l.id} l={l} onFolderClick={onFolderClick}/>)}
    </div>
  );

  return (
    <div style={{ position:'relative', overflow:'hidden' }}>
      <div ref={scrollRef} onScroll={onScroll} style={{
        display:'flex', overflowX:'auto', scrollSnapType:'x mandatory',
        scrollbarWidth:'none', WebkitOverflowScrolling:'touch',
      }}>
        {Array.from({length: pageCount}, (_,pi) => (
          <div key={pi} style={{
            display:'grid',
            gridTemplateColumns:`repeat(${perRow}, 1fr)`,
            gridTemplateRows:'repeat(2, auto)',
            gap, flexShrink:0, scrollSnapAlign:'start',
            width:'100%', minWidth:'100%', boxSizing:'border-box',
          }}>
            {links.slice(pi*pageSize, (pi+1)*pageSize).map(l => <QuickLinkIcon key={l.id} l={l} onFolderClick={onFolderClick}/>)}
          </div>
        ))}
      </div>

      {/* Page dots */}
      {pageCount > 1 && (
        <div style={{ display:'flex', justifyContent:'center', gap:4, marginTop:6 }}>
          {Array.from({length:pageCount}, (_,i) => (
            <div key={i} onClick={() => scrollTo(i)} style={{
              width: i===page ? 14 : 5, height:5, borderRadius:3, cursor:'pointer',
              background: i===page ? '#4ecdc4' : 'rgba(255,255,255,0.2)',
              transition:'all 0.2s',
            }}/>
          ))}
        </div>
      )}
    </div>
  );
}

function QuickLinkIcon({ l, onFolderClick }) {
  const [imgOk, setImgOk] = useState(true);

  const hasCustomIcon = l.icon && l.icon !== '🔗';
  const faviconUrl = (!hasCustomIcon && !l.isFolder) ? (() => {
    try { return `https://www.google.com/s2/favicons?sz=64&domain=${new URL(l.url).hostname}`; }
    catch { return null; }
  })() : null;

  const showFavicon   = !hasCustomIcon && !!faviconUrl && imgOk && !l.isFolder;
  const showCustomImg = hasCustomIcon && !l.isFolder && l.icon.startsWith('http');
  const showEmoji     = !showFavicon && !showCustomImg;
  const emoji         = l.isFolder ? (l.icon||'📁') : (hasCustomIcon ? l.icon : '🔗');

  const content = (
    <>
      {showFavicon   && <img src={faviconUrl} alt="" style={{width:18,height:18,objectFit:'contain'}} onError={()=>setImgOk(false)}/>}
      {showCustomImg && <img src={l.icon}     alt="" style={{width:18,height:18,objectFit:'contain'}} onError={()=>setImgOk(false)}/>}
      {showEmoji     && <span style={{fontSize:18}}>{emoji}</span>}
      <span style={{color:'rgba(255,255,255,0.65)',fontSize:8,fontFamily:'monospace',
        maxWidth:52,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{l.title||l.name}</span>
    </>
  );

  const baseStyle = {display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',gap:3,
    padding:'4px 2px',background:'rgba(255,255,255,0.04)',
    border: l.isFolder ? '1px solid rgba(78,205,196,0.25)' : '1px solid rgba(255,255,255,0.08)',
    borderRadius:10,textDecoration:'none',height:48,width:'100%',boxSizing:'border-box',cursor:'pointer'};

  if (l.isFolder && onFolderClick) {
    const realId = typeof l.id==='string'&&l.id.startsWith('folder-') ? parseInt(l.id.replace('folder-','')) : l.id;
    return <button onClick={()=>onFolderClick({...l,id:realId})} style={{...baseStyle,background:'rgba(78,205,196,0.04)'}}>{content}</button>;
  }
  return <a href={l.url} target="_blank" rel="noopener noreferrer" style={baseStyle}>{content}</a>;
}

function FolderModal({ folder, onClose }) {
  const [links, setLinks] = useState([]);
  const [loading, setLoading] = useState(true);
  useEffect(()=>{
    api(`/tools/linkliste/folders/${folder.id}/links`)
      .then(d=>{ setLinks(d.links||[]); setLoading(false); })
      .catch(()=>setLoading(false));
  },[folder.id]);
  const mob = window.innerWidth < 768;
  return (
    <div style={{position:'fixed',inset:0,background:'rgba(0,0,0,0.85)',zIndex:6000,
      display:'flex',alignItems:mob?'flex-end':'center',justifyContent:'center',padding:mob?0:24}}
      onClick={e=>e.target===e.currentTarget&&onClose()}>
      <div style={{background:'#1a1a1e',borderRadius:mob?'16px 16px 0 0':14,width:'100%',maxWidth:480,
        padding:'20px 20px 28px',border:'1px solid rgba(255,255,255,0.12)'}}>
        {mob&&<div style={{width:36,height:4,background:'rgba(255,255,255,0.15)',borderRadius:2,margin:'0 auto 14px'}}/>}
        <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:16}}>
          <div style={{color:'#fff',fontFamily:"'Space Mono',monospace",fontSize:15,fontWeight:700}}>
            {folder.icon} {folder.name}
          </div>
          <button onClick={onClose} style={{background:'transparent',border:'none',color:'rgba(255,255,255,0.4)',
            cursor:'pointer',fontSize:20,padding:'0 4px'}}>✕</button>
        </div>
        {loading ? <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:12,textAlign:'center',padding:20}}>…</div>
        : links.length===0 ? <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:12,textAlign:'center',padding:20}}>Ordner ist leer</div>
        : <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(72px,1fr))',gap:8}}>
            {links.map(l=><QuickLinkIcon key={l.id} l={l}/>)}
          </div>
        }
      </div>
    </div>
  );
}

function QuickLinks({ toast, mobile }) {
  const [links,       setLinks]       = useState([]);
  const [folderModal, setFolderModal] = useState(null);
  const [editMode,    setEditMode]    = useState(false);

  useEffect(() => { api('/dashboard/links').then(setLinks).catch(() => {}); }, []);

  const legacyItems = links.filter(l => l.item_type === 'quicklink' || !l.item_type);
  const hasLegacy   = legacyItems.length > 0;

  const deleteLegacy = async id => {
    try {
      await api(`/dashboard/links/${id}`, { method:'DELETE' });
      setLinks(p => p.filter(l => l.id !== id));
      toast('Entfernt');
    } catch(e) { toast(e.message,'error'); }
  };

  const allItems = links.map(l => l.item_type === 'folder'
    ? { ...l, id:`folder-${l.id}`, isFolder:true, icon:l.icon||'📁', title:l.name }
    : l
  );

  return (
    <div style={{ ...S.card, marginBottom:10 }}>
      {folderModal && <FolderModal folder={folderModal} onClose={()=>setFolderModal(null)}/>}
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:10 }}>
        <div style={S.head}>SCHNELLZUGRIFF</div>
        {hasLegacy && (
          <button onClick={()=>setEditMode(p=>!p)} style={{
            background:'transparent', border:'none', color:'rgba(255,255,255,0.3)',
            cursor:'pointer', fontFamily:'monospace', fontSize:10, padding:'2px 6px'}}>
            {editMode ? '✓ Fertig' : '✎'}
          </button>
        )}
      </div>

      {/* Edit-Modus: alte Quick-Links verwalten */}
      {editMode && hasLegacy && (
        <div style={{marginBottom:12,padding:'10px 12px',background:'rgba(255,255,255,0.03)',
          borderRadius:8,border:'1px solid rgba(255,255,255,0.07)'}}>
          <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:9,marginBottom:8}}>
            ÄLTERE LINKS (aus Einstellungen) — können hier gelöscht werden
          </div>
          {legacyItems.map(l=>(
            <div key={l.id} style={{display:'flex',alignItems:'center',gap:8,padding:'5px 0',
              borderBottom:'1px solid rgba(255,255,255,0.04)'}}>
              <span style={{fontSize:14,flexShrink:0}}>{l.icon?.startsWith('http')
                ? <img src={l.icon} style={{width:14,height:14,objectFit:'contain',verticalAlign:'middle'}} alt=""/>
                : l.icon||'🔗'}</span>
              <div style={{flex:1,minWidth:0}}>
                <div style={{color:'#fff',fontFamily:'monospace',fontSize:12,
                  overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{l.title}</div>
              </div>
              <button onClick={()=>deleteLegacy(l.id)}
                style={{...S.btn('#ff6b9d',true),fontSize:10,padding:'3px 8px',flexShrink:0}}>✕</button>
            </div>
          ))}
          <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,marginTop:8,lineHeight:1.6}}>
            Neue Links über Linkliste → ● Schnellzugriff verwalten
          </div>
        </div>
      )}

      {allItems.length === 0
        ? <div style={{ color:'rgba(255,255,255,0.5)', fontSize:11, fontFamily:'monospace', padding:'6px 0' }}>
            Links oder Ordner in der Linkliste mit ● Schnellzugriff aktivieren.
          </div>
        : mobile
          ? <QuickLinksCarousel links={allItems} onFolderClick={setFolderModal}/>
          : <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fill,minmax(58px,1fr))', gap:6 }}>
              {allItems.map(l => <QuickLinkIcon key={l.id} l={l} onFolderClick={l.isFolder?setFolderModal:undefined}/>)}
            </div>
      }
    </div>
  );
}

function TodoList({ toast }) {
  const [items, setItems] = useState([]);
  const [text,  setText]  = useState('');
  const [isOpen, setIsOpen] = useState(false);
  useEffect(() => { api('/dashboard/todos').then(setItems).catch(() => {}); }, []);

  const add = async () => {
    if (!text.trim()) return;
    try { const n = await api('/dashboard/todos', { body:{ text:text.trim() } }); setItems(p => [...p, n]); setText(''); }
    catch(e) { toast(e.message, 'error'); }
  };
  const toggle = async item => {
    try { const u = await api(`/dashboard/todos/${item.id}`, { method:'PUT', body:{ done:!item.done } }); setItems(p => p.map(i => i.id===item.id ? u : i)); }
    catch(e) { toast(e.message, 'error'); }
  };
  const del = async id => {
    try { await api(`/dashboard/todos/${id}`, { method:'DELETE' }); setItems(p => p.filter(i => i.id!==id)); }
    catch(e) { toast(e.message, 'error'); }
  };

  const open = items.filter(i => !i.done);
  const done = items.filter(i =>  i.done);

  return (
    <div style={{ ...S.card, marginBottom:10 }}>
      <button onClick={()=>setIsOpen(v=>!v)} style={{width:'100%',background:'transparent',border:'none',
        display:'flex',justifyContent:'space-between',alignItems:'center',cursor:'pointer',padding:0}}>
        <div style={{...S.head, marginBottom:0}}>TO-DO{items.filter(i=>!i.done).length > 0
          ? <span style={{color:'#4ecdc4',fontFamily:'monospace',fontSize:10,marginLeft:6}}>({items.filter(i=>!i.done).length})</span>
          : null}</div>
        <span style={{color:'rgba(255,255,255,0.4)',fontSize:11,display:'inline-block',
          transform:isOpen?'rotate(90deg)':'rotate(0)',transition:'transform 0.2s'}}>▶</span>
      </button>
      {isOpen && <><div style={{ display:'flex', gap:6, marginBottom:10, marginTop:10 }}>
        <input value={text} onChange={e => setText(e.target.value)}
          onKeyDown={e => e.key==='Enter' && add()}
          style={{ ...S.inp, flex:1, fontSize:16 }} placeholder="Neue Aufgabe…" />
        <button onClick={add} style={{ ...S.btn('#4ecdc4'), padding:'0 16px', fontSize:20, lineHeight:1 }}>+</button>
      </div>
      <div style={{ maxHeight:220, overflowY:'auto' }}>
        {open.length === 0 && done.length === 0 && (
          <div style={{ color:'rgba(255,255,255,0.45)', fontSize:11, fontFamily:'monospace', padding:'6px 0' }}>Noch keine Aufgaben.</div>
        )}
        {open.map(item => (
          <div key={item.id} style={{ display:'flex', alignItems:'center', gap:10, padding:'11px 0', borderBottom:'1px solid rgba(255,255,255,0.04)' }}>
            <button onClick={() => toggle(item)} style={{
              width:22, height:22, borderRadius:5, flexShrink:0, cursor:'pointer',
              border:'1.5px solid rgba(78,205,196,0.5)', background:'transparent',
            }} />
            <span style={{ color:'rgba(255,255,255,0.8)', fontSize:13, fontFamily:'monospace', flex:1, lineHeight:1.4 }}>{item.text}</span>
            <button onClick={() => del(item.id)} style={{ background:'transparent', border:'none', color:'rgba(255,255,255,0.5)', cursor:'pointer', fontSize:18, padding:'0 2px', lineHeight:1 }}>✕</button>
          </div>
        ))}
        {done.length > 0 && (
          <>
            <div style={{ ...S.head, padding:'10px 0 4px', marginBottom:0 }}>ERLEDIGT</div>
            {done.map(item => (
              <div key={item.id} style={{ display:'flex', alignItems:'center', gap:10, padding:'9px 0', opacity:0.4 }}>
                <button onClick={() => toggle(item)} style={{
                  width:22, height:22, borderRadius:5, flexShrink:0, cursor:'pointer',
                  border:'1.5px solid rgba(78,205,196,0.4)', background:'rgba(78,205,196,0.15)',
                  color:'#4ecdc4', fontSize:12, lineHeight:1,
                }}>✓</button>
                <span style={{ color:'rgba(255,255,255,0.4)', fontSize:13, fontFamily:'monospace', flex:1, textDecoration:'line-through' }}>{item.text}</span>
                <button onClick={() => del(item.id)} style={{ background:'transparent', border:'none', color:'rgba(255,255,255,0.5)', cursor:'pointer', fontSize:18 }}>✕</button>
              </div>
            ))}
          </>
        )}
      </div></>}
    </div>
  );
}

function Notepad({ toast }) {
  const [content,  setContent]  = useState('');
  const [saved,    setSaved]    = useState(true);
  const [saveTime, setSaveTime] = useState(null);
  const [isOpen,   setIsOpen]   = useState(false);
  const tmr = useRef(null);

  useEffect(() => {
    api('/dashboard/note').then(d => { setContent(d.content || ''); setSaveTime(d.updated_at); }).catch(() => {});
  }, []);

  const change = val => {
    setContent(val); setSaved(false); clearTimeout(tmr.current);
    tmr.current = setTimeout(async () => {
      try { const r = await api('/dashboard/note', { method:'PUT', body:{ content:val } }); setSaved(true); setSaveTime(r.updated_at); }
      catch(e) { toast(e.message, 'error'); }
    }, 900);
  };

  return (
    <div style={{ ...S.card }}>
      <button onClick={()=>setIsOpen(v=>!v)} style={{width:'100%',background:'transparent',border:'none',
        display:'flex',alignItems:'center',gap:8,cursor:'pointer',padding:0}}>
        <div style={{...S.head, marginBottom:0}}>NOTIZEN</div>
        {content.length > 0 && (
          <span style={{color:'#4ecdc4',fontFamily:'monospace',fontSize:10}}>({content.length} Zeichen)</span>
        )}
        <span style={{color:'rgba(255,255,255,0.4)',fontSize:11,display:'inline-block',marginLeft:'auto',
          transform:isOpen?'rotate(90deg)':'rotate(0)',transition:'transform 0.2s'}}>▶</span>
      </button>
      {isOpen && <div style={{ color: saved ? 'rgba(78,205,196,0.5)' : 'rgba(255,230,109,0.6)',
        fontSize:10, fontFamily:'monospace', marginTop:4, marginBottom:6, textAlign:'right' }}>
        {saved ? (saveTime ? `✓ ${new Date(saveTime).toLocaleTimeString('de-DE', { hour:'2-digit', minute:'2-digit' })}` : '✓') : '…'}
      </div>}
      {isOpen && <textarea value={content} onChange={e => change(e.target.value)}
        placeholder="Notizen, IPs, Zugangsdaten…"
        style={{ ...S.inp, width:'100%', minHeight:140, resize:'vertical', lineHeight:1.7, padding:'10px 12px', fontSize:14 }} />}
    </div>
  );
}


// ── Bestell-Statistik Widget ─────────────────────────────────────────────────
function BestellStats() {
  const [stats,  setStats]  = useState(null);
  const [open,   setOpen]   = useState(false);
  const [loaded, setLoaded] = useState(false);

  const load = () => {
    if (loaded) return;
    api('/dashboard/stats').then(s => { setStats(s); setLoaded(true); }).catch(() => {});
  };

  const toggle = () => { setOpen(v => !v); if (!open) load(); };

  const Row = ({label, val, color='#fff'}) => (
    <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',padding:'7px 0',borderBottom:'1px solid rgba(255,255,255,0.05)'}}>
      <span style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:12}}>{label}</span>
      <span style={{color,fontFamily:"'Space Mono',monospace",fontSize:13,fontWeight:700}}>{val}</span>
    </div>
  );

  return (
    <div style={{...S.card,marginBottom:10}}>
      <button onClick={toggle} style={{width:'100%',background:'transparent',border:'none',
        display:'flex',justifyContent:'space-between',alignItems:'center',cursor:'pointer',padding:0}}>
        <div style={{...S.head, marginBottom:0}}>3D-DRUCK STATISTIK</div>
        <span style={{color:'rgba(255,255,255,0.3)',fontSize:11,display:'inline-block',
          transform:open?'rotate(90deg)':'rotate(0)',transition:'transform 0.2s'}}>▶</span>
      </button>
      {open && (
        <div style={{marginTop:12}}>
          {!stats ? (
            <div style={{color:'rgba(255,255,255,0.55)',fontFamily:'monospace',fontSize:12}}>Lädt…</div>
          ) : (
            <>
              <div style={{marginBottom:12}}>
                <div style={{...S.head,marginBottom:6}}>BESTELLUNGEN</div>
                <Row label="Gesamt" val={stats.totalOrders}/>
                <Row label="Diesen Monat" val={stats.ordersThisMonth} color="#4ecdc4"/>
                <Row label="Warteliste" val={stats.byStatus.warteliste||0} color="#ffe66d"/>
                <Row label="In Arbeit" val={stats.byStatus.in_arbeit||0} color="#4ecdc4"/>
                <Row label="Fertig (unbezahlt)" val={stats.byStatus.fertig||0} color="#6bcb77"/>
                <Row label="Bezahlt" val={stats.bezahltOrders} color="#c084fc"/>
              </div>
              <div>
                <div style={{...S.head,marginBottom:6}}>EINNAHMEN</div>
                <Row label="Eingenommen (bezahlt)" val={`${stats.totalRevenue.toFixed(2)} €`} color="#6bcb77"/>
                <Row label="Offen (in Arbeit/Fertig)" val={`${stats.offeneRevenue.toFixed(2)} €`} color="#ffe66d"/>
                <Row label="Archiv-Einträge" val={stats.totalCalcs} color="rgba(255,255,255,0.5)"/>
              </div>
            </>
          )}
        </div>
      )}
    </div>
  );
}

// ── Nachrichten Dashboard Widget ──────────────────────────────────────────────
function NachrichtenWidget({ unreadMsgs, setActive }) {
  const [isOpen, setIsOpen] = useState(false);
  return (
    <div style={{ ...S.card, marginBottom:10 }}>
      <button onClick={() => setIsOpen(v=>!v)} style={{
        width:'100%', background:'transparent', border:'none', cursor:'pointer',
        display:'flex', alignItems:'center', padding:0, gap:8 }}>
        <MessageIcon size={13} color="rgba(255,255,255,0.5)"/>
        <span style={{ ...S.head, marginBottom:0, flex:1, textAlign:'left' }}>NACHRICHTEN</span>
        {unreadMsgs > 0 && !isOpen && (
          <span style={{ background:'#4ecdc4', color:'#000', borderRadius:10, fontSize:9,
            fontFamily:'monospace', padding:'2px 7px', fontWeight:700 }}>
            {unreadMsgs} neu
          </span>
        )}
        <span style={{ color:'rgba(255,255,255,0.4)', fontSize:11,
          transform:isOpen?'rotate(90deg)':'rotate(0)', transition:'transform 0.2s' }}>▶</span>
      </button>
      {isOpen && (
        <div style={{ marginTop:12, display:'flex', alignItems:'center', justifyContent:'space-between', gap:10 }}>
          <span style={{ color: unreadMsgs > 0 ? '#4ecdc4' : 'rgba(255,255,255,0.35)',
            fontSize:12, fontFamily:'monospace' }}>
            {unreadMsgs > 0
              ? `${unreadMsgs} ungelesene Nachricht${unreadMsgs !== 1 ? 'en' : ''}`
              : 'Keine neuen Nachrichten'}
          </span>
          <button onClick={() => setActive('nachrichten')} style={{ ...S.btn('#4ecdc4', true), flexShrink:0 }}>
            Öffnen →
          </button>
        </div>
      )}
    </div>
  );
}

// ── Pushover Settings ─────────────────────────────────────────────────────────
function PushoverSettings({ toast }) {
  const [form,     setForm]     = useState({ user_key:'', app_token:'', retry: '', expire: '' });
  const [busy,     setBusy]     = useState(false);
  const [hasSaved, setHasSaved] = useState(false);
  const [emergency,setEmergency]= useState(false);

  useEffect(() => {
    api('/tools/nachrichten/pushover')
      .then(d => {
        const v = { user_key: d.user_key||'', app_token: d.app_token||'',
          retry: d.retry||'', expire: d.expire||'' };
        setForm(v);
        setHasSaved(!!(v.user_key && v.app_token));
        setEmergency(!!(d.retry && d.expire));
      }).catch(()=>{});
  }, []);

  const save = async () => {
    if (!form.user_key.trim() || !form.app_token.trim()) { toast('Beide Felder ausfüllen', 'error'); return; }
    setBusy(true);
    try {
      await api('/tools/nachrichten/pushover', { body: {
        ...form,
        retry:  emergency ? (parseInt(form.retry)  || 60)  : null,
        expire: emergency ? (parseInt(form.expire) || 3600) : null,
      }});
      toast('Gespeichert'); setHasSaved(true);
    } catch(e) { toast(e.message, 'error'); }
    setBusy(false);
  };

  const test = async () => {
    setBusy(true);
    try { await api('/tools/nachrichten/pushover/test', { method:'POST', body:{ emergency: false } }); toast('Test gesendet ✓ (normale Priorität)'); }
    catch(e) { toast(e.message, 'error'); }
    setBusy(false);
  };

  const testEmergency = async () => {
    setBusy(true);
    try { await api('/tools/nachrichten/pushover/test', { method:'POST', body:{ emergency: true } }); toast('🚨 Emergency-Test gesendet – quittiere in der Pushover-App!'); }
    catch(e) { toast(e.message, 'error'); }
    setBusy(false);
  };

  const clear = async () => {
    try {
      await api('/tools/nachrichten/pushover', { method:'DELETE' });
      setForm({ user_key:'', app_token:'', retry:'', expire:'' });
      setHasSaved(false); setEmergency(false);
      toast('Pushover entfernt');
    } catch(e) { toast(e.message, 'error'); }
  };

  return (
    <div>
      <p style={{ color:'rgba(255,255,255,0.42)', fontSize:11, fontFamily:'monospace', marginBottom:12, lineHeight:1.7 }}>
        Push-Benachrichtigungen via{' '}
        <a href="https://pushover.net" target="_blank" rel="noopener noreferrer" style={{ color:'#4ecdc4' }}>pushover.net</a>.
        Du brauchst einen Account und eine eigene App (kostenlos).
      </p>
      <Field label="USER KEY"  value={form.user_key}  onChange={e=>setForm(p=>({...p,user_key:e.target.value}))} />
      <Field label="APP TOKEN" value={form.app_token} onChange={e=>setForm(p=>({...p,app_token:e.target.value}))} />

      {/* Emergency / Wiederholung */}
      <div style={{ marginBottom:14, background:'rgba(255,255,255,0.03)', border:'1px solid rgba(255,255,255,0.08)',
        borderRadius:8, padding:'10px 12px' }}>
        <label style={{ display:'flex', alignItems:'center', gap:8, cursor:'pointer', marginBottom: emergency ? 10 : 0 }}>
          <input type="checkbox" checked={emergency} onChange={e=>setEmergency(e.target.checked)}
            style={{ width:14, height:14, accentColor:'#ff6b9d', cursor:'pointer' }}/>
          <span style={{ color:'rgba(255,255,255,0.7)', fontFamily:'monospace', fontSize:11 }}>
            🚨 Wiederholen bis Quittierung (Emergency-Priorität)
          </span>
        </label>
        {emergency && (
          <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:8, marginTop:6 }}>
            <div>
              <label style={{ ...S.head, display:'block', marginBottom:3, fontSize:9 }}>WIEDERHOLUNG (SEK)</label>
              <input type="number" value={form.retry||60} min={30} max={3600}
                onChange={e=>setForm(p=>({...p,retry:e.target.value}))}
                style={{ ...S.inp, fontSize:13 }}/>
              <div style={{ color:'rgba(255,255,255,0.2)', fontFamily:'monospace', fontSize:8, marginTop:2 }}>
                min. 30s · z.B. 60 = jede Minute
              </div>
            </div>
            <div>
              <label style={{ ...S.head, display:'block', marginBottom:3, fontSize:9 }}>MAX. DAUER (SEK)</label>
              <input type="number" value={form.expire||3600} min={60} max={86400}
                onChange={e=>setForm(p=>({...p,expire:e.target.value}))}
                style={{ ...S.inp, fontSize:13 }}/>
              <div style={{ color:'rgba(255,255,255,0.2)', fontFamily:'monospace', fontSize:8, marginTop:2 }}>
                z.B. 3600 = max. 1 Stunde
              </div>
            </div>
          </div>
        )}
      </div>

      <div style={{ display:'flex', gap:8 }}>
        <button onClick={save} disabled={busy}
          style={{ ...S.btn('#4ecdc4'), flex:1, textAlign:'center', padding:'10px 0', opacity:busy?0.5:1 }}>
          {busy ? '…' : '✓ Speichern'}
        </button>
        {hasSaved && (
          <button onClick={test} disabled={busy}
            style={{ ...S.btn('#ffe66d', true), padding:'0 10px', opacity:busy?0.5:1, fontSize:10 }}>Test</button>
        )}
        {hasSaved && emergency && (
          <button onClick={testEmergency} disabled={busy}
            style={{ ...S.btn('#ff6b9d', true), padding:'0 10px', opacity:busy?0.5:1, fontSize:10 }}>🚨 Test</button>
        )}
        {hasSaved && (
          <button onClick={clear}
            style={{ ...S.btn('#ff6b9d', true), padding:'0 10px' }}>✕</button>
        )}
      </div>
    </div>
  );
}

// ── Ideen-Board Modal ─────────────────────────────────────────────────────────
// ── Board ItemRow (außerhalb BoardModal damit Inputs stabil bleiben) ───────────
function BoardItemRow({ item, isAdmin, userId, editId, editForm, setEditId, setEditForm, onSave, onDelete, onPromote }) {
  const isEditing = editId === item.id;
  const canEdit = isAdmin || (item.user_id === userId && item.type === 'wish');
  return (
    <div style={{padding:'10px 0',borderBottom:'1px solid rgba(255,255,255,0.05)'}}>
      {isEditing ? (
        <div>
          <input value={editForm.title} onChange={e=>setEditForm(p=>({...p,title:e.target.value}))}
            style={{...S.inp,marginBottom:6,fontSize:13}} autoFocus/>
          <input value={editForm.description} onChange={e=>setEditForm(p=>({...p,description:e.target.value}))}
            placeholder="Beschreibung (optional)" style={{...S.inp,marginBottom:8,fontSize:12}}/>
          <div style={{display:'flex',gap:6}}>
            <button onClick={()=>onSave(item.id)} style={{...S.btn('#4ecdc4'),fontSize:11,padding:'5px 12px'}}>✓</button>
            <button onClick={()=>setEditId(null)} style={{...S.btn('#ff6b9d',true),fontSize:11,padding:'5px 10px'}}>✕</button>
          </div>
        </div>
      ) : (
        <div style={{display:'flex',alignItems:'flex-start',gap:8}}>
          <div style={{flex:1,minWidth:0}}>
            <div style={{display:'flex',alignItems:'center',gap:5,flexWrap:'wrap'}}>
              <div style={{color:'#fff',fontFamily:'monospace',fontSize:13,fontWeight:700}}>{item.title}</div>
              {!!item.promoted_from_wish && (
                <span style={{background:'rgba(255,230,109,0.1)',border:'1px solid rgba(255,230,109,0.25)',
                  borderRadius:5,padding:'1px 6px',color:'#ffe66d',fontSize:9,fontFamily:'monospace'}}>↑ aus Wünschen</span>
              )}
            </div>
            {item.description && <div style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:11,marginTop:2}}>{item.description}</div>}
            <div style={{color:'rgba(255,255,255,0.2)',fontFamily:'monospace',fontSize:9,marginTop:3}}>
              {item.author} · {item.created_at ? new Date(item.created_at.replace(' ','T')).toLocaleDateString('de-DE') : ''}
            </div>
          </div>
          <div style={{display:'flex',gap:4,flexShrink:0}}>
            {isAdmin && item.type==='wish' && (
              <button onClick={()=>onPromote(item.id)} title="Zur Roadmap befördern"
                style={{...S.btn('#ffe66d',true),padding:'3px 8px',fontSize:11}}>↑</button>
            )}
            {canEdit && (
              <>
                <button onClick={()=>{setEditId(item.id);setEditForm({title:item.title,description:item.description||''});}}
                  style={{...S.btn('#4ecdc4',true),padding:'3px 8px',fontSize:11}}>✎</button>
                <button onClick={()=>onDelete(item.id)} style={{...S.btn('#ff6b9d',true),padding:'3px 8px',fontSize:11}}>✕</button>
              </>
            )}
          </div>
        </div>
      )}
    </div>
  );
}

function BoardModal({ user, toast, onClose, onRead }) {
  const isAdmin = user?.role === 'admin';
  const [items,    setItems]    = useState([]);
  const [loading,  setLoading]  = useState(true);
  const [form,     setForm]     = useState({ type:'wish', title:'', description:'' });
  const [editId,   setEditId]   = useState(null);
  const [editForm, setEditForm] = useState({ title:'', description:'' });
  const isMobile = window.innerWidth < 768;

  useEffect(()=>{
    api('/dashboard/board').then(d=>{ setItems(d.items||[]); setLoading(false); }).catch(()=>setLoading(false));
    api('/dashboard/board/read',{method:'POST'}).then(()=>onRead()).catch(()=>{});
  },[]);

  const add = async () => {
    if (!form.title.trim()) { toast('Titel erforderlich','error'); return; }
    try {
      const r = await api('/dashboard/board',{body:form});
      setItems(p=>[r,...p]); setForm({type:'wish',title:'',description:''});
      toast('Hinzugefügt ✓');
    } catch(e){ toast(e.message,'error'); }
  };

  const save = async id => {
    try {
      const r = await api(`/dashboard/board/${id}`,{method:'PUT',body:editForm});
      setItems(p=>p.map(i=>i.id===id?r:i)); setEditId(null); toast('Gespeichert ✓');
    } catch(e){ toast(e.message,'error'); }
  };

  const del = async id => {
    try { await api(`/dashboard/board/${id}`,{method:'DELETE'}); setItems(p=>p.filter(i=>i.id!==id)); }
    catch(e){ toast(e.message,'error'); }
  };

  const promote = async id => {
    try {
      const r = await api(`/dashboard/board/${id}/promote`,{method:'POST'});
      setItems(p=>p.map(i=>i.id===id?r:i)); toast('Zur Roadmap befördert ✓');
    } catch(e){ toast(e.message,'error'); }
  };

  const roadmap = items.filter(i=>i.type==='roadmap');
  const wishes  = items.filter(i=>i.type==='wish');
  const rowProps = { isAdmin, userId:user?.id, editId, editForm, setEditId, setEditForm,
    onSave:save, onDelete:del, onPromote:promote };

  return (
    <div style={{position:'fixed',inset:0,background:'rgba(0,0,0,0.85)',zIndex:7000,
      display:'flex',alignItems:isMobile?'flex-end':'center',justifyContent:'center',padding:isMobile?0:24}}
      onClick={e=>e.target===e.currentTarget&&onClose()}>
      <div style={{background:'#1a1a1e',borderRadius:isMobile?'16px 16px 0 0':16,width:'100%',maxWidth:520,
        border:'1px solid rgba(255,255,255,0.12)',display:'flex',flexDirection:'column',
        maxHeight:isMobile?'85vh':'80vh',overflow:'hidden'}}>

        <div style={{padding:'16px 20px',borderBottom:'1px solid rgba(255,255,255,0.08)',flexShrink:0,
          display:'flex',justifyContent:'space-between',alignItems:'center',position:'relative'}}>
          {isMobile&&<div style={{width:36,height:4,background:'rgba(255,255,255,0.15)',borderRadius:2,
            position:'absolute',top:8,left:'50%',transform:'translateX(-50%)'}}/>}
          <div style={{color:'#fff',fontFamily:"'Space Mono',monospace",fontSize:14,fontWeight:700}}>📋 Ideen & Roadmap</div>
          <button onClick={onClose} style={{background:'transparent',border:'none',color:'rgba(255,255,255,0.4)',cursor:'pointer',fontSize:18}}>✕</button>
        </div>

        <div style={{overflowY:'auto',flex:1,padding:'0 20px'}}>
          <div style={{padding:'14px 0 4px'}}>
            <div style={{...S.head,marginBottom:6}}>🗺 ROADMAP{isAdmin&&<span style={{color:'rgba(255,255,255,0.3)',fontWeight:400}}> (Admin)</span>}</div>
            {loading ? <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:11}}>Lädt…</div>
              : roadmap.length===0 ? <div style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:11}}>Noch keine Einträge.</div>
              : roadmap.map(i=><BoardItemRow key={i.id} item={i} {...rowProps}/>)}
            {isAdmin && (
              <div style={{marginTop:10}}>
                <input value={form.type==='roadmap'?form.title:''} placeholder="+ Roadmap-Eintrag hinzufügen…"
                  onFocus={()=>setForm(f=>({...f,type:'roadmap'}))}
                  onChange={e=>setForm(f=>({...f,title:e.target.value,type:'roadmap'}))}
                  onKeyDown={e=>e.key==='Enter'&&form.type==='roadmap'&&add()}
                  style={{...S.inp,fontSize:13,marginBottom:form.type==='roadmap'&&form.title?6:0}}/>
                {form.type==='roadmap'&&form.title&&(
                  <div style={{display:'flex',gap:6}}>
                    <input value={form.description} placeholder="Beschreibung (optional)"
                      onChange={e=>setForm(f=>({...f,description:e.target.value}))}
                      style={{...S.inp,fontSize:12,flex:1}}/>
                    <button onClick={add} style={{...S.btn('#4ecdc4'),fontSize:11,padding:'5px 12px'}}>+</button>
                  </div>
                )}
              </div>
            )}
          </div>

          <div style={{padding:'14px 0 20px',borderTop:'1px solid rgba(255,255,255,0.06)',marginTop:8}}>
            <div style={{...S.head,marginBottom:2}}>💡 WÜNSCHE & IDEEN</div>
            {isAdmin && <div style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:9,marginBottom:8}}>↑ befördert Wunsch zur Roadmap</div>}
            {loading ? null : wishes.length===0
              ? <div style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:11}}>Noch keine Wünsche.</div>
              : wishes.map(i=><BoardItemRow key={i.id} item={i} {...rowProps}/>)}
            <div style={{marginTop:10}}>
              <input value={form.type==='wish'?form.title:''} placeholder="+ Wunsch oder Idee einreichen…"
                onFocus={()=>setForm(f=>({...f,type:'wish'}))}
                onChange={e=>setForm(f=>({...f,title:e.target.value,type:'wish'}))}
                onKeyDown={e=>e.key==='Enter'&&form.type==='wish'&&add()}
                style={{...S.inp,fontSize:13,marginBottom:form.type==='wish'&&form.title?6:0}}/>
              {form.type==='wish'&&form.title&&(
                <div style={{display:'flex',gap:6}}>
                  <input value={form.description} placeholder="Beschreibung (optional)"
                    onChange={e=>setForm(f=>({...f,description:e.target.value}))}
                    style={{...S.inp,fontSize:12,flex:1}}/>
                  <button onClick={add} style={{...S.btn('#4ecdc4'),fontSize:11,padding:'5px 12px'}}>+</button>
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}


// ── Push-Zeitplaner Widget ────────────────────────────────────────────────────
function PushScheduler({ toast }) {
  const [isOpen,     setIsOpen]     = useState(false);
  const [schedules,  setSchedules]  = useState([]);
  const [msg,        setMsg]        = useState('');
  const [dt,         setDt]         = useState('');
  const [editId,     setEditId]     = useState(null);
  const [editMsg,    setEditMsg]    = useState('');
  const [editDt,     setEditDt]     = useState('');
  const [hasPushover,setHasPushover]= useState(null); // null=unbekannt

  useEffect(() => {
    if (!isOpen) return;
    api('/dashboard/push-schedules').then(setSchedules).catch(()=>{});
    if (hasPushover === null)
      api('/tools/nachrichten/pushover').then(d=>setHasPushover(!!(d.user_key&&d.app_token))).catch(()=>{});
  }, [isOpen]);

  // Vorauswahl: aktuelle Lokalzeit
  const defaultDt = () => {
    const d = new Date();
    const p = n => String(n).padStart(2,'0');
    return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`;
  };

  const fmtDt = s => { if (!s) return ''; const d = new Date(s.replace(' ','T')); return isNaN(d)?s:d.toLocaleString('de-DE',{day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}); };

  const add = async () => {
    if (!msg.trim()) { toast('Nachricht eingeben','error'); return; }
    if (!dt)         { toast('Datum und Uhrzeit wählen','error'); return; }
    try {
      const r = await api('/dashboard/push-schedules',{body:{message:msg.trim(),scheduled_at:dt}});
      setSchedules(p=>[...p,r].sort((a,b)=>a.scheduled_at.localeCompare(b.scheduled_at)));
      setMsg(''); setDt(''); toast('Erinnerung gespeichert ✓');
    } catch(e){ toast(e.message,'error'); }
  };

  const del = async id => {
    try { await api(`/dashboard/push-schedules/${id}`,{method:'DELETE'}); setSchedules(p=>p.filter(s=>s.id!==id)); }
    catch(e){ toast(e.message,'error'); }
  };

  const saveEdit = async () => {
    try {
      const r = await api(`/dashboard/push-schedules/${editId}`,{method:'PUT',body:{message:editMsg,scheduled_at:editDt}});
      setSchedules(p=>p.map(s=>s.id===editId?r:s).sort((a,b)=>a.scheduled_at.localeCompare(b.scheduled_at)));
      setEditId(null); toast('Gespeichert ✓');
    } catch(e){ toast(e.message,'error'); }
  };

  return (
    <div style={{...S.card, marginBottom:10}}>
      <button onClick={()=>setIsOpen(v=>!v)} style={{
        width:'100%',background:'transparent',border:'none',cursor:'pointer',
        display:'flex',alignItems:'center',padding:0,gap:8}}>
        <span style={{fontSize:13}}>🔔</span>
        <span style={{...S.head,marginBottom:0,flex:1,textAlign:'left'}}>PUSH-ERINNERUNGEN</span>
        {schedules.length>0&&!isOpen&&(
          <span style={{color:'rgba(78,205,196,0.7)',fontFamily:'monospace',fontSize:9}}>{schedules.length} aktiv</span>
        )}
        <span style={{color:'rgba(255,255,255,0.4)',fontSize:11,
          transform:isOpen?'rotate(90deg)':'rotate(0)',transition:'transform 0.2s'}}>▶</span>
      </button>

      {isOpen && (
        <div style={{marginTop:12}}>
          {hasPushover===false && (
            <div style={{background:'rgba(255,230,109,0.07)',border:'1px solid rgba(255,230,109,0.2)',
              borderRadius:7,padding:'8px 10px',color:'#ffe66d',fontFamily:'monospace',fontSize:10,
              marginBottom:10,lineHeight:1.6}}>
              ⚠ Kein Pushover eingerichtet. Bitte erst unter Einstellungen → Profil → Pushover konfigurieren.
            </div>
          )}

          {/* Neue Erinnerung */}
          <div style={{marginBottom:14}}>
            <input value={msg} onChange={e=>setMsg(e.target.value)}
              placeholder="Nachricht…" style={{...S.inp,marginBottom:6,fontSize:13}}
              onKeyDown={e=>e.key==='Enter'&&add()}/>
            <div style={{display:'flex',gap:6}}>
              <input type="datetime-local" value={dt||defaultDt()}
                onChange={e=>setDt(e.target.value)}
                style={{...S.inp,flex:1,fontSize:12,colorScheme:'dark'}}/>
              <button onClick={add} style={{...S.btn('#4ecdc4'),padding:'0 14px',flexShrink:0}}>+</button>
            </div>
          </div>

          {/* Liste */}
          {schedules.length===0 ? (
            <div style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:11}}>Keine aktiven Erinnerungen.</div>
          ) : schedules.map(s=>(
            <div key={s.id} style={{padding:'8px 0',borderBottom:'1px solid rgba(255,255,255,0.05)'}}>
              {editId===s.id ? (
                <div>
                  <input value={editMsg} onChange={e=>setEditMsg(e.target.value)}
                    style={{...S.inp,marginBottom:6,fontSize:13}} autoFocus/>
                  <div style={{display:'flex',gap:6}}>
                    <input type="datetime-local" value={editDt} onChange={e=>setEditDt(e.target.value)}
                      style={{...S.inp,flex:1,fontSize:12,colorScheme:'dark'}}/>
                    <button onClick={saveEdit} style={{...S.btn('#4ecdc4'),padding:'0 10px'}}>✓</button>
                    <button onClick={()=>setEditId(null)} style={{...S.btn('#ff6b9d',true),padding:'0 10px'}}>✕</button>
                  </div>
                </div>
              ) : (
                <div style={{display:'flex',alignItems:'center',gap:8}}>
                  <div style={{flex:1,minWidth:0}}>
                    <div style={{color:'#fff',fontFamily:'monospace',fontSize:12,
                      overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{s.message}</div>
                    <div style={{color:'#4ecdc4',fontFamily:'monospace',fontSize:10,marginTop:2}}>
                      🕐 {fmtDt(s.scheduled_at)}
                    </div>
                  </div>
                  <button onClick={()=>{setEditId(s.id);setEditMsg(s.message);setEditDt(s.scheduled_at?.slice(0,16)||'');}}
                    style={{...S.btn('#4ecdc4',true),padding:'3px 8px',fontSize:11,flexShrink:0}}>✎</button>
                  <button onClick={()=>del(s.id)}
                    style={{...S.btn('#ff6b9d',true),padding:'3px 8px',fontSize:11,flexShrink:0}}>✕</button>
                </div>
              )}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

// ── Changelog Modal ───────────────────────────────────────────────────────────
function ChangelogModal({ user, toast, onClose, onRead }) {
  const isAdmin = user?.role === 'admin';
  const [entries,    setEntries]    = useState([]);
  const [form,       setForm]       = useState({ version:'', title:'', body:'' });
  const [busy,       setBusy]       = useState(false);
  const [boardItems, setBoardItems] = useState([]);
  const [toDelete,   setToDelete]   = useState(new Set()); // ids to delete on submit
  const isMobile = window.innerWidth < 768;

  useEffect(()=>{
    api('/dashboard/changelog').then(setEntries).catch(()=>{});
    api('/dashboard/changelog/read',{method:'POST'}).then(()=>{ if(onRead) onRead(); }).catch(()=>{});
    if (user?.role==='admin') {
      api('/dashboard/board').then(d=>setBoardItems(d.items||[])).catch(()=>{});
    }
  },[]);

  const toggleDelete = id => setToDelete(p => {
    const n = new Set(p);
    n.has(id) ? n.delete(id) : n.add(id);
    return n;
  });

  const add = async () => {
    if (!form.version.trim() || !form.title.trim()) { toast('Stichwort und Titel erforderlich','error'); return; }
    setBusy(true);
    try {
      const r = await api('/dashboard/changelog',{body:{
        ...form,
        build_time: typeof __BUILD_TIME__ !== 'undefined' ? __BUILD_TIME__ : null,
      }});
      setEntries(p=>[r,...p]);
      // Markierte Board-Items löschen
      if (toDelete.size > 0) {
        await Promise.all([...toDelete].map(id => api(`/dashboard/board/${id}`,{method:'DELETE'}).catch(()=>{})));
        setBoardItems(p => p.filter(i => !toDelete.has(i.id)));
        setToDelete(new Set());
      }
      setForm({version:'',title:'',body:''});
      toast('Eintrag hinzugefügt ✓');
    } catch(e){ toast(e.message,'error'); }
    setBusy(false);
  };

  const del = async id => {
    try { await api(`/dashboard/changelog/${id}`,{method:'DELETE'}); setEntries(p=>p.filter(e=>e.id!==id)); }
    catch(e){ toast(e.message,'error'); }
  };

  const fmtDate = s => { if (!s) return ''; const d = new Date(s.replace(' ','T')); return isNaN(d)?'':d.toLocaleString('de-DE',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'}); };

  const roadmap = boardItems.filter(i=>i.type==='roadmap');
  const wishes  = boardItems.filter(i=>i.type==='wish');

  return (
    <div style={{position:'fixed',inset:0,background:'rgba(0,0,0,0.85)',zIndex:7000,
      display:'flex',alignItems:isMobile?'flex-end':'center',justifyContent:'center',padding:isMobile?0:24}}
      onClick={e=>e.target===e.currentTarget&&onClose()}>
      <div style={{background:'#1a1a1e',borderRadius:isMobile?'16px 16px 0 0':16,
        width:'100%',maxWidth: isAdmin ? 860 : 540,
        border:'1px solid rgba(255,255,255,0.12)',display:'flex',flexDirection:'column',
        maxHeight:isMobile?'92vh':'84vh',overflow:'hidden'}}>

        {/* Header */}
        <div style={{padding:'16px 20px',borderBottom:'1px solid rgba(255,255,255,0.08)',flexShrink:0,
          display:'flex',justifyContent:'space-between',alignItems:'center',position:'relative'}}>
          {isMobile&&<div style={{width:36,height:4,background:'rgba(255,255,255,0.15)',borderRadius:2,
            position:'absolute',top:8,left:'50%',transform:'translateX(-50%)'}}/>}
          <div style={{color:'#fff',fontFamily:"'Space Mono',monospace",fontSize:14,fontWeight:700}}>📝 Changelog</div>
          <button onClick={onClose} style={{background:'transparent',border:'none',color:'rgba(255,255,255,0.4)',cursor:'pointer',fontSize:18}}>✕</button>
        </div>

        <div style={{display:'flex',flex:1,overflow:'hidden',flexDirection:isMobile?'column':'row'}}>

          {/* Linke Spalte: Formular + Einträge */}
          <div style={{flex:1,overflowY:'auto',padding:'0 20px',minWidth:0}}>
            {/* Admin-Formular */}
            {isAdmin && (
              <div style={{padding:'14px 0',borderBottom:'1px solid rgba(255,255,255,0.08)'}}>
                <div style={{...S.head,marginBottom:8}}>NEUER EINTRAG</div>
                <div style={{display:'grid',gridTemplateColumns:'1fr 2fr',gap:8,marginBottom:8}}>
                  <input value={form.version} onChange={e=>setForm(p=>({...p,version:e.target.value}))}
                    placeholder="Stichwort" style={{...S.inp,fontSize:12}}/>
                  <input value={form.title} onChange={e=>setForm(p=>({...p,title:e.target.value}))}
                    placeholder="Kurztitel" style={{...S.inp,fontSize:12}}/>
                </div>
                <textarea value={form.body} onChange={e=>setForm(p=>({...p,body:e.target.value}))}
                  placeholder="Beschreibung…"
                  rows={3} style={{...S.inp,resize:'vertical',fontSize:12,marginBottom:8,width:'100%',boxSizing:'border-box'}}/>
                {toDelete.size>0 && (
                  <div style={{background:'rgba(255,107,157,0.07)',border:'1px solid rgba(255,107,157,0.2)',
                    borderRadius:6,padding:'6px 10px',marginBottom:8,color:'rgba(255,107,157,0.7)',
                    fontFamily:'monospace',fontSize:10}}>
                    🗑 {toDelete.size} ToDo{toDelete.size!==1?'s':''} wird beim Hinzufügen gelöscht
                  </div>
                )}
                <button onClick={add} disabled={busy}
                  style={{...S.btn('#4ecdc4'),width:'100%',textAlign:'center',padding:'9px 0',opacity:busy?0.5:1}}>
                  {busy?'…':'+ Eintrag hinzufügen'}
                </button>
              </div>
            )}

            {/* Einträge */}
            <div style={{padding:'8px 0 20px'}}>
              {entries.length===0 ? (
                <div style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:11,padding:'20px 0',textAlign:'center'}}>
                  Noch keine Changelog-Einträge.
                </div>
              ) : entries.map(e=>(
                <div key={e.id} style={{padding:'14px 0',borderBottom:'1px solid rgba(255,255,255,0.05)'}}>
                  <div style={{display:'flex',alignItems:'flex-start',justifyContent:'space-between',gap:10,marginBottom:4}}>
                    <div style={{display:'flex',alignItems:'center',gap:8,flexWrap:'wrap'}}>
                      <span style={{
                        background:'rgba(78,205,196,0.12)',border:'1px solid rgba(78,205,196,0.25)',
                        borderRadius:5,padding:'2px 8px',color:'#4ecdc4',
                        fontFamily:'monospace',fontSize:11,fontWeight:700,flexShrink:0
                      }}>{e.version}</span>
                      <span style={{color:'#fff',fontFamily:'monospace',fontSize:13,fontWeight:700}}>{e.title}</span>
                    </div>
                    <div style={{display:'flex',alignItems:'center',gap:6,flexShrink:0}}>
                      <span style={{color:'rgba(255,255,255,0.2)',fontFamily:'monospace',fontSize:9}}>
                        {e.build_time
                          ? (isNaN(Number(e.build_time))
                              ? e.build_time  // altes Format: bereits lesbarer String
                              : new Date(Number(e.build_time)*1000).toLocaleString('de-DE',{timeZone:'Europe/Berlin',day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'}))
                          : fmtDate(e.created_at)}
                      </span>
                      {isAdmin && (
                        <button onClick={()=>del(e.id)}
                          style={{background:'transparent',border:'none',cursor:'pointer',
                            color:'rgba(255,107,157,0.5)',fontSize:13,padding:'0 2px',lineHeight:1}}>✕</button>
                      )}
                    </div>
                  </div>
                  {e.body && (
                    <div style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:11,
                      lineHeight:1.7,whiteSpace:'pre-wrap',paddingLeft:4}}>
                      {e.body}
                    </div>
                  )}
                </div>
              ))}
            </div>
          </div>

          {/* Rechte Spalte: Board-Items zum Abhaken (nur Admin, nur wenn vorhanden) */}
          {isAdmin && boardItems.length>0 && (
            <div style={{width: isMobile?'100%':240,flexShrink:0,borderLeft:isMobile?'none':'1px solid rgba(255,255,255,0.07)',
              borderTop:isMobile?'1px solid rgba(255,255,255,0.07)':'none',
              overflowY:'auto',padding:'14px 16px',background:'rgba(0,0,0,0.15)'}}>
              <div style={{...S.head,marginBottom:8}}>TODOS MITERLEDIGEN</div>
              <p style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:9,marginBottom:12,lineHeight:1.6}}>
                Markierte Einträge werden beim Hinzufügen des Changelogs gelöscht.
              </p>

              {roadmap.length>0 && (
                <>
                  <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1,marginBottom:6}}>🗺 ROADMAP</div>
                  {roadmap.map(item=>(
                    <label key={item.id} style={{display:'flex',alignItems:'flex-start',gap:8,
                      padding:'6px 0',borderBottom:'1px solid rgba(255,255,255,0.04)',cursor:'pointer'}}>
                      <input type="checkbox" checked={toDelete.has(item.id)} onChange={()=>toggleDelete(item.id)}
                        style={{marginTop:2,accentColor:'#4ecdc4',flexShrink:0}}/>
                      <div>
                        <div style={{color: toDelete.has(item.id)?'rgba(255,255,255,0.35)':'rgba(255,255,255,0.7)',
                          fontFamily:'monospace',fontSize:11,
                          textDecoration: toDelete.has(item.id)?'line-through':'none'}}>
                          {item.title}
                        </div>
                        {item.description&&<div style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:9}}>{item.description}</div>}
                      </div>
                    </label>
                  ))}
                </>
              )}

              {wishes.length>0 && (
                <div style={{marginTop: roadmap.length>0?12:0}}>
                  <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1,marginBottom:6}}>💡 WÜNSCHE</div>
                  {wishes.map(item=>(
                    <label key={item.id} style={{display:'flex',alignItems:'flex-start',gap:8,
                      padding:'6px 0',borderBottom:'1px solid rgba(255,255,255,0.04)',cursor:'pointer'}}>
                      <input type="checkbox" checked={toDelete.has(item.id)} onChange={()=>toggleDelete(item.id)}
                        style={{marginTop:2,accentColor:'#ffe66d',flexShrink:0}}/>
                      <div>
                        <div style={{color: toDelete.has(item.id)?'rgba(255,255,255,0.35)':'rgba(255,255,255,0.7)',
                          fontFamily:'monospace',fontSize:11,
                          textDecoration: toDelete.has(item.id)?'line-through':'none'}}>
                          {item.title}
                        </div>
                        {item.description&&<div style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:9}}>{item.description}</div>}
                      </div>
                    </label>
                  ))}
                </div>
              )}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}
function formatIcalDate(dt) {
  if (!dt) return '';
  try {
    const hasTime = dt.includes('T');
    const isUTC   = dt.endsWith('Z');
    const y = dt.slice(0,4), mo = dt.slice(4,6), d = dt.slice(6,8);
    if (hasTime) {
      const h = dt.slice(9,11), mi = dt.slice(11,13), s = dt.slice(13,15)||'00';
      const iso = `${y}-${mo}-${d}T${h}:${mi}:${s}${isUTC?'Z':''}`;
      const date = new Date(iso);
      if (isNaN(date)) return dt;
      return date.toLocaleString('de-DE', {
        timeZone:'Europe/Berlin',
        day:'2-digit', month:'2-digit', year:'numeric',
        hour:'2-digit', minute:'2-digit'
      }) + ' Uhr';
    } else {
      const date = new Date(`${y}-${mo}-${d}T12:00:00Z`);
      if (isNaN(date)) return dt;
      return date.toLocaleDateString('de-DE', {
        timeZone:'Europe/Berlin', day:'2-digit', month:'2-digit', year:'numeric'
      });
    }
  } catch { return dt.slice(0,10); }
}

function SearchResultItem({ item, idx, activeIdx, onNavigate, onHover }) {
  const isActive = idx === activeIdx;
  let icon = '❓', primary = '', secondary = '';
  if (item.type==='tool')     { icon=item.icon||'🛠'; primary=item.label; secondary=item.sub||'Navigation'; }
  if (item.type==='devtool')  { icon=item.icon||'🔧'; primary=item.label; secondary=item.sub||'Dev-Tools'; }
  if (item.type==='settings') { icon=item.icon||'⚙️'; primary=item.label; secondary=item.sub||'Einstellungen'; }
  if (item.type==='folder')   { icon=item.icon||'📁'; primary=item.name;  secondary='Linkliste – Ordner'; }
  if (item.type==='link') {
    const hasCustom = item.icon && item.icon !== '🔗';
    icon = hasCustom ? item.icon : '🔗';
    primary = item.title; secondary = item.url;
  }
  if (item.type==='snippet')     { icon='📄'; primary=item.title; secondary=`Code · ${item.language||''}`; }
  if (item.type==='calc')        { icon='🖨'; primary=item.name;  secondary='3D-Archiv'; }
  if (item.type==='order')       { icon='📦'; primary=item.name;  secondary=`Bestellung · ${item.status||''}`; }
  if (item.type==='todo')           { icon=item.done?'✅':'☐'; primary=item.text; secondary='ToDo'; }
  if (item.type==='note')           { icon='📝'; primary='Notiz'; secondary=item.preview||''; }
  if (item.type==='board')          { icon=item.btype==='roadmap'?'🗺':'⭐'; primary=item.title; secondary=`${item.btype==='roadmap'?'Roadmap':'Wünsche'}${item.description?' · '+item.description.slice(0,40):''}`; }
  if (item.type==='changelog')      { icon='📋'; primary=`${item.version} – ${item.title}`; secondary='Changelog'; }
  if (item.type==='file')           { icon='📄'; primary=item.originalname; secondary=`Datei · ${item.size?(item.size/1024/1024).toFixed(1)+' MB':''}`; }
  if (item.type==='file_folder')    { icon='📁'; primary=item.name; secondary='Datei-Ordner'; }
  if (item.type==='push')           { icon='🔔'; primary=item.message; secondary=`Push · ${(item.scheduled_at||'').slice(0,16)}`; }
  if (item.type==='calendar_feed')  { icon='📅'; primary=item.name; secondary='Kalender-Abo'; }
  if (item.type==='calendar_event') { icon='📆'; primary=item.summary; secondary=`${item.feed_name||'Kalender'}${item.start_dt?' · '+formatIcalDate(item.start_dt):''}`; }
  if (item.type==='qr')             { icon='◻'; primary=item.label||item.url; secondary=`QR-Code · ${item.url}`; }
  return (
    <div onClick={()=>onNavigate(item)} onMouseEnter={()=>onHover(idx)}
      style={{display:'flex',alignItems:'center',gap:10,padding:'9px 14px',cursor:'pointer',
        background:isActive?'rgba(78,205,196,0.1)':'transparent',
        borderBottom:'1px solid rgba(255,255,255,0.04)'}}>
      <span style={{fontSize:16,flexShrink:0,width:24,textAlign:'center'}}>{icon}</span>
      <div style={{flex:1,minWidth:0}}>
        <div style={{color:isActive?'#4ecdc4':'#fff',fontFamily:'monospace',fontSize:13,fontWeight:500,
          overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{primary}</div>
        <div style={{color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:10,
          overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{secondary}</div>
      </div>
      {item.type==='link' && <span style={{color:'rgba(255,255,255,0.2)',fontSize:10,flexShrink:0}}>↗</span>}
      {(item.type==='tool'||item.type==='devtool'||item.type==='settings'||item.type==='folder'||item.type==='snippet'||item.type==='calc'||item.type==='order') && <span style={{color:'rgba(255,255,255,0.2)',fontSize:10,flexShrink:0}}>→</span>}
    </div>
  );
}

// ── Globale Suche ─────────────────────────────────────────────────────────────
// Statischer Such-Index – mit Keywords für umfassende Suche
const SEARCH_TOOL_INDEX = [
  // ── Haupt-Tools ──────────────────────────────────────────────────────────
  {type:'tool',label:'Dashboard',icon:'🏠',sub:'',id:'dashboard'},
  {type:'tool',label:'Bestellungen',icon:'📦',sub:'3D-Druck',id:'bestellungen',keywords:['order','auftrag','3d druck']},
  {type:'tool',label:'Kalkulator3D',icon:'🖨',sub:'3D-Druck',id:'kalkulator3d',keywords:['kalkulator','rechner','3d archiv','archiv','berechnen','filament','druckkosten']},
  {type:'tool',label:'3D-Archiv',icon:'🖨',sub:'3D-Druck',id:'kalkulator3d',keywords:['archiv','druck']},
  {type:'tool',label:'Dateien',icon:'📁',sub:'Werkzeuge',id:'dateien',keywords:['datei','file','upload','dokument','ordner','speicher']},
  {type:'tool',label:'Nachrichten',icon:'💬',sub:'Werkzeuge',id:'nachrichten',keywords:['chat','nachricht','message','kommunikation','e2e','verschlüsselt']},
  {type:'tool',label:'Linkliste',icon:'🔗',sub:'Werkzeuge',id:'linkliste',keywords:['link','url','lesezeichen','bookmark','webseite']},
  {type:'tool',label:'Code-Schnipsel',icon:'📄',sub:'Werkzeuge',id:'codeschnipsel',keywords:['code','snippet','skript','programmierung','source']},
  {type:'tool',label:'CAD-Skizzen',icon:'📐',sub:'Werkzeuge',id:'skizze',keywords:['cad','skizze','zeichnung','sketch']},
  {type:'tool',label:'Dev-Tools',icon:'🔧',sub:'Werkzeuge',id:'devtools',keywords:['entwickler','developer','tools','werkzeuge']},
  {type:'tool',label:'Paywall-Killer',icon:'🔓',sub:'Werkzeuge',id:'paywallkiller',keywords:['paywall','archiv','archive','artikel','zeitung','bild','waz','heise','spiegel','zeit','bypass','umgehen','lesen','gesperrt','bezahlschranke']},
  {type:'tool',label:'Media',icon:'🎬',sub:'Freizeit',id:'media',keywords:['kino','film','movie','streaming','demnächst','favoriten','cinema']},
  {type:'tool',label:'Kino – Aktuell',icon:'🎬',sub:'Media',id:'media',keywords:['kino','kinocharts','charts','laufend','now playing']},
  {type:'tool',label:'Kino – Demnächst',icon:'🗓',sub:'Media',id:'media',keywords:['demnächst','neustart','upcoming','vorschau','kino']},
  {type:'tool',label:'Kino – Favoriten',icon:'❤',sub:'Media',id:'media',keywords:['favoriten','film favoriten','gemerkte filme','watchlist']},
  // ── Dashboard-Widgets ──────────────────────────────────────────────────
  {type:'tool',label:'Schnellzugriff',icon:'⚡',sub:'Dashboard',id:'dashboard',keywords:['quick','links','favoriten','shortcuts']},
  {type:'tool',label:'Kalender',icon:'📅',sub:'Dashboard',id:'dashboard',keywords:['kalender','termine','events','ical','abo']},
  {type:'tool',label:'ToDo',icon:'✅',sub:'Dashboard',id:'dashboard',keywords:['todo','aufgabe','task','erledigt','checkliste']},
  {type:'tool',label:'Notizen',icon:'📝',sub:'Dashboard',id:'dashboard',keywords:['notiz','note','memo','text']},
  {type:'tool',label:'Push-Erinnerungen',icon:'🔔',sub:'Dashboard',id:'dashboard',keywords:['push','erinnerung','benachrichtigung','reminder','alarm']},
  {type:'tool',label:'Board / Roadmap',icon:'🗺',sub:'Dashboard',id:'dashboard',keywords:['roadmap','wünsche','board','ideen','planung','feature']},
  {type:'tool',label:'Changelog',icon:'📋',sub:'Dashboard',id:'dashboard',keywords:['changelog','versionen','updates','änderungen']},
  // ── Einstellungen ────────────────────────────────────────────────────────
  {type:'settings',label:'Profil',icon:'👤',sub:'Einstellungen',section:'profil',keywords:['avatar','benutzername','username','account','konto']},
  {type:'settings',label:'Passwort ändern',icon:'🔑',sub:'Einstellungen → Profil',section:'profil',keywords:['passwort','password','sicherheit','ändern']},
  {type:'settings',label:'Benutzername ändern',icon:'✏️',sub:'Einstellungen → Profil',section:'profil',keywords:['benutzername','username','name']},
  {type:'settings',label:'Pushover',icon:'🔔',sub:'Einstellungen → Profil',section:'profil',keywords:['pushover','push','api key','token','benachrichtigung','notification']},
  {type:'settings',label:'Account löschen',icon:'⚠️',sub:'Einstellungen → Profil',section:'profil',keywords:['löschen','delete','entfernen']},
  {type:'settings',label:'Sicherheit',icon:'🔒',sub:'Einstellungen',section:'sicherheit',keywords:['login','sperrung','schutz','lockout','versuche']},
  {type:'settings',label:'Login-Schutz',icon:'🔒',sub:'Einstellungen → Sicherheit',section:'sicherheit',keywords:['login schutz','lockout','sperrung','fehlversuche']},
  {type:'settings',label:'Dashboard-Einstellungen',icon:'🏠',sub:'Einstellungen',section:'dashboard',keywords:['schnellzugriff','kalender','ical','widgets']},
  {type:'settings',label:'iCal Kalender hinzufügen',icon:'📅',sub:'Einstellungen → Dashboard',section:'dashboard',keywords:['ical','kalender','abo','feed','url']},
  {type:'settings',label:'Benutzer verwalten',icon:'👥',sub:'Einstellungen (Admin)',section:'benutzer',keywords:['user','benutzer','admin','anlegen','erstellen','löschen']},
  {type:'settings',label:'Datei-Limits',icon:'📁',sub:'Einstellungen (Admin)',section:'benutzer',keywords:['datei limit','speicher','quota','upload limit']},
  {type:'settings',label:'Backup',icon:'💾',sub:'Einstellungen (Admin)',section:'backup',keywords:['backup','sicherung','wiederherstellen','export','import','datenbank']},
  // ── Crontab ─────────────────────────────────────────────────────────────
  {type:'devtool',label:'Crontab',icon:'⏱',sub:'Dev-Tools',tool:'cron',keywords:['cron','cronjob','schedule','zeitplan','scheduler','interval','task','job','automatisch','wiederkehrend']},
  {type:'devtool',label:'Crontab erklären',icon:'⏱',sub:'Crontab',tool:'cron',keywords:['erklären','explain','verstehen','bedeutung','syntax','auslesen']},
  {type:'devtool',label:'Crontab erstellen',icon:'⏱',sub:'Crontab',tool:'cron',keywords:['erstellen','generator','bauen','konfigurieren','jede minute','täglich','wöchentlich']},
  // ── Elektro ─────────────────────────────────────────────────────────────
  {type:'devtool',label:'Elektro-Rechner',icon:'⚡',sub:'Dev-Tools',tool:'elektro',keywords:['elektro','elektrizität','elektronik','schaltkreis','physik']},
  {type:'devtool',label:'Ohmsches Gesetz U=R·I',icon:'⚡',sub:'Elektro',tool:'elektro',keywords:['ohm','ohmsches gesetz','spannung','strom','widerstand','volt','ampere','resistance','voltage','current','u=ri','u=r*i']},
  {type:'devtool',label:'Leistung P=U·I',icon:'⚡',sub:'Elektro',tool:'elektro',keywords:['leistung','watt','kilowatt','power','p=ui','p=u*i','energieverbrauch','verbrauch']},
  {type:'devtool',label:'Stromkosten berechnen',icon:'⚡',sub:'Elektro',tool:'elektro',keywords:['stromkosten','kosten','preis','kwh','kilowattstunde','jahreskosten','monatlich','preisrechner']},
  {type:'devtool',label:'Widerstände Serie & Parallel',icon:'⚡',sub:'Elektro',tool:'elektro',keywords:['widerstand','serie','parallel','reihenschaltung','parallelschaltung','gesamtwiderstand']},
  {type:'devtool',label:'Wirkungsgrad',icon:'⚡',sub:'Elektro',tool:'elektro',keywords:['wirkungsgrad','effizienz','efficiency','verluste','nutzleistung','eta']},
  // ── JSON ──────────────────────────────────────────────────────────────
  {type:'devtool',label:'JSON Viewer / Builder',icon:'{}',sub:'Dev-Tools',tool:'json',keywords:['json','javascript object','daten','struktur','objekt','array','xml','konvertieren','formatieren','prettify','minify','validieren']},
  // ── Regex ─────────────────────────────────────────────────────────────
  {type:'devtool',label:'Regex Builder',icon:'🔍',sub:'Dev-Tools',tool:'regex',keywords:['regex','regexp','regulärer ausdruck','pattern','muster','match','suchen','validierung','e-mail regex','plz regex','telefon regex','url regex','ip regex','datum regex','zahlen regex']},
  // ── Text Diff ────────────────────────────────────────────────────────
  {type:'devtool',label:'Text Diff',icon:'±',sub:'Dev-Tools',tool:'diff',keywords:['diff','vergleich','unterschied','unterschiede','änderungen','delta','merge','texte vergleichen','versions vergleich']},
  // ── Netzwerk ────────────────────────────────────────────────────────
  {type:'devtool',label:'Netzwerk-Rechner',icon:'🌐',sub:'Dev-Tools',tool:'netzwerk',keywords:['netzwerk','network','internet','lan','wan','router']},
  {type:'devtool',label:'Geschwindigkeit: Mbit/s ↔ MB/s',icon:'🌐',sub:'Netzwerk',tool:'netzwerk',keywords:['geschwindigkeit','speed','mbit','gbit','megabit','gigabit','mb/s','gb/s','mbit/s','gbit/s','megabyte','gigabyte','bandbreite','bandwidth','download','upload','transfer','übertragung','internetgeschwindigkeit','downloadzeit','dauer']},
  {type:'devtool',label:'IP / Subnetz Rechner',icon:'🌐',sub:'Netzwerk',tool:'netzwerk',keywords:['ip','ipv4','ipv6','subnet','subnetz','subnetzmaske','netzmaske','gateway','cidr','broadcast','netzadresse','hostbereich','slash notation']},
  {type:'devtool',label:'CIDR Tabelle',icon:'🌐',sub:'Netzwerk',tool:'netzwerk',keywords:['cidr','prefix','klasse a','klasse b','klasse c','hosts','adressen','/24','/16','/8']},
  // ── Datetime ─────────────────────────────────────────────────────────
  {type:'devtool',label:'Datetime / Zeitrechner',icon:'📅',sub:'Dev-Tools',tool:'datetime',keywords:['datum','zeit','time','date','rechner','kalender']},
  {type:'devtool',label:'Unix Timestamp ↔ Datum',icon:'📅',sub:'Datetime',tool:'datetime',keywords:['unix','timestamp','epoch','sekunden','millisekunden','umrechnen','konvertieren','unixzeit']},
  {type:'devtool',label:'Moseszeit Umrechner',icon:'📅',sub:'Datetime',tool:'datetime',keywords:['moses','moseszeit','moses time','proprietär','spezial']},
  {type:'devtool',label:'Geburtstagsrechner',icon:'🎂',sub:'Datetime',tool:'datetime',keywords:['geburtstag','alter','geburtsdatum','jahre','monate','tage','birthday','age','wie alt']},
  {type:'devtool',label:'Zeitdifferenz berechnen',icon:'📅',sub:'Datetime',tool:'datetime',keywords:['zeitdifferenz','differenz','abstand','dauer','zeitraum','zwischen','tage berechnen','stunden berechnen']},
  {type:'devtool',label:'Datum / Kalenderwoche',icon:'📅',sub:'Datetime',tool:'datetime',keywords:['kalenderwoche','kw','wochentag','weekday','woche']},
  // ── Farben ──────────────────────────────────────────────────────────
  {type:'devtool',label:'Farb-Rechner',icon:'🎨',sub:'Dev-Tools',tool:'farben',keywords:['farbe','color','colour','design','palette']},
  {type:'devtool',label:'HEX ↔ RGB ↔ HSL ↔ CMYK',icon:'🎨',sub:'Farben',tool:'farben',keywords:['hex','rgb','hsl','hsv','cmyk','oklch','rgba','hsla','farbraum','konvertieren','farbwert','farbcode','#ffffff','css farbe']},
  {type:'devtool',label:'Color Picker',icon:'🎨',sub:'Farben',tool:'farben',keywords:['color picker','farbwähler','farbauswahl','pipette','picker']},
  {type:'devtool',label:'WCAG Kontrast',icon:'🎨',sub:'Farben',tool:'farben',keywords:['wcag','kontrast','accessibility','barrierefrei','contrast ratio','lesbarkeit','aa','aaa']},
  // ── Key-Gen ─────────────────────────────────────────────────────────
  {type:'devtool',label:'Key-Generator',icon:'🔐',sub:'Dev-Tools',tool:'keygen',keywords:['key','schlüssel','geheim','secret','generator','passwort','zufällig','sicher','random']},
  {type:'devtool',label:'Laravel APP_KEY',icon:'🔐',sub:'Key-Gen',tool:'keygen',keywords:['laravel','app_key','php','framework','base64:']},
  {type:'devtool',label:'JWT Secret HS256/HS512',icon:'🔐',sub:'Key-Gen',tool:'keygen',keywords:['jwt','json web token','hs256','hs512','bearer','auth token','authentication']},
  {type:'devtool',label:'AES-256 / AES-128 Key',icon:'🔐',sub:'Key-Gen',tool:'keygen',keywords:['aes','aes256','aes128','verschlüsselung','encryption','symmetrisch','cipher']},
  {type:'devtool',label:'UUID v4',icon:'🔐',sub:'Key-Gen',tool:'keygen',keywords:['uuid','uid','guid','identifier','eindeutig','unique id']},
  {type:'devtool',label:'NanoID',icon:'🔐',sub:'Key-Gen',tool:'keygen',keywords:['nanoid','nano','kurz','compact','id']},
  {type:'devtool',label:'Passwort Generator',icon:'🔐',sub:'Key-Gen',tool:'keygen',keywords:['passwort','password','sicher','stark','zufällig','sonderzeichen']},
  {type:'devtool',label:'Docker / DB Passwort',icon:'🔐',sub:'Key-Gen',tool:'keygen',keywords:['docker','mysql','postgres','mariadb','db passwort','datenbank passwort','compose']},
  {type:'devtool',label:'Hex / Base64 Token',icon:'🔐',sub:'Key-Gen',tool:'keygen',keywords:['hex','base64','base64url','token','random bytes','crypto']},
  {type:'devtool',label:'NEXTAUTH_SECRET',icon:'🔐',sub:'Key-Gen',tool:'keygen',keywords:['nextauth','next auth','nextjs','next.js','auth.js']},
  {type:'devtool',label:'VAPID Key',icon:'🔐',sub:'Key-Gen',tool:'keygen',keywords:['vapid','web push','push notification','service worker']},
  {type:'devtool',label:'OAuth / Webhook Secret',icon:'🔐',sub:'Key-Gen',tool:'keygen',keywords:['oauth','webhook','signing secret','hmac','client secret']},
  {type:'devtool',label:'Session Secret',icon:'🔐',sub:'Key-Gen',tool:'keygen',keywords:['session','express session','cookie secret','session_secret']},
  {type:'devtool',label:'MYSQL / POSTGRES Passwort',icon:'🔐',sub:'Key-Gen',tool:'keygen',keywords:['mysql','mysql_root_password','postgres','postgresql','postgres_password','mariadb']},
  // ── QR-Code ────────────────────────────────────────────────────────
  {type:'devtool',label:'QR-Code Generator',icon:'◻',sub:'Dev-Tools',tool:'qrcode',keywords:['qr','qr code','qrcode','barcode','scan','scannen','link teilen','url teilen']},
];

function GlobalSearch({ setActive, setDevToolsNav, mobile }) {
  const [query,   setQuery]   = useState('');
  const [results, setResults] = useState(null);
  const [active,  setActiveR] = useState(-1); // keyboard nav
  const [loading, setLoading] = useState(false);
  const inputRef = useRef(null);
  const listRef  = useRef(null);
  const debounce = useRef(null);

  const close = () => { setQuery(''); setResults(null); setActiveR(-1); };

  // Static matches
  function matchStatic(q) {
    const lq = q.toLowerCase();
    return SEARCH_TOOL_INDEX.filter(t =>
      t.label.toLowerCase().includes(lq) ||
      t.sub.toLowerCase().includes(lq) ||
      (t.tool||t.id||'').toLowerCase().includes(lq) ||
      (t.keywords||[]).some(k => k.toLowerCase().includes(lq))
    ).slice(0, 8);
  }

  useEffect(() => {
    if (!query.trim()) { setResults(null); setActiveR(-1); return; }
    clearTimeout(debounce.current);
    debounce.current = setTimeout(async () => {
      const staticMatches = matchStatic(query);
      if (query.trim().length < 2) { setResults({static:staticMatches,links:[],snippets:[],calculations:[],orders:[]}); return; }
      setLoading(true);
      try {
        const d = await api(`/search?q=${encodeURIComponent(query.trim())}`);
        setResults({ static:staticMatches, ...d });
      } catch { setResults({static:staticMatches,links:[],snippets:[],calculations:[],orders:[]}); }
      setLoading(false);
    }, 250);
  }, [query]);

  // Alle Einträge flach für Keyboard-Nav
  const allItems = results ? [
    ...results.static,
    ...(results.links||[]).map(l=>({...l,type:'link'})),
    ...(results.folders||[]).map(f=>({...f,type:'folder'})),
    ...(results.snippets||[]).map(s=>({...s,type:'snippet'})),
    ...(results.calculations||[]).map(c=>({...c,type:'calc'})),
    ...(results.orders||[]).map(o=>({...o,type:'order'})),
    ...(results.todos||[]).map(t=>({...t,type:'todo'})),
    ...(results.notes||[]).map(n=>({...n,type:'note'})),
    ...(results.board_items||[]).map(b=>({...b,type:'board'})),
    ...(results.changelog||[]).map(e=>({...e,type:'changelog'})),
    ...(results.files||[]).map(f=>({...f,type:'file'})),
    ...(results.file_folders||[]).map(f=>({...f,type:'file_folder'})),
    ...(results.push_schedules||[]).map(p=>({...p,type:'push'})),
    ...(results.calendar_feeds||[]).map(f=>({...f,type:'calendar_feed'})),
    ...(results.calendar_events||[]).map(e=>({...e,type:'calendar_event'})),
    ...(results.qr_codes||[]).map(q=>({...q,type:'qr'})),
  ] : [];

  const navigate = (item) => {
    close();
    if (item.type === 'tool')        { setActive(item.id); }
    else if (item.type === 'devtool')  { setActive('devtools'); setDevToolsNav({tool: item.tool, ts: Date.now()}); }
    else if (item.type === 'settings') { setActive('admin');    setDevToolsNav({section: item.section, ts: Date.now()}); }
    else if (item.type === 'link')     { window.open(item.url, '_blank', 'noopener'); }
    else if (item.type === 'folder')   { setActive('linkliste'); }
    else if (item.type === 'snippet')  { setActive('codeschnipsel'); }
    else if (item.type === 'calc')     { setActive('kalkulator3d'); }
    else if (item.type === 'order')    { setActive('bestellungen'); }
    else if (item.type === 'todo')           { setActive('dashboard'); }
    else if (item.type === 'note')           { setActive('dashboard'); }
    else if (item.type === 'board')          { setActive('dashboard'); }
    else if (item.type === 'changelog')      { setActive('dashboard'); }
    else if (item.type === 'file' || item.type === 'file_folder') { setActive('dateien'); }
    else if (item.type === 'push')           { setActive('dashboard'); }
    else if (item.type === 'calendar_feed' || item.type === 'calendar_event') { setActive('dashboard'); }
    else if (item.type === 'qr')             { setActive('devtools'); setDevToolsNav({tool:'qrcode', ts: Date.now()}); }
  };

  const onKey = e => {
    if (!results) return;
    if (e.key === 'ArrowDown') { e.preventDefault(); setActiveR(p => Math.min(p+1, allItems.length-1)); }
    else if (e.key === 'ArrowUp') { e.preventDefault(); setActiveR(p => Math.max(p-1, -1)); }
    else if (e.key === 'Enter' && active >= 0 && allItems[active]) { navigate(allItems[active]); }
    else if (e.key === 'Escape') { close(); inputRef.current?.blur(); }
  };



  const hasResults = allItems.length > 0;
  const showDropdown = !!query.trim() && results !== null;

  return (
    <div style={{position:'relative',marginBottom:16}}>
      <div style={{position:'relative'}}>
        <span style={{position:'absolute',left:12,top:'50%',transform:'translateY(-50%)',
          color:'rgba(255,255,255,0.4)',fontSize:16,pointerEvents:'none'}}>⌕</span>
        <input
          ref={inputRef}
          value={query}
          onChange={e=>setQuery(e.target.value)}
          onKeyDown={onKey}
          placeholder="Suchen in der App… Tools, Links, Code, Bestellungen"
          style={{...S.inp, paddingLeft:38, paddingRight: query?36:14, width:'100%', boxSizing:'border-box', fontSize:14}}
        />
        {!!query && (
          <button onClick={close} style={{position:'absolute',right:10,top:'50%',transform:'translateY(-50%)',
            background:'transparent',border:'none',color:'rgba(255,255,255,0.4)',cursor:'pointer',fontSize:18,padding:'0 2px'}}>✕</button>
        )}
      </div>

      {/* Dropdown */}
      {showDropdown && (
        <div ref={listRef} style={{
          position:'absolute',top:'calc(100% + 6px)',left:0,right:0,zIndex:9000,
          background:'#1a1a1e',borderRadius:12,border:'1px solid rgba(255,255,255,0.12)',
          boxShadow:'0 8px 32px rgba(0,0,0,0.6)',maxHeight:'60vh',overflowY:'auto',
        }}>
          {loading && (
            <div style={{padding:'12px 14px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:11}}>Suche…</div>
          )}
          {!loading && !hasResults && (
            <div style={{padding:'16px 14px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:12,textAlign:'center'}}>
              Keine Treffer für „{query}"
            </div>
          )}
          {!loading && hasResults && (
            <>
              {results.static?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>NAVIGATION</div>
                  {results.static.map((item,i)=><SearchResultItem key={`s${i}`} item={item} idx={i} activeIdx={active} onNavigate={navigate} onHover={setActiveR}/>)}
                </div>
              )}
              {results.links?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>LINKS</div>
                  {results.links.map((item,i)=><SearchResultItem key={`l${i}`} item={{...item,type:'link'}} idx={results.static.length+i} activeIdx={active} onNavigate={navigate} onHover={setActiveR}/>)}
                </div>
              )}
              {results.folders?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>LINK-ORDNER</div>
                  {results.folders.map((item,i)=>{
                    const off=results.static.length+(results.links||[]).length+(results.folders||[]).length;
                    return <SearchResultItem key={`f${i}`} item={{...item,type:'folder'}} idx={off+i} activeIdx={active} onNavigate={navigate} onHover={setActiveR}/>;
                  })}
                </div>
              )}
              {results.snippets?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>CODE-SCHNIPSEL</div>
                  {results.snippets.map((item,i)=>{
                    const off=results.static.length+(results.links||[]).length;
                    return <SearchResultItem key={`sn${i}`} item={{...item,type:'snippet'}} idx={off+i} activeIdx={active} onNavigate={navigate} onHover={setActiveR}/>;
                  })}
                </div>
              )}
              {results.calculations?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>3D-ARCHIV</div>
                  {results.calculations.map((item,i)=>{
                    const off=results.static.length+(results.links||[]).length+(results.folders||[]).length+(results.snippets||[]).length;
                    return <SearchResultItem key={`c${i}`} item={{...item,type:'calc'}} idx={off+i} activeIdx={active} onNavigate={navigate} onHover={setActiveR}/>;
                  })}
                </div>
              )}
              {results.orders?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>BESTELLUNGEN</div>
                  {results.orders.map((item,i)=>{
                    const off=results.static.length+(results.links||[]).length+(results.folders||[]).length+(results.snippets||[]).length+(results.calculations||[]).length;
                    return <SearchResultItem key={`o${i}`} item={{...item,type:'order'}} idx={off+i} activeIdx={active} onNavigate={navigate} onHover={setActiveR}/>;
                  })}
                </div>
              )}
              {results.todos?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>TODOS</div>
                  {results.todos.map((item,i)=>{
                    const off=allItems.length-((results.todos||[]).length+(results.files||[]).length+(results.file_folders||[]).length+(results.push_schedules||[]).length+(results.calendar_feeds||[]).length+(results.qr_codes||[]).length)+i;
                    return <SearchResultItem key={`t${i}`} item={{...item,type:'todo'}} idx={off} activeIdx={active} onNavigate={navigate} onHover={setActiveR}/>;
                  })}
                </div>
              )}
              {results.files?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>DATEIEN</div>
                  {results.files.map((item,i)=>(<SearchResultItem key={`fi${i}`} item={{...item,type:'file'}} idx={0} activeIdx={-1} onNavigate={navigate} onHover={()=>{}}/>))}
                </div>
              )}
              {results.file_folders?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>DATEI-ORDNER</div>
                  {results.file_folders.map((item,i)=>(<SearchResultItem key={`ff${i}`} item={{...item,type:'file_folder'}} idx={0} activeIdx={-1} onNavigate={navigate} onHover={()=>{}}/>))}
                </div>
              )}
              {results.push_schedules?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>PUSH-ERINNERUNGEN</div>
                  {results.push_schedules.map((item,i)=>(<SearchResultItem key={`p${i}`} item={{...item,type:'push'}} idx={0} activeIdx={-1} onNavigate={navigate} onHover={()=>{}}/>))}
                </div>
              )}
              {results.calendar_feeds?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>KALENDER-ABOS</div>
                  {results.calendar_feeds.map((item,i)=>(<SearchResultItem key={`cf${i}`} item={{...item,type:'calendar'}} idx={0} activeIdx={-1} onNavigate={navigate} onHover={()=>{}}/>))}
                </div>
              )}
              {results.qr_codes?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>QR-CODES</div>
                  {results.qr_codes.map((item,i)=>(<SearchResultItem key={`qr${i}`} item={{...item,type:'qr'}} idx={0} activeIdx={-1} onNavigate={navigate} onHover={()=>{}}/>))}
                </div>
              )}
              {results.notes?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>NOTIZEN</div>
                  {results.notes.map((item,i)=>(<SearchResultItem key={`n${i}`} item={{...item,type:'note'}} idx={0} activeIdx={-1} onNavigate={navigate} onHover={()=>{}}/>))}
                </div>
              )}
              {results.board_items?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>ROADMAP / WÜNSCHE</div>
                  {results.board_items.map((item,i)=>(<SearchResultItem key={`b${i}`} item={{...item,type:'board',btype:item.type}} idx={0} activeIdx={-1} onNavigate={navigate} onHover={()=>{}}/>))}
                </div>
              )}
              {results.changelog?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>CHANGELOG</div>
                  {results.changelog.map((item,i)=>(<SearchResultItem key={`cl${i}`} item={{...item,type:'changelog'}} idx={0} activeIdx={-1} onNavigate={navigate} onHover={()=>{}}/>))}
                </div>
              )}
              {results.calendar_events?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>KALENDER-EINTRÄGE</div>
                  {results.calendar_events.map((item,i)=>(<SearchResultItem key={`ce${i}`} item={{...item,type:'calendar_event'}} idx={0} activeIdx={-1} onNavigate={navigate} onHover={()=>{}}/>))}
                </div>
              )}
              {results.calendar_feeds?.length>0 && (
                <div>
                  <div style={{padding:'7px 14px 4px',color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,letterSpacing:1}}>KALENDER-ABOS</div>
                  {results.calendar_feeds.map((item,i)=>(<SearchResultItem key={`cf${i}`} item={{...item,type:'calendar_feed'}} idx={0} activeIdx={-1} onNavigate={navigate} onHover={()=>{}}/>))}
                </div>
              )}
            </>
          )}
        </div>
      )}

      {/* Backdrop zum Schließen – leicht opak damit Dashboard nicht durchscheint */}
      {showDropdown && (
        <div style={{position:'fixed',inset:0,zIndex:8999,background:'rgba(0,0,0,0.25)'}} onClick={close}/>
      )}
    </div>
  );
}


function Dashboard({ toast, mobile, setActive, setDevToolsNav, unreadMsgs=0, unreadBoard=0, unreadPromoted=0, onBoardRead, unreadChangelog=0, onChangelogRead, user }) {
  const [now, setNow] = useState(new Date());
  const [showBoard,     setShowBoard]     = useState(false);
  const [showChangelog, setShowChangelog] = useState(false);
  useEffect(() => { const t = setInterval(() => setNow(new Date()), 30000); return () => clearInterval(t); }, []);
  // Kalender-Events im Hintergrund für die Suche cachen
  useEffect(() => {
    api('/search/sync-calendar', { method:'POST', body:{} })
      .then(r => { if (r.errors?.length) console.warn('[CalendarSync]', r); })
      .catch(e => console.error('[CalendarSync] Fehler:', e));
  }, []);
  return (
    <div style={{ padding: mobile ? '14px 12px 90px' : '36px 44px', maxWidth:1100 }}>
      {showBoard && <BoardModal user={user} toast={toast} onClose={()=>setShowBoard(false)} onRead={onBoardRead}/>}
      {showChangelog && <ChangelogModal user={user} toast={toast} onClose={()=>setShowChangelog(false)} onRead={onChangelogRead}/>}

      <div style={{ marginBottom:16, display:'flex', alignItems:'flex-start', justifyContent:'space-between' }}>
        <div>
          <div style={{ display:'flex', alignItems:'center', gap:10 }}>
            <h1 style={{ color:'#fff', fontFamily:"'Space Mono',monospace", fontSize:mobile ? 17 : 22, margin:0 }}>Dashboard</h1>
            {unreadChangelog > 0 && user?.role !== 'admin' && (
              <button onClick={()=>setShowChangelog(true)} style={{
                background:'linear-gradient(135deg,rgba(78,205,196,0.15),rgba(78,205,196,0.05))',
                border:'1px solid rgba(78,205,196,0.3)', borderRadius:20,
                color:'#4ecdc4', cursor:'pointer', fontFamily:'monospace',
                fontSize:10, padding:'3px 10px', letterSpacing:0.5,
                animation:'pulse 2s infinite',
              }}>✨ Was ist neu?</button>
            )}
          </div>
          <p style={{ color:'rgba(255,255,255,0.55)', fontFamily:'monospace', fontSize:10, marginTop:3 }}>
            {now.toLocaleDateString('de-DE', { weekday:'long', day:'numeric', month:'long' })}
            {' · '}
            {now.toLocaleTimeString('de-DE', { hour:'2-digit', minute:'2-digit' })}
          </p>
        </div>
        <button onClick={() => window.location.reload()}
          title="Seite neu laden"
          style={{ background:'transparent', border:'1px solid rgba(255,255,255,0.1)', borderRadius:8,
            color:'rgba(255,255,255,0.4)', cursor:'pointer', padding:'6px 10px', fontSize:14,
            fontFamily:'monospace', marginTop:2, flexShrink:0 }}>↺</button>
      </div>
      {/* Globale Suche */}
      <GlobalSearch setActive={setActive} setDevToolsNav={setDevToolsNav} mobile={mobile}/>
      <QuickLinks toast={toast} mobile={mobile} />
      <PushScheduler toast={toast}/>
      <CalendarWidget/>
      <BestellStats/>
      <TodoList   toast={toast} />
      <Notepad    toast={toast} />
      <div style={{ marginTop:24, display:'flex', alignItems:'center', justifyContent:'center', gap:12 }}>
        <button onClick={()=>setShowChangelog(true)} style={{
          background:'transparent', border:'none', cursor:'pointer',
          color:'rgba(255,255,255,0.35)', fontSize:9, fontFamily:'monospace',
          letterSpacing:1, padding:0, textDecoration:'underline dotted',
          textUnderlineOffset:3 }}>
          {typeof __BUILD_TIME__ !== 'undefined' && __BUILD_TIME__
            ? `Letzte Versionierung: ${new Date(Number(__BUILD_TIME__)*1000).toLocaleString('de-DE',{timeZone:'Europe/Berlin',day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})}` : 'Changelog'}
        </button>
        <button onClick={()=>setShowBoard(true)} style={{
          position:'relative', background:'rgba(255,255,255,0.04)',
          border:'1px solid rgba(255,255,255,0.1)', borderRadius:7,
          color:'rgba(255,255,255,0.45)', cursor:'pointer',
          padding:'4px 10px', fontSize:9, fontFamily:'monospace', letterSpacing:1 }}>
          📋 ToDo
          {unreadBoard > 0 && (
            <span style={{ position:'absolute', top:-3, right:-3, width:7, height:7,
              borderRadius:'50%', background:'#4ecdc4', boxShadow:'0 0 5px #4ecdc4' }}/>
          )}
          {unreadPromoted > 0 && unreadBoard === 0 && (
            <span style={{ position:'absolute', top:-3, right:-3, width:7, height:7,
              borderRadius:'50%', background:'#ffe66d', boxShadow:'0 0 5px #ffe66d' }}/>
          )}
          {unreadPromoted > 0 && unreadBoard > 0 && (
            <span style={{ position:'absolute', top:-3, left:-3, width:7, height:7,
              borderRadius:'50%', background:'#ffe66d', boxShadow:'0 0 5px #ffe66d' }}/>
          )}
        </button>
      </div>
    </div>
  );
}


// ── Admin Panel ───────────────────────────────────────────────────────────────
// Sec muss außerhalb von AdminPanel definiert sein damit Inputs den Fokus behalten
const Sec = ({title, children}) => (
  <div style={{...S.card, marginBottom:12}}>
    <div style={S.head}>{title}</div>
    {children}
  </div>
);

// ── Account-Löschung ─────────────────────────────────────────────────────────
function DeleteAccountSection({ toast, user }) {
  const [pw,  setPw]  = useState('');
  const [busy, setBusy] = useState(false);
  const { confirm, ConfirmDialog } = useConfirm();

  const doDelete = async () => {
    if (!pw) { toast('Passwort eingeben', 'error'); return; }
    if (!await confirm(`Account "${user.username}" und alle Daten wirklich löschen? Das kann nicht rückgängig gemacht werden.`)) return;
    setBusy(true);
    try {
      await api('/auth/delete-account', { method:'DELETE', body:{ password:pw } });
      localStorage.removeItem('sk_token');
      window.location.reload();
    } catch(e) { toast(e.message, 'error'); } finally { setBusy(false); }
  };

  return (
    <div>
      <ConfirmDialog/>
      <Field label="PASSWORT ZUR BESTÄTIGUNG" type="password" value={pw} onChange={e=>setPw(e.target.value)}/>
      <button onClick={doDelete} disabled={busy} style={{
        ...S.btn('#ff6b9d'), width:'100%', textAlign:'center', padding:'11px 0',
        opacity: busy ? 0.6 : 1,
      }}>
        {busy ? '…' : '✕ Account unwiderruflich löschen'}
      </button>
    </div>
  );
}

// ── QuickLinks Einstellungen ─────────────────────────────────────────────────
function QuickLinksSettings({ toast }) {
  const [links,   setLinks]   = useState([]);
  const [showForm, setForm]   = useState(false);
  const [form,    setF]       = useState({ title:'', url:'', icon:'🔗' });
  const [editId,  setEditId]  = useState(null);
  const dragRef = useRef(null);
  const touchRef = useRef({});
  const { confirm, ConfirmDialog } = useConfirm();

  useEffect(() => { api('/dashboard/links').then(setLinks).catch(() => {}); }, []);

  const getFavicon = url => {
    try { return `https://www.google.com/s2/favicons?sz=64&domain=${new URL(url).hostname}`; }
    catch { return null; }
  };

  const save = async () => {
    if (!form.title.trim() || !form.url.trim()) { toast('Titel und URL erforderlich', 'error'); return; }
    let url = form.url.trim();
    if (!/^https?:\/\//i.test(url)) url = 'https://' + url;
    const icon = form.icon === '🔗' ? (getFavicon(url) || '🔗') : form.icon;
    try {
      if (editId) {
        const u = await api(`/dashboard/links/${editId}`, { method:'PUT', body:{...form, icon, url} });
        setLinks(p => p.map(l => l.id===editId ? u : l));
      } else {
        const n = await api('/dashboard/links', { body:{...form, icon, url} });
        setLinks(p => [...p, n]);
      }
      toast(editId ? 'Aktualisiert' : 'Gespeichert');
      setF({ title:'', url:'', icon:'🔗' }); setForm(false); setEditId(null);
    } catch(e) { toast(e.message, 'error'); }
  };

  const del = async id => {
    if (!await confirm('Link wirklich löschen?')) return;
    try { await api(`/dashboard/links/${id}`, { method:'DELETE' }); setLinks(p => p.filter(l => l.id!==id)); toast('Gelöscht'); }
    catch(e) { toast(e.message, 'error'); }
  };

  const reorder = async (newLinks) => {
    setLinks(newLinks);
    try { await api('/dashboard/links-order', { method:'PUT', body:{ ids: newLinks.map(l=>l.id) } }); }
    catch(e) { toast(e.message,'error'); }
  };

  // Desktop drag handlers
  const onDragStart = (e, id) => { dragRef.current = id; e.dataTransfer.effectAllowed = 'move'; };
  const onDragOver  = (e, id) => {
    e.preventDefault();
    if (dragRef.current === id) return;
    const from = links.findIndex(l => l.id === dragRef.current);
    const to   = links.findIndex(l => l.id === id);
    if (from < 0 || to < 0) return;
    const next = [...links]; next.splice(to, 0, next.splice(from, 1)[0]);
    setLinks(next);
  };
  const onDrop = () => reorder(links);

  // Touch handlers (long press + drag)
  const onTouchStart = (e, id) => {
    touchRef.current = { id, startY: e.touches[0].clientY, timer: setTimeout(() => {
      touchRef.current.active = true;
    }, 300) };
  };
  const onTouchMove = (e) => {
    if (!touchRef.current.active) return;
    e.preventDefault();
    const y = e.touches[0].clientY;
    const els = document.elementsFromPoint(e.touches[0].clientX, y);
    const target = els.find(el => el.dataset?.linkId);
    if (!target) return;
    const targetId = parseInt(target.dataset.linkId);
    if (targetId === touchRef.current.id) return;
    const from = links.findIndex(l => l.id === touchRef.current.id);
    const to   = links.findIndex(l => l.id === targetId);
    if (from < 0 || to < 0) return;
    const next = [...links]; next.splice(to, 0, next.splice(from, 1)[0]);
    setLinks(next);
  };
  const onTouchEnd = () => {
    clearTimeout(touchRef.current.timer);
    if (touchRef.current.active) reorder(links);
    touchRef.current = {};
  };

  return (
    <div>
      <ConfirmDialog/>
      {/* Link-Liste */}
      {links.length > 0 && (
        <div style={{ marginBottom:12 }}>
          {links.map(l => (
            <div key={l.id}
              data-link-id={l.id}
              draggable
              onDragStart={e => onDragStart(e, l.id)}
              onDragOver={e => onDragOver(e, l.id)}
              onDrop={onDrop}
              onTouchStart={e => onTouchStart(e, l.id)}
              onTouchMove={onTouchMove}
              onTouchEnd={onTouchEnd}
              style={{ display:'flex', alignItems:'center', gap:10, padding:'8px 0',
                borderBottom:'1px solid rgba(255,255,255,0.05)',
                cursor:'grab', userSelect:'none' }}>
              <span style={{ color:'rgba(255,255,255,0.2)', fontSize:14, flexShrink:0, cursor:'grab' }}>⠿</span>
              <span style={{ fontSize:18, width:24, textAlign:'center', flexShrink:0 }}>
                {l.icon && l.icon.startsWith('http')
                  ? <img src={l.icon} alt="" style={{width:18,height:18,objectFit:'contain'}} onError={e=>e.target.style.display='none'}/>
                  : l.icon}
              </span>
              <div style={{ flex:1, minWidth:0 }}>
                <div style={{ color:'#fff', fontFamily:'monospace', fontSize:13 }}>{l.title}</div>
                <div style={{ color:'rgba(255,255,255,0.4)', fontFamily:'monospace', fontSize:10,
                  overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{l.url}</div>
              </div>
              <div style={{ display:'flex', gap:5, flexShrink:0 }}>
                <button onClick={() => { setF({ title:l.title, url:l.url, icon:l.icon }); setEditId(l.id); setForm(true); }}
                  style={S.btn('#4ecdc4', true)}>✎</button>
                <button onClick={() => del(l.id)} style={S.btn('#ff6b9d', true)}>✕</button>
              </div>
            </div>
          ))}
        </div>
      )}

      {/* Formular */}
      {showForm ? (
        <div style={{ background:'rgba(0,0,0,0.2)', border:'1px solid rgba(255,255,255,0.07)', borderRadius:10, padding:12 }}>
          <div style={{ marginBottom:10 }}>
            <div style={{ ...S.head, marginBottom:6 }}>ICON</div>
            <div style={{ display:'flex', flexWrap:'wrap', gap:5 }}>
              {['🔗','🌐','📊','📁','🛠','📧','🗓','💾','🖥','📡','🔒','📖','🎯','⚡','🏠','🔧','📱','🎮','🚀','💡'].map(ic => (
                <button key={ic} onClick={() => setF(f => ({...f, icon:ic}))} style={{
                  width:34, height:34, borderRadius:7, fontSize:17, cursor:'pointer',
                  border:`1px solid ${form.icon===ic ? '#4ecdc4' : 'rgba(255,255,255,0.1)'}`,
                  background: form.icon===ic ? 'rgba(78,205,196,0.15)' : 'rgba(255,255,255,0.04)',
                }}>{ic}</button>
              ))}
            </div>
          </div>
          <Field label="TITEL" value={form.title} onChange={e => setF(f => ({...f, title:e.target.value}))} placeholder="Mein Link"/>
          <Field label="URL" value={form.url} onChange={e => setF(f => ({...f, url:e.target.value}))} placeholder="https://…" autoCapitalize="none"/>
          <div style={{ display:'flex', gap:8, marginTop:4 }}>
            <button onClick={save} style={{ ...S.btn('#4ecdc4'), flex:1, textAlign:'center', padding:'10px 0' }}>{editId ? '✓ Aktualisieren' : '✓ Speichern'}</button>
            <button onClick={() => { setForm(false); setEditId(null); setF({ title:'', url:'', icon:'🔗' }); }}
              style={{ ...S.btn('#ff6b9d'), flex:1, textAlign:'center', padding:'10px 0' }}>Abbrechen</button>
          </div>
        </div>
      ) : (
        <button onClick={() => { setForm(true); setEditId(null); setF({ title:'', url:'', icon:'🔗' }); }}
          style={{ ...S.btn('#4ecdc4'), width:'100%', textAlign:'center', padding:'10px 0' }}>
          + Link hinzufügen
        </button>
      )}
    </div>
  );
}

// ── Benutzerverwaltung ────────────────────────────────────────────────────────
// ── Login-Sicherheit Admin ────────────────────────────────────────────────────
function SecuritySettingsAdmin({ toast }) {
  const [s, setS] = useState({ login_max_attempts:'5', login_lockout_minutes:'30' });
  const [locked, setLocked] = useState([]);
  const [busy,   setBusy]   = useState(false);

  const load = () => {
    api('/admin/security-settings').then(setS).catch(()=>{});
    api('/admin/lockouts').then(setLocked).catch(()=>{});
  };
  useEffect(()=>{ load(); },[]);

  const save = async () => {
    setBusy(true);
    try { await api('/admin/security-settings',{method:'PUT',body:s}); toast('Gespeichert ✓'); }
    catch(e) { toast(e.message,'error'); }
    setBusy(false);
  };

  const unlock = async uid => {
    try { await api(`/admin/lockouts/${uid}`,{method:'DELETE'}); toast('Sperre aufgehoben'); setLocked(p=>p.filter(l=>l.id!==uid)); }
    catch(e) { toast(e.message,'error'); }
  };

  const fmtDate = s => { if (!s) return ''; const d = new Date(s.replace(' ','T')); return isNaN(d)?'':d.toLocaleString('de-DE',{day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}); };

  return (
    <div>
      <Sec title="LOGIN-SCHUTZ EINSTELLUNGEN">
        <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:10, marginBottom:12 }}>
          <div>
            <label style={{ ...S.head, display:'block', marginBottom:4 }}>MAX. FEHLVERSUCHE</label>
            <input type="number" min="1" max="20" value={s.login_max_attempts}
              onChange={e=>setS(p=>({...p,login_max_attempts:e.target.value}))}
              style={{ ...S.inp, fontSize:15 }}/>
          </div>
          <div>
            <label style={{ ...S.head, display:'block', marginBottom:4 }}>SPERRZEIT (MINUTEN)</label>
            <input type="number" min="1" max="1440" value={s.login_lockout_minutes}
              onChange={e=>setS(p=>({...p,login_lockout_minutes:e.target.value}))}
              style={{ ...S.inp, fontSize:15 }}/>
          </div>
        </div>
        <button onClick={save} disabled={busy}
          style={{ ...S.btn('#4ecdc4'), width:'100%', textAlign:'center', padding:'10px 0', opacity:busy?0.5:1 }}>
          {busy?'…':'✓ Speichern'}
        </button>
      </Sec>

      <Sec title={`GESPERRTE KONTEN ${locked.length>0?`(${locked.length})`:''}`}>
        {locked.length===0 ? (
          <div style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:11 }}>
            ✓ Keine gesperrten Konten
          </div>
        ) : locked.map(u => (
          <div key={u.id} style={{ display:'flex', alignItems:'center', gap:10,
            padding:'10px 0', borderBottom:'1px solid rgba(255,255,255,0.05)' }}>
            <div style={{ flex:1, minWidth:0 }}>
              <div style={{ color:'#ff6b9d', fontFamily:'monospace', fontSize:13, fontWeight:700 }}>
                🔒 {u.username}
              </div>
              <div style={{ color:'rgba(255,255,255,0.35)', fontFamily:'monospace', fontSize:10, marginTop:3, lineHeight:1.7 }}>
                {u.failed_attempts} Fehlversuch{u.failed_attempts!==1?'e':''} ·
                Gesperrt bis: {fmtDate(u.locked_until)}
              </div>
              {u.last_failed_at && (
                <div style={{ color:'rgba(255,255,255,0.2)', fontFamily:'monospace', fontSize:9 }}>
                  Letzter Versuch: {fmtDate(u.last_failed_at)}
                </div>
              )}
              {u.ips?.length > 0 && (
                <div style={{ marginTop:4, display:'flex', flexWrap:'wrap', gap:4 }}>
                  {u.ips.map((ip,i) => (
                    <span key={i} style={{ background:'rgba(255,107,157,0.1)', border:'1px solid rgba(255,107,157,0.2)',
                      borderRadius:5, padding:'1px 7px', color:'rgba(255,107,157,0.7)',
                      fontFamily:'monospace', fontSize:9 }}>{ip}</span>
                  ))}
                </div>
              )}
            </div>
            <button onClick={()=>unlock(u.id)}
              style={{ ...S.btn('#4ecdc4', true), flexShrink:0, fontSize:11 }}>
              🔓 Entsperren
            </button>
          </div>
        ))}
      </Sec>
    </div>
  );
}

function UserManagement({ toast }) {
  const [users,   setUsers]   = useState([]);
  const [loading, setLoading] = useState(true);
  const [form,    setForm]    = useState({ username:'', password:'', role:'user' });
  const [resetId, setResetId] = useState(null);
  const [resetPw, setResetPw] = useState('');
  const [delId,   setDelId]   = useState(null);
  const { confirm, ConfirmDialog } = useConfirm();

  const load = () => {
    setLoading(true);
    api('/admin/users').then(setUsers).catch(e=>toast(e.message,'error')).finally(()=>setLoading(false));
  };
  useEffect(()=>{ load(); },[]);

  const create = async () => {
    if (!form.username.trim()||!form.password) { toast('Alle Felder ausfüllen','error'); return; }
    try {
      await api('/admin/users',{body:form});
      toast(`Benutzer "${form.username}" angelegt`);
      setForm({username:'',password:'',role:'user'}); load();
    } catch(e) { toast(e.message,'error'); }
  };

  const del = async id => {
    if (!await confirm('Benutzer und alle seine Daten wirklich löschen?')) return;
    try { await api(`/admin/users/${id}`,{method:'DELETE'}); toast('Gelöscht'); load(); }
    catch(e) { toast(e.message,'error'); }
  };

  const resetPassword = async () => {
    if (!resetPw||resetPw.length<8) { toast('Mind. 8 Zeichen','error'); return; }
    try {
      await api(`/admin/users/${resetId}/reset-password`,{method:'PUT',body:{newPassword:resetPw}});
      toast('Passwort zurückgesetzt'); setResetId(null); setResetPw('');
    } catch(e) { toast(e.message,'error'); }
  };

  return (
    <div>
      <ConfirmDialog/>

      {/* Neuen Benutzer anlegen */}
      <Sec title="NEUEN BENUTZER ANLEGEN">
        <Field label="BENUTZERNAME" value={form.username} onChange={e=>setForm(f=>({...f,username:e.target.value}))} autoCapitalize="none"/>
        <Field label="PASSWORT (mind. 8 Zeichen)" type="password" value={form.password} onChange={e=>setForm(f=>({...f,password:e.target.value}))}/>
        <div style={{marginBottom:12}}>
          <label style={{...S.head,display:'block',marginBottom:4}}>ROLLE</label>
          <select value={form.role} onChange={e=>setForm(f=>({...f,role:e.target.value}))} style={{...S.inp,fontSize:14}}>
            <option value="user" style={{background:'#1a1a1e'}}>Benutzer</option>
            <option value="admin" style={{background:'#1a1a1e'}}>Administrator</option>
          </select>
        </div>
        <button onClick={create} style={{...S.btn('#4ecdc4'),width:'100%',textAlign:'center',padding:'11px 0'}}>
          + Benutzer anlegen
        </button>
      </Sec>

      {/* Passwort-Reset Modal */}
      {resetId && (
        <div style={{position:'fixed',inset:0,background:'rgba(0,0,0,0.8)',zIndex:5000,
          display:'flex',alignItems:'center',justifyContent:'center',padding:20}}>
          <div style={{...S.card,width:'100%',maxWidth:360}}>
            <div style={{color:'#fff',fontFamily:"'Space Mono',monospace",fontSize:14,fontWeight:700,marginBottom:16}}>
              Passwort zurücksetzen
            </div>
            <Field label="NEUES PASSWORT" type="password" value={resetPw} onChange={e=>setResetPw(e.target.value)}/>
            <div style={{display:'flex',gap:8}}>
              <button onClick={resetPassword} style={{...S.btn('#4ecdc4'),flex:1,textAlign:'center',padding:'10px 0'}}>✓ Setzen</button>
              <button onClick={()=>{setResetId(null);setResetPw('');}} style={{...S.btn('#ff6b9d'),flex:1,textAlign:'center',padding:'10px 0'}}>Abbrechen</button>
            </div>
          </div>
        </div>
      )}

      {/* Benutzerliste */}
      <Sec title="BENUTZER">
        {loading && <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:12}}>Lädt…</div>}
        {users.map(u => (
          <div key={u.id} style={{
            padding:'12px 0', borderBottom:'1px solid rgba(255,255,255,0.06)',
          }}>
            {/* Zeile 1: Name + Badges */}
            <div style={{display:'flex',alignItems:'flex-start',justifyContent:'space-between',gap:8,marginBottom:6}}>
              <div style={{minWidth:0,flex:1}}>
                <div style={{display:'flex',alignItems:'center',flexWrap:'wrap',gap:4,marginBottom:3}}>
                  <span style={{color:'#fff',fontFamily:'monospace',fontSize:13,fontWeight:700}}>{u.username}</span>
                  <span style={{
                    fontSize:9, fontFamily:'monospace',
                    color: u.role==='admin'?'#ffe66d':'#4ecdc4',
                    background: u.role==='admin'?'rgba(255,230,109,0.1)':'rgba(78,205,196,0.1)',
                    border:`1px solid ${u.role==='admin'?'rgba(255,230,109,0.3)':'rgba(78,205,196,0.3)'}`,
                    borderRadius:4, padding:'1px 6px',
                  }}>{u.role}</span>
                  {!!u.has_pushover && (
                    <span style={{fontSize:9,fontFamily:'monospace',color:'rgba(255,107,157,0.8)',
                      background:'rgba(255,107,157,0.1)',border:'1px solid rgba(255,107,157,0.25)',
                      borderRadius:4,padding:'1px 6px'}}>🔔 Push</span>
                  )}
                  {!!u.hidden && (
                    <span style={{fontSize:9,fontFamily:'monospace',color:'rgba(255,230,109,0.7)',
                      background:'rgba(255,230,109,0.08)',border:'1px solid rgba(255,230,109,0.2)',
                      borderRadius:4,padding:'1px 6px'}}>👁 versteckt</span>
                  )}
                </div>
                {u.last_active_at && (
                  <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9}}>
                    ● {(() => {
                      const d = new Date(u.last_active_at.replace(' ','T'));
                      const diff = Math.floor((new Date() - d) / 60000);
                      if (diff < 2)    return 'gerade eben';
                      if (diff < 60)   return `vor ${diff} min`;
                      if (diff < 1440) return `vor ${Math.floor(diff/60)} h`;
                      return d.toLocaleDateString('de-DE',{day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'});
                    })()}
                  </div>
                )}
              </div>
              {/* Aktions-Buttons */}
              <div style={{display:'flex',gap:4,flexShrink:0,flexWrap:'wrap',justifyContent:'flex-end'}}>
                <button onClick={()=>{setResetId(u.id);setResetPw('');}}
                  style={{...S.btn('#ffe66d',true),fontSize:10,padding:'4px 8px'}}>🔑</button>
                <button
                  onClick={async ()=>{
                    if (u.role==='admin') return;
                    try {
                      const r = await api(`/admin/users/${u.id}/hidden`,{method:'PUT'});
                      setUsers(p=>p.map(x=>x.id===u.id?{...x,hidden:r.hidden}:x));
                      toast(r.hidden ? 'Ausgeblendet' : 'Eingeblendet');
                    } catch(e){ toast(e.message,'error'); }
                  }}
                  disabled={u.role==='admin'}
                  title={u.hidden?'Einblenden':'Ausblenden'}
                  style={{
                    fontSize:13, cursor: u.role==='admin'?'not-allowed':'pointer',
                    padding:'4px 7px', borderRadius:6, border:'none',
                    background: u.hidden ? 'rgba(255,230,109,0.15)' : 'rgba(255,255,255,0.08)',
                    color: u.hidden ? '#ffe66d' : 'rgba(255,255,255,0.4)',
                    opacity: u.role==='admin' ? 0.3 : 1,
                  }}>👁</button>
                <button onClick={()=>del(u.id)} style={{...S.btn('#ff6b9d',true),padding:'4px 7px'}}>
                  <TrashIcon size={13} color="#ff6b9d"/>
                </button>
              </div>
            </div>
            {/* Statistik */}
            <div style={{display:'flex',flexWrap:'wrap',gap:6}}>
              {[
                ['◈ Archiv', u.archiv],
                ['📦 Bestellungen', u.bestellung],
                ['</> Codes', u.snippets_c],
                ['✓ Todos', u.todos],
                ['🔗 Links', u.links],
                ['📁 Dateien', u.files],
                ['📅 iCals', u.icals],
                [`✎ ${u.noteLen} Zeichen`, null],
              ].map(([label, val]) => (
                <span key={label} style={{
                  fontSize:10, fontFamily:'monospace',
                  color:'rgba(255,255,255,0.55)',
                  background:'rgba(255,255,255,0.04)',
                  border:'1px solid rgba(255,255,255,0.08)',
                  borderRadius:8, padding:'2px 8px',
                }}>
                  {label}{val !== null ? `: ${val}` : ''}
                </span>
              ))}
            </div>
          </div>
        ))}
      </Sec>
    </div>
  );
}

// ── Sicherheit / Key-Management ───────────────────────────────────────────────
function SecuritySettings({ toast }) {
  const [kp,          setKp]         = useState(null);
  const [fingerprint, setFp]         = useState('');
  const [expPw,       setExpPw]      = useState('');
  const [expPw2,      setExpPw2]     = useState('');
  const [showExport,  setShowExport] = useState(false);
  const [impFile,     setImpFile]    = useState(null);
  const [impPw,       setImpPw]      = useState('');
  const [showImport,  setShowImport] = useState(false);
  const [busy,        setBusy]       = useState(false);

  useEffect(() => {
    getOrCreateKeyPair()
      .then(async k => {
        setKp(k);
        setFp(await getFingerprint(k));
      })
      .catch(() => {});
  }, []);

  const doExport = async () => {
    if (!kp) return;
    if (expPw.length < 8)    { toast('Passwort mind. 8 Zeichen', 'error'); return; }
    if (expPw !== expPw2)    { toast('Passwörter stimmen nicht überein', 'error'); return; }
    setBusy(true);
    try {
      const json = await exportEncrypted(kp, expPw);
      const a    = Object.assign(document.createElement('a'), {
        href: URL.createObjectURL(new Blob([json], { type:'application/json' })),
        download: 'dickendock-key.json',
      });
      a.click(); URL.revokeObjectURL(a.href);
      setExpPw(''); setExpPw2(''); setShowExport(false);
      toast('Schlüssel exportiert ✓');
    } catch { toast('Export fehlgeschlagen', 'error'); }
    setBusy(false);
  };

  const doImport = async () => {
    if (!impFile || !impPw) { toast('Datei und Passwort erforderlich', 'error'); return; }
    setBusy(true);
    try {
      const text  = await impFile.text();
      const newKp = await importEncrypted(text, impPw);
      const jwk   = await getPublicKeyJwk(newKp);
      await api('/tools/nachrichten/keys', { body: { public_key: JSON.stringify(jwk) } });
      setKp(newKp);
      setFp(await getFingerprint(newKp));
      setImpFile(null); setImpPw(''); setShowImport(false);
      toast('Schlüssel importiert ✓');
    } catch(e) { toast(e.message, 'error'); }
    setBusy(false);
  };

  const doReset = async () => {
    if (!window.confirm(
      'Neues Schlüsselpaar erzeugen?\n\n' +
      'Alle bisherigen Nachrichten werden vom Server gelöscht und können nicht mehr gelesen werden. ' +
      'Geräte mit dem alten Schlüssel können keine neuen Nachrichten mehr lesen.'
    )) return;
    setBusy(true);
    try {
      const newKp = await generateNewKeyPair();
      const jwk   = await getPublicKeyJwk(newKp);
      await api('/tools/nachrichten/keys', { body: { public_key: JSON.stringify(jwk) } });
      await api('/tools/nachrichten/my-messages', { method:'DELETE' });
      toast('Neues Schlüsselpaar aktiv – App wird neu geladen…');
      setTimeout(() => window.location.reload(), 1500);
    } catch(e) { toast(e.message, 'error'); setBusy(false); }
  };

  const FpChar = ({ c }) => (
    <span style={{ background:'rgba(255,255,255,0.06)', border:'1px solid rgba(255,255,255,0.09)',
      borderRadius:5, padding:'5px 8px', fontFamily:'monospace', fontSize:14, letterSpacing:2,
      color: c === '-' ? 'rgba(255,255,255,0.2)' : '#4ecdc4' }}>{c}</span>
  );

  return (
    <div>
      {/* Fingerprint */}
      <Sec title="SCHLÜSSEL-FINGERPRINT">
        <p style={{ color:'rgba(255,255,255,0.42)', fontSize:11, fontFamily:'monospace',
          marginBottom:12, lineHeight:1.7 }}>
          Das ist der Fingerprint <strong style={{color:'rgba(255,255,255,0.65)'}}>deines eigenen</strong> Public Keys.
          Dein Gesprächspartner sieht im Chat-Header deinen Fingerprint – er soll ihn mit diesem hier vergleichen.
          Stimmen beide überein, ist die Verbindung sicher.
        </p>
        {fingerprint ? (
          <div style={{ display:'flex', gap:5, flexWrap:'wrap', marginBottom:8 }}>
            {fingerprint.split('').map((c, i) => <FpChar key={i} c={c}/>)}
          </div>
        ) : (
          <div style={{ color:'rgba(255,255,255,0.25)', fontFamily:'monospace', fontSize:11 }}>Wird geladen…</div>
        )}
        <div style={{ color:'rgba(255,255,255,0.25)', fontSize:9, fontFamily:'monospace', marginTop:6 }}>
          SHA-256(PublicKey) · X25519
        </div>
      </Sec>

      {/* Export */}
      <Sec title="SCHLÜSSEL EXPORTIEREN">
        <p style={{ color:'rgba(255,255,255,0.42)', fontSize:11, fontFamily:'monospace',
          marginBottom:12, lineHeight:1.7 }}>
          Exportiere deinen privaten Schlüssel verschlüsselt als Datei um ihn auf einem anderen Gerät zu nutzen.
        </p>
        {!showExport ? (
          <button onClick={() => setShowExport(true)} disabled={!kp}
            style={{ ...S.btn('#4ecdc4'), width:'100%', textAlign:'center', padding:'10px 0' }}>
            ↓ Schlüssel exportieren
          </button>
        ) : (
          <div>
            <Field label="EXPORT-PASSWORT (mind. 8 Zeichen)" type="password"
              value={expPw} onChange={e => setExpPw(e.target.value)}/>
            <Field label="PASSWORT BESTÄTIGEN" type="password"
              value={expPw2} onChange={e => setExpPw2(e.target.value)}/>
            <div style={{ display:'flex', gap:8 }}>
              <button onClick={doExport} disabled={busy}
                style={{ ...S.btn('#4ecdc4'), flex:1, textAlign:'center', padding:'10px 0', opacity:busy?0.5:1 }}>
                {busy ? '…' : '↓ Herunterladen'}
              </button>
              <button onClick={() => { setShowExport(false); setExpPw(''); setExpPw2(''); }}
                style={{ ...S.btn('#ff6b9d', true), padding:'0 14px' }}>Abbrechen</button>
            </div>
          </div>
        )}
      </Sec>

      {/* Import */}
      <Sec title="SCHLÜSSEL IMPORTIEREN">
        <p style={{ color:'rgba(255,255,255,0.42)', fontSize:11, fontFamily:'monospace',
          marginBottom:12, lineHeight:1.7 }}>
          Importiere eine zuvor exportierte Schlüsseldatei. Der aktuelle Schlüssel wird ersetzt.
        </p>
        {!showImport ? (
          <button onClick={() => setShowImport(true)}
            style={{ ...S.btn('#ffe66d'), width:'100%', textAlign:'center', padding:'10px 0' }}>
            ↑ Schlüssel importieren
          </button>
        ) : (
          <div>
            <div style={{ marginBottom:12 }}>
              <label style={{ ...S.head, display:'block', marginBottom:4 }}>SCHLÜSSELDATEI (.json)</label>
              <input type="file" accept=".json"
                onChange={e => setImpFile(e.target.files?.[0] || null)}
                style={{ ...S.inp, fontSize:13, padding:'9px 10px', cursor:'pointer' }}/>
            </div>
            <Field label="EXPORT-PASSWORT" type="password"
              value={impPw} onChange={e => setImpPw(e.target.value)}/>
            <div style={{ display:'flex', gap:8 }}>
              <button onClick={doImport} disabled={busy || !impFile || !impPw}
                style={{ ...S.btn('#ffe66d'), flex:1, textAlign:'center', padding:'10px 0',
                  opacity: busy || !impFile || !impPw ? 0.4 : 1 }}>
                {busy ? '…' : '✓ Importieren'}
              </button>
              <button onClick={() => { setShowImport(false); setImpFile(null); setImpPw(''); }}
                style={{ ...S.btn('#ff6b9d', true), padding:'0 14px' }}>Abbrechen</button>
            </div>
          </div>
        )}
      </Sec>

      {/* Reset – Danger Zone */}
      <Sec title="SCHLÜSSEL ZURÜCKSETZEN">
        <p style={{ color:'rgba(255,107,157,0.6)', fontSize:11, fontFamily:'monospace',
          marginBottom:12, lineHeight:1.7 }}>
          ⚠ Gefahrenzone: Erzeugt ein neues Schlüsselpaar und löscht alle deine Nachrichten vom Server.
          Alte Nachrichten sind danach nicht mehr lesbar. Geräte mit dem alten Schlüssel werden ausgesperrt.
        </p>
        <button onClick={doReset} disabled={busy}
          style={{ ...S.btn('#ff6b9d'), width:'100%', textAlign:'center', padding:'10px 0',
            opacity: busy ? 0.5 : 1 }}>
          {busy ? '…' : '✕ Neues Schlüsselpaar erzeugen'}
        </button>
      </Sec>
    </div>
  );
}

// Field außerhalb definiert → Tastatur bleibt beim Tippen erhalten
const Field = ({ label, type='text', value, onChange, placeholder='', autoCapitalize='off' }) => (
  <div style={{ marginBottom:12 }}>
    <label style={{ ...S.head, display:'block', marginBottom:4 }}>{label}</label>
    <input type={type} value={value} onChange={onChange} placeholder={placeholder}
      autoCapitalize={autoCapitalize}
      style={{ ...S.inp, fontSize:16, padding:'11px 12px' }} />
  </div>
);

// ── Admin: Upload-Freigabe Berechtigungen ─────────────────────────────────────
function TmdbSettings({ toast }) {
  const [token,    setToken]    = useState('');
  const [configured, setConfigured] = useState(false);
  const [busy,     setBusy]     = useState(false);

  useEffect(() => {
    api('/tools/media/settings').then(d => setConfigured(d.configured)).catch(()=>{});
  }, []);

  const save = async () => {
    if (!token.trim()) { toast('Token eingeben', 'error'); return; }
    setBusy(true);
    try {
      await api('/tools/media/settings', { method:'PUT', body:{ tmdb_token: token.trim() } });
      toast('TMDb Token gespeichert ✓');
      setConfigured(true); setToken('');
    } catch(e) { toast(e.message, 'error'); } finally { setBusy(false); }
  };

  return (
    <div>
      <div style={{ fontSize:12, color:'rgba(255,255,255,0.45)', fontFamily:'monospace', marginBottom:10, lineHeight:1.6 }}>
        API Read Access Token von <a href="https://www.themoviedb.org/settings/api" target="_blank"
          rel="noopener noreferrer" style={{ color:'#4ecdc4' }}>themoviedb.org</a> eintragen.
        {configured && <span style={{ color:'#4ecdc4', marginLeft:6 }}>✓ Konfiguriert</span>}
      </div>
      <Field label="BEARER TOKEN (API READ ACCESS TOKEN)" value={token} onChange={e=>setToken(e.target.value)}
        type="password" placeholder={configured ? '••• Token ersetzen •••' : 'eyJhbGciOi…'} />
      <button onClick={save} disabled={busy} style={{ ...S.btn('#4ecdc4'), width:'100%', textAlign:'center', padding:'11px 0' }}>
        {busy ? '…' : '✓ Token speichern'}
      </button>
    </div>
  );
}

function UploadShareAdminPanel({ toast }) {
  const [users,    setUsers]    = useState([]);
  const [logs,     setLogs]     = useState([]);
  const [showLogs, setShowLogs] = useState(false);

  const load = () => {
    api('/admin/users').then(setUsers).catch(()=>{});
  };
  const loadLogs = () => {
    api('/upload-shares/admin/logs').then(setLogs).catch(()=>{});
    setShowLogs(true);
  };
  useEffect(()=>{ load(); },[]);

  const toggle = async (uid, current) => {
    try {
      await api('/upload-shares/admin/permission', { body:{ user_id: uid, allow: !current } });
      setUsers(p => p.map(u => u.id===uid ? {...u, allow_upload_share: current?0:1} : u));
      toast(current ? 'Berechtigung entzogen' : 'Berechtigung erteilt');
    } catch(e) { toast(e.message,'error'); }
  };

  const fmtDate = d => d ? new Date(d).toLocaleString('de-DE',{timeZone:'Europe/Berlin',day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'}) : '–';
  const fmtSize = b => b>1024*1024 ? (b/1024/1024).toFixed(1)+' MB' : (b/1024).toFixed(0)+' KB';

  return (
    <div>
      <div style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:12,marginBottom:12,lineHeight:1.7}}>
        Admins haben immer Zugriff auf Upload-Freigaben.<br/>
        Hier kannst du die Funktion für einzelne Mitglieder freischalten.
      </div>
      {users.filter(u=>u.role!=='admin').map(u => (
        <div key={u.id} style={{display:'flex',alignItems:'center',gap:10,padding:'8px 0',borderBottom:'1px solid rgba(255,255,255,0.06)'}}>
          <span style={{flex:1,color:'rgba(255,255,255,0.7)',fontFamily:'monospace',fontSize:13}}>{u.username}</span>
          <button onClick={()=>toggle(u.id, !!u.allow_upload_share)}
            style={{
              padding:'4px 14px',borderRadius:16,fontFamily:'monospace',fontSize:11,cursor:'pointer',
              background: u.allow_upload_share ? 'rgba(78,205,196,0.15)' : 'rgba(255,255,255,0.05)',
              border: `1px solid ${u.allow_upload_share ? '#4ecdc4' : 'rgba(255,255,255,0.15)'}`,
              color: u.allow_upload_share ? '#4ecdc4' : 'rgba(255,255,255,0.4)',
            }}>
            {u.allow_upload_share ? '● Erlaubt' : '○ Gesperrt'}
          </button>
        </div>
      ))}
      <button onClick={loadLogs} style={{...S.btn('#ffe66d',true),marginTop:14,fontSize:11}}>
        📋 Nutzungsprotokoll anzeigen
      </button>
      {showLogs && (
        <div style={{marginTop:12,background:'#131316',border:'1px solid rgba(255,255,255,0.08)',borderRadius:10,padding:'12px 14px'}}>
          <div style={{...S.head,marginBottom:8}}>ALLE UPLOADS ÜBER FREIGABE-LINKS</div>
          {logs.length===0
            ? <div style={{color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:11}}>Noch keine Uploads.</div>
            : logs.map(l=>(
              <div key={l.id} style={{display:'flex',gap:10,padding:'7px 0',borderBottom:'1px solid rgba(255,255,255,0.05)',flexWrap:'wrap'}}>
                <span style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:11,flexShrink:0,minWidth:90}}>{l.username}</span>
                <div style={{flex:1,minWidth:120}}>
                  <div style={{color:'#fff',fontFamily:'monospace',fontSize:12,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{l.originalname}</div>
                  <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:10}}>{fmtDate(l.uploaded_at)} · {fmtSize(l.size)} · Max: {l.max_size_mb} MB</div>
                </div>
              </div>
            ))
          }
        </div>
      )}
    </div>
  );
}


function AdminPanel({ toast, mobile, user, nav }) {
  const [section, setSection] = useState('profil');
  useEffect(() => { if (nav?.section) setSection(nav.section); }, [nav?.ts]);
  const [pw,  setPw]  = useState({ current:'', newPw:'', confirm:'' });
  const [avatar, setAvatar] = useState(localStorage.getItem('dd_avatar') || null);
  const [un,  setUn]  = useState({ newUsername:'', unPw:'' });
  const [file, setFile] = useState(null);
  const [busy, setBusy] = useState({ pw:false, un:false, restore:false });
  const set = (key, val) => setBusy(b => ({...b, [key]:val}));

  const changePw = async () => {
    if (pw.newPw !== pw.confirm) { toast('Passwörter stimmen nicht überein', 'error'); return; }
    if (pw.newPw.length < 8)    { toast('Mindestens 8 Zeichen', 'error'); return; }
    set('pw', true);
    try { await api('/auth/change-password', { body:{ currentPassword:pw.current, newPassword:pw.newPw } }); toast('Passwort geändert'); setPw({ current:'', newPw:'', confirm:'' }); }
    catch(e) { toast(e.message, 'error'); } finally { set('pw', false); }
  };

  const changeUn = async () => {
    if (!un.newUsername.trim()) { toast('Benutzername eingeben', 'error'); return; }
    set('un', true);
    try {
      await api('/auth/change-username', { body:{ newUsername:un.newUsername.trim(), password:un.unPw } });
      toast('Benutzername geändert – bitte neu einloggen');
      setUn({ newUsername:'', unPw:'' });
      setTimeout(() => { localStorage.removeItem('sk_token'); window.location.reload(); }, 1800);
    } catch(e) { toast(e.message, 'error'); } finally { set('un', false); }
  };

  const backup = async () => {
    try {
      const blob = await api('/admin/backup');
      const a = Object.assign(document.createElement('a'), {
        href: URL.createObjectURL(blob),
        download: `dickendock-backup-${new Date().toISOString().split('T')[0]}.db`
      });
      a.click(); URL.revokeObjectURL(a.href); toast('Backup heruntergeladen');
    } catch(e) { toast(e.message, 'error'); }
  };

  const restore = async () => {
    if (!file) { toast('Datei auswählen', 'error'); return; }
    if (!window.confirm('⚠️ Aktuelle Datenbank wird ersetzt. Fortfahren?')) return;
    set('restore', true);
    try {
      const f = new FormData(); f.append('database', file);
      await api('/admin/restore', { method:'POST', body:f, isFile:true });
      toast('Wiederhergestellt'); setFile(null);
    } catch(e) { toast(e.message, 'error'); } finally { set('restore', false); }
  };

  return (
    <div style={{ padding: mobile ? '14px 12px 90px' : '36px 44px', maxWidth:560 }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:16 }}>
        <h1 style={{ color:'#fff', fontFamily:"'Space Mono',monospace", fontSize:mobile?17:22, margin:0 }}>Einstellungen</h1>
        <button onClick={() => window.location.reload()} title="Seite neu laden"
          style={{ background:'transparent', border:'1px solid rgba(255,255,255,0.1)', borderRadius:8,
            color:'rgba(255,255,255,0.4)', cursor:'pointer', padding:'6px 10px', fontSize:14, fontFamily:'monospace' }}>↺</button>
      </div>

      <div style={{ display:'flex', gap:6, marginBottom:18, flexWrap:'wrap' }}>
        {[['profil','Profil'],['sicherheit','Sicherheit'],['dashboard','Dashboard'], ...(user?.role==='admin'?[['benutzer','Benutzer'],['backup','Backup']]:[])]
          .map(([k,l]) => (
          <button key={k} onClick={()=>setSection(k)} style={{
            padding:'6px 16px', borderRadius:20, fontFamily:'monospace', fontSize:12, cursor:'pointer',
            border:`1px solid ${section===k?'rgba(78,205,196,0.5)':'rgba(255,255,255,0.15)'}`,
            background: section===k?'rgba(78,205,196,0.12)':'transparent',
            color: section===k?'#4ecdc4':'rgba(255,255,255,0.55)',
          }}>{l}</button>
        ))}
      </div>

      {section === 'profil' && (
        <div>
          <Sec title="AVATAR">
            <div style={{ display:'flex', alignItems:'center', gap:14, marginBottom:12 }}>
              <div style={{ width:60, height:60, borderRadius:'50%', overflow:'hidden', flexShrink:0,
                background:'linear-gradient(135deg,#ff6b9d,#4ecdc4)',
                display:'flex', alignItems:'center', justifyContent:'center' }}>
                {avatar
                  ? <img src={avatar} alt="Avatar" style={{width:'100%',height:'100%',objectFit:'cover'}}/>
                  : <UserIcon size={28} color="#0d0d0f"/>}
              </div>
              <div style={{flex:1}}>
                <label style={{...S.head,display:'block',marginBottom:6}}>PROFILBILD</label>
                <input type="file" accept="image/*" onChange={async e => {
                  const file = e.target.files?.[0]; if(!file) return;
                  const reader = new FileReader();
                  reader.onload = ev => {
                    const canvas = document.createElement('canvas');
                    const img = new Image(); img.onload = () => {
                      const size = 120;
                      canvas.width = size; canvas.height = size;
                      const ctx = canvas.getContext('2d');
                      ctx.beginPath(); ctx.arc(size/2,size/2,size/2,0,Math.PI*2);
                      ctx.clip();
                      const min = Math.min(img.width,img.height);
                      ctx.drawImage(img,(img.width-min)/2,(img.height-min)/2,min,min,0,0,size,size);
                      const data = canvas.toDataURL('image/jpeg',0.85);
                      setAvatar(data); localStorage.setItem('dd_avatar',data); toast('Avatar gespeichert');
                    }; img.src = ev.target.result;
                  }; reader.readAsDataURL(file);
                }} style={{...S.inp,fontSize:13,padding:'7px 10px',cursor:'pointer'}}/>
              </div>
            </div>
            {avatar && <button onClick={()=>{setAvatar(null);localStorage.removeItem('dd_avatar');toast('Avatar entfernt');}}
              style={{...S.btn('#ff6b9d',true)}}>✕ Entfernen</button>}
          </Sec>
          {user?.role==='admin' && <Sec title="BENUTZERNAME ÄNDERN">
            <Field label="NEUER BENUTZERNAME" value={un.newUsername} onChange={e=>setUn(p=>({...p,newUsername:e.target.value}))} />
            <Field label="PASSWORT ZUR BESTÄTIGUNG" type="password" value={un.unPw} onChange={e=>setUn(p=>({...p,unPw:e.target.value}))} />
            <button onClick={changeUn} disabled={busy.un} style={{ ...S.btn('#4ecdc4'), width:'100%', textAlign:'center', padding:'11px 0' }}>
              {busy.un ? '…' : '✓ Benutzername ändern'}
            </button>
          </Sec>}
          <Sec title="PASSWORT ÄNDERN">
            <Field label="AKTUELLES PASSWORT"  type="password" value={pw.current} onChange={e=>setPw(p=>({...p,current:e.target.value}))} />
            <Field label="NEUES PASSWORT"      type="password" value={pw.newPw}   onChange={e=>setPw(p=>({...p,newPw:e.target.value}))} />
            <Field label="BESTÄTIGEN"          type="password" value={pw.confirm} onChange={e=>setPw(p=>({...p,confirm:e.target.value}))} />
            <button onClick={changePw} disabled={busy.pw} style={{ ...S.btn('#4ecdc4'), width:'100%', textAlign:'center', padding:'11px 0' }}>
              {busy.pw ? '…' : '✓ Passwort ändern'}
            </button>
          </Sec>
          <Sec title="ACCOUNT LÖSCHEN">
            <p style={{ color:'rgba(255,255,255,0.55)', fontSize:12, fontFamily:'monospace', marginBottom:12, lineHeight:1.6 }}>
              Löscht deinen Account und alle deine Daten unwiderruflich.
            </p>
            <DeleteAccountSection toast={toast} user={user}/>
          </Sec>
          <Sec title="PUSHOVER BENACHRICHTIGUNGEN">
            <PushoverSettings toast={toast}/>
          </Sec>
        </div>
      )}

      {section === 'sicherheit' && (
        <SecuritySettings toast={toast}/>
      )}

      {section === 'benutzer' && user?.role==='admin' && (
        <div>
          <SecuritySettingsAdmin toast={toast}/>
          <UserManagement toast={toast}/>
          <Sec title="DATEI-MANAGER LIMITS">
            <DateiSettings toast={toast}/>
          </Sec>
          <Sec title="UPLOAD-FREIGABE">
            <UploadShareAdminPanel toast={toast}/>
          </Sec>
          <Sec title="TMDB API (KINO-FEATURE)">
            <TmdbSettings toast={toast}/>
          </Sec>
        </div>
      )}

      {section === 'dashboard' && (
        <div>
          <Sec title="SCHNELLZUGRIFF">
            <div style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:12,lineHeight:1.7}}>
              Schnellzugriff-Links werden direkt in der <strong style={{color:'#4ecdc4'}}>Linkliste</strong> verwaltet.<br/>
              Dort den ● Schnellzugriff-Button bei einem Link oder Ordner aktivieren.
            </div>
          </Sec>
          <Sec title="KALENDER (iCal)">
            <CalendarSettings toast={toast}/>
          </Sec>
        </div>
      )}

      {section === 'backup' && (
        <div>
          <Sec title="DATENBANK">
            <p style={{ color:'rgba(255,255,255,0.55)', fontSize:12, fontFamily:'monospace', marginBottom:12, lineHeight:1.6 }}>
              Vollständige SQLite-Datenbank sichern oder wiederherstellen.
            </p>
            <button onClick={backup} style={{ ...S.btn('#ffe66d'), width:'100%', textAlign:'center', padding:'11px 0', marginBottom:12 }}>
              ↓ Backup herunterladen
            </button>
            <label style={{ ...S.head, display:'block', marginBottom:4 }}>SICHERUNG EINSPIELEN (.db)</label>
            <div style={{ display:'flex', gap:8 }}>
              <input type="file" accept=".db" onChange={e => setFile(e.target.files[0])}
                style={{ ...S.inp, flex:1, padding:'9px 10px', cursor:'pointer', fontSize:13 }} />
              <button onClick={restore} disabled={busy.restore || !file}
                style={{ ...S.btn('#ff6b9d'), opacity: busy.restore || !file ? 0.4 : 1, padding:'0 14px', flexShrink:0 }}>
                {busy.restore ? '…' : '↑'}
              </button>
            </div>
            {file && <div style={{ color:'rgba(255,255,255,0.55)', fontSize:10, fontFamily:'monospace', marginTop:5 }}>{file.name}</div>}
          </Sec>
        </div>
      )}
    </div>
  );
}


// ── App Shell ─────────────────────────────────────────────────────────────────

// ── Öffentliche Upload-Seite ─────────────────────────────────────────────────

// ── Öffentliche Datei/Ordner-Download-Seite ────────────────────────────────
function PublicFileShare({ token }) {
  const [phase,    setPhase]    = useState('loading');
  const [info,     setInfo]     = useState(null);
  const [files,    setFiles]    = useState([]);
  const [pw,       setPw]       = useState('');
  const [password, setPassword] = useState('');
  const [errMsg,   setErrMsg]   = useState('');
  const [dlLoading,setDlLoading]= useState(null);

  const S2 = {
    wrap:    { minHeight:'100vh', background:'#0f1117', display:'flex', alignItems:'center', justifyContent:'center', padding:20 },
    box:     { background:'#1a1d2e', border:'1px solid rgba(255,255,255,0.08)', borderRadius:16, padding:32, maxWidth:480, width:'100%' },
    head:    { color:'#fff', fontFamily:'monospace', fontSize:18, fontWeight:700, marginBottom:6 },
    sub:     { color:'rgba(255,255,255,0.4)', fontFamily:'monospace', fontSize:12, marginBottom:24 },
    inp:     { width:'100%', boxSizing:'border-box', background:'rgba(255,255,255,0.06)', border:'1px solid rgba(255,255,255,0.12)', borderRadius:8, padding:'10px 14px', color:'#fff', fontFamily:'monospace', fontSize:13, outline:'none' },
    btn:     (c='#4ecdc4') => ({ background:`${c}18`, border:`1px solid ${c}44`, borderRadius:8, padding:'10px 16px', color:c, fontFamily:'monospace', fontSize:13, cursor:'pointer', width:'100%', marginTop:10 }),
    fileRow: { display:'flex', alignItems:'center', gap:12, padding:'10px 14px', background:'rgba(255,255,255,0.04)', border:'1px solid rgba(255,255,255,0.07)', borderRadius:8, marginBottom:8 },
    err:     { color:'#f87171', fontFamily:'monospace', fontSize:12, marginTop:8 },
  };

  const BASE = '/api/public/file-share/' + token;

  useEffect(() => {
    fetch(BASE)
      .then(async r => {
        const d = await r.json();
        if (!r.ok) { setErrMsg(d.error || 'Link ungültig'); setPhase('error'); return; }
        setInfo(d);
        setPhase('pw'); // Passwort immer Pflicht
      })
      .catch(() => { setErrMsg('Verbindung fehlgeschlagen'); setPhase('error'); });
  }, [token]);

  async function handlePw() {
    if (!pw.trim()) return;
    const url = BASE + (info?.is_folder ? '/folder' : '/download');
    const res = await fetch(url, {
      method:'POST', headers:{'Content-Type':'application/json'},
      body: JSON.stringify({ password: pw }),
    });
    const d = await res.json();
    if (!res.ok) { setErrMsg(d.error || 'Falsches Passwort'); return; }
    setPassword(pw);
    setErrMsg('');
    if (info?.is_folder) { setFiles(d.files || []); setPhase('ready'); }
    else { setPhase('ready'); }
  }

  async function downloadFile(fileId, fileName) {
    setDlLoading(fileId || 'single');
    try {
      const url = BASE + (fileId ? '/folder/' + fileId : '/download');
      const res = await fetch(url, {
        method:'POST', headers:{'Content-Type':'application/json'},
        body: JSON.stringify({ password }),
      });
      if (!res.ok) { const d = await res.json(); setErrMsg(d.error); return; }
      const blob = await res.blob();
      const objUrl = URL.createObjectURL(blob);
      const a = document.createElement('a'); a.href = objUrl; a.download = fileName;
      document.body.appendChild(a); a.click(); document.body.removeChild(a);
      setTimeout(() => URL.revokeObjectURL(objUrl), 5000);
    } finally { setDlLoading(null); }
  }

  function fmtSize(b) {
    if (!b) return '';
    if (b < 1024) return b + ' B';
    if (b < 1024*1024) return (b/1024).toFixed(1) + ' KB';
    return (b/1024/1024).toFixed(1) + ' MB';
  }

  // Abgelaufen/ungültig → Redirect zu google.de
  if (phase === 'error') {
    window.location.replace('https://www.google.de');
    return <div style={S2.wrap}><div style={{color:'#fff',fontFamily:'monospace'}}>Weiterleitung…</div></div>;
  }

  return (
    <div style={S2.wrap}>
      <div style={S2.box}>
        <div style={S2.head}>🔗 {info?.is_folder ? '📁 Ordner' : '📄 Datei'} – Freigabe</div>
        <div style={S2.sub}>
          {info?.folder_name || info?.file_name || info?.label || 'Öffentlicher Link'}
          {info?.expires_at && (
            <span style={{marginLeft:8}}>
              · Gültig bis {new Date(info.expires_at*1000).toLocaleDateString('de-DE')}
            </span>
          )}
        </div>

        {phase === 'loading' && <div style={S2.sub}>Lade...</div>}

        {phase === 'pw' && (
          <>
            <span style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:11,marginBottom:6,display:'block'}}>Passwort</span>
            <input value={pw} onChange={e=>setPw(e.target.value)}
              onKeyDown={e=>e.key==='Enter'&&handlePw()} autoFocus
              placeholder="Passwort eingeben" style={S2.inp} />
            {errMsg && <div style={S2.err}>⚠ {errMsg}</div>}
            <button onClick={handlePw} style={S2.btn('#4ecdc4')}>🔓 Entsperren</button>
          </>
        )}

        {phase === 'ready' && !info?.is_folder && (
          <>
            <div style={{...S2.fileRow, marginBottom:16}}>
              <span style={{fontSize:24}}>📄</span>
              <div>
                <div style={{color:'#fff',fontFamily:'monospace',fontSize:13}}>{info.file_name}</div>
                <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:11}}>{fmtSize(info.file_size)}</div>
              </div>
            </div>
            <button onClick={() => downloadFile(null, info.file_name)} disabled={dlLoading==='single'}
              style={S2.btn('#4ade80')}>
              {dlLoading==='single' ? '⏳ Lädt...' : '⬇ Datei herunterladen'}
            </button>
          </>
        )}

        {phase === 'ready' && info?.is_folder && (
          <>
            <div style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:11,marginBottom:12}}>
              {files.length} Datei{files.length!==1?'en':''} im Ordner
            </div>
            {files.map(f => (
              <div key={f.id} style={S2.fileRow}>
                <span style={{fontSize:18}}>📄</span>
                <div style={{flex:1}}>
                  <div style={{color:'#fff',fontFamily:'monospace',fontSize:12}}>{f.name}</div>
                  <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10}}>{fmtSize(f.size)}</div>
                </div>
                <button onClick={()=>downloadFile(f.id, f.name)} disabled={!!dlLoading}
                  style={{...S2.btn('#4ade80'), width:'auto', marginTop:0, padding:'6px 14px', fontSize:11}}>
                  {dlLoading===f.id ? '⏳' : '⬇'}
                </button>
              </div>
            ))}
            {files.length===0 && <div style={S2.sub}>Ordner ist leer.</div>}
          </>
        )}
      </div>
    </div>
  );
}


function PublicUpload({ token }) {
  const [phase,    setPhase]    = useState('loading');
  const [info,     setInfo]     = useState(null);
  const [errorMsg, setErrorMsg] = useState('');
  const [pw,       setPw]       = useState('');
  const [password, setPassword] = useState('');
  const [uploading,setUploading]= useState(false);
  const [done,     setDone]     = useState([]);
  const [msg,      setMsg]      = useState(null);
  const [attLeft,  setAttLeft]  = useState(null);
  const [dragging, setDragging] = useState(false);
  const fileRef = useRef(null);

  useEffect(() => {
    fetch('/api/upload-shares/public/' + token)
      .then(async r => {
        const d = await r.json();
        if (!r.ok) { setErrorMsg(d.error||'Link ungültig'); setPhase('error'); return; }
        setInfo(d); setPhase('pw');
      })
      .catch(e => { setErrorMsg('Verbindung fehlgeschlagen: '+e.message); setPhase('error'); });
  }, [token]);

  const verify = async () => {
    if (!pw.trim()) return;
    try {
      const r = await fetch('/api/upload-shares/public/'+token+'/verify', {
        method:'POST', headers:{'Content-Type':'application/json'},
        body: JSON.stringify({ password: pw })
      });
      const d = await r.json();
      if (!r.ok) {
        setMsg({text:d.error,type:'error'});
        if (d.locked) { setPhase('error'); setErrorMsg(d.error); return; }
        if (d.attempts_left!=null) setAttLeft(d.attempts_left);
        return;
      }
      setPassword(pw); setInfo(d); setPhase('upload'); setMsg(null);
    } catch(e) { setMsg({text:'Netzwerkfehler: '+e.message,type:'error'}); }
  };

  const doUpload = async (droppedFiles) => {
    const files = droppedFiles || fileRef.current?.files;
    if (!files?.length) { fileRef.current?.click(); return; }
    setUploading(true); setMsg(null);
    const newDone = [];
    for (const file of files) {
      if (file.size > info.max_size_mb * 1024 * 1024) {
        setMsg({text:`"${file.name}" zu groß (max ${info.max_size_mb} MB)`,type:'warn'}); continue;
      }
      const fd = new FormData(); fd.append('file', file);
      try {
        const r = await fetch('/api/upload-shares/public/'+token+'/upload', {
          method:'POST', headers:{'x-upload-password': password}, body:fd
        });
        const d = await r.json();
        if (r.ok) newDone.push(file.name);
        else setMsg({text:d.error,type:'error'});
      } catch(e) { setMsg({text:e.message,type:'error'}); }
    }
    if (newDone.length) {
      setDone(p=>[...p,...newDone]);
      setMsg({text:newDone.length+' Datei'+(newDone.length!==1?'en':'')+' hochgeladen ✓',type:'ok'});
    }
    if (fileRef.current) fileRef.current.value='';
    setUploading(false);
  };

  const onDragOver = e => { e.preventDefault(); setDragging(true); };
  const onDragLeave = e => { if (!e.currentTarget.contains(e.relatedTarget)) setDragging(false); };
  const onDrop = e => {
    e.preventDefault(); setDragging(false);
    if (!fileRef.current) return;
    // DataTransfer.files is read-only – assign via input to trigger React update
    const dt = e.dataTransfer;
    if (!dt.files.length) return;
    // Assign dropped files to the file input
    Object.defineProperty(fileRef.current, 'files', { value: dt.files, writable: true });
    fileRef.current.files = dt.files;
    // Trigger display update by reading back
    setMsg({text:`${dt.files.length} Datei${dt.files.length!==1?'en':''} ausgewählt`,type:'ok'});
    doUpload(dt.files);
  };

  const fmtDate = d => new Date(d).toLocaleString('de-DE',{timeZone:'Europe/Berlin',day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'});

  const C = {
    page: {minHeight:'100vh',background:'#0d0d0f',display:'flex',alignItems:'center',justifyContent:'center',padding:20},
    card: {background:'#1a1a1e',border:'1px solid rgba(255,255,255,0.1)',borderRadius:16,padding:28,width:'100%',maxWidth:440},
    inp:  {width:'100%',background:'rgba(255,255,255,0.06)',border:'1px solid rgba(255,255,255,0.12)',borderRadius:8,padding:'10px 12px',color:'#fff',fontFamily:"'Courier New',monospace",fontSize:14,outline:'none',boxSizing:'border-box'},
    infoBox: {background:'rgba(78,205,196,0.08)',border:'1px solid rgba(78,205,196,0.2)',borderRadius:8,padding:'10px 12px',fontSize:12,color:'rgba(255,255,255,0.6)',marginBottom:14},
    btn: {width:'100%',background:'#4ecdc4',border:'none',borderRadius:8,color:'#0d0d0f',cursor:'pointer',fontFamily:"'Courier New',monospace",fontSize:14,fontWeight:700,padding:12,marginTop:12},
    msg: t=>({borderRadius:8,padding:'10px 12px',fontSize:13,marginTop:10,...(t==='ok'?{background:'rgba(78,205,196,0.12)',border:'1px solid rgba(78,205,196,0.3)',color:'#4ecdc4'}:t==='warn'?{background:'rgba(255,230,109,0.12)',border:'1px solid rgba(255,230,109,0.3)',color:'#ffe66d'}:{background:'rgba(255,107,157,0.12)',border:'1px solid rgba(255,107,157,0.3)',color:'#ff6b9d'})}),
    dropZone: active=>({border:`2px dashed ${active?'#4ecdc4':'rgba(255,255,255,0.18)'}`,borderRadius:12,padding:'28px 16px',textAlign:'center',cursor:'pointer',transition:'border-color 0.15s, background 0.15s',background:active?'rgba(78,205,196,0.07)':'rgba(255,255,255,0.02)',marginBottom:12}),
  };

  return (
    <div style={{...C.page,fontFamily:"'Courier New',monospace"}}>
      <div style={C.card}>
        <h1 style={{fontSize:18,color:'#4ecdc4',margin:'0 0 4px'}}>📤 Datei hochladen</h1>


        {phase==='loading' && <div style={C.infoBox}>⏳ Link wird geprüft…</div>}
        {phase==='error'   && (() => { window.location.replace('https://www.google.de'); return <div style={C.msg('error')}>Weiterleitung…</div>; })()}

        {phase==='pw' && info && <>
          <div style={C.infoBox}>🔐 Gültig bis {fmtDate(info.expires_at)} · max {info.max_size_mb} MB pro Datei</div>
          <div style={{color:'rgba(255,255,255,0.45)',fontSize:10,letterSpacing:2,marginBottom:4}}>PASSWORT</div>
          <input type="password" value={pw} onChange={e=>setPw(e.target.value)}
            onKeyDown={e=>e.key==='Enter'&&verify()} placeholder="Passwort eingeben"
            autoCapitalize="none" autoComplete="off" style={C.inp}/>
          {attLeft!=null && <div style={{color:'#ffe66d',fontSize:11,marginTop:6}}>⚠ Noch {attLeft} Versuch{attLeft===1?'':'e'} übrig</div>}
          <button onClick={verify} style={C.btn}>Zugang bestätigen</button>
          {msg && <div style={C.msg(msg.type)}>{msg.text}</div>}
        </>}

        {phase==='upload' && info && <>
          <div style={C.infoBox}>✓ Zugang OK · max {info.max_size_mb} MB pro Datei · bis {fmtDate(info.expires_at)}</div>
          <div
            style={C.dropZone(dragging)}
            onDragOver={onDragOver}
            onDragLeave={onDragLeave}
            onDrop={onDrop}
            onClick={()=>fileRef.current?.click()}
          >
            <div style={{fontSize:28,marginBottom:6}}>{dragging?'📂':'📁'}</div>
            <div style={{color:dragging?'#4ecdc4':'rgba(255,255,255,0.5)',fontSize:13}}>
              {dragging?'Loslassen zum Hochladen':'Dateien hierher ziehen oder klicken'}
            </div>
            <div style={{color:'rgba(255,255,255,0.25)',fontSize:11,marginTop:4}}>max {info.max_size_mb} MB pro Datei</div>
          </div>
          <input type="file" ref={fileRef} multiple onChange={()=>doUpload()} style={{display:'none'}}/>
          <button onClick={()=>doUpload()} disabled={uploading} style={{...C.btn,opacity:uploading?0.5:1}}>
            {uploading?'⏳ Lädt hoch…':'⬆ Hochladen'}
          </button>
          {msg && <div style={C.msg(msg.type)}>{msg.text}</div>}
          {done.map((f,i)=><div key={i} style={{color:'rgba(78,205,196,0.7)',fontSize:11,padding:'2px 0'}}>✓ {f}</div>)}
        </>}
      </div>
    </div>
  );
}

export default function App() {
  // Öffentliche Upload-Seite – kein Login, kein Auth
  const _uploadMatch = window.location.pathname.match(/^\/u\/(.+)$/);
  if (_uploadMatch) return <PublicUpload token={_uploadMatch[1]}/>;
  const _shareMatch = window.location.pathname.match(/^\/s\/(.+)$/);
  if (_shareMatch) return <PublicFileShare token={_shareMatch[1]}/>;

  const mobile = useIsMobile();
  const [splash, setSplash] = useState(() => {
    // Only show splash in standalone PWA mode, and only once per session
    return window.matchMedia('(display-mode: standalone)').matches && !sessionStorage.getItem('splash_shown');
  });
  const [user,setUser]=useState(null);
  const [active,setActiveRaw]=useState('dashboard');
  const [devToolsNav, setDevToolsNav] = useState(null);
  const setActive = (page) => {
    // Reload nur beim Klick auf Dashboard und nur wenn ein neuer Build vorliegt
    // und keine Eingabe aktiv ist (schützt ungespeicherte Eingaben)
    if (page === 'dashboard' && window.__newBuildAvailable && window.__safeReloadIfNewBuild?.()) return;
    setActiveRaw(page);
  };
  const [toastMsg,setToastMsg]=useState({msg:'',type:'success'});
  const [unreadMsgs,  setUnreadMsgs]  = useState(0);
  const [unreadBoard,    setUnreadBoard]    = useState(0);
  const [unreadPromoted, setUnreadPromoted] = useState(0);
  const [unreadChangelog,setUnreadChangelog]= useState(0);

  const [authChecked, setAuthChecked] = useState(false);
  useEffect(()=>{
    const token=localStorage.getItem('sk_token');
    if(token){
      try{const p=JSON.parse(atob(token.split('.')[1]));
        if(p.exp*1000>Date.now())setUser({id:p.id,username:p.username,role:p.role});
        else localStorage.removeItem('sk_token');
      }catch{localStorage.removeItem('sk_token');}
    }
    setAuthChecked(true);
  },[]);

  useEffect(()=>{
    if(!user)return;
  },[user]);

  // Schlüssel sofort nach Login initialisieren und Public Key registrieren
  useEffect(()=>{
    if(!user)return;
    getOrCreateKeyPair().then(async kp=>{
      try{
        const jwk=await getPublicKeyJwk(kp);
        await api('/tools/nachrichten/keys',{body:{public_key:JSON.stringify(jwk)}});
      }catch{}
    }).catch(()=>{});
  },[user]);

  useEffect(()=>{
    if(!user)return;
    const poll=()=>api('/dashboard/board/unread').then(d=>{ setUnreadBoard(d.unread||0); setUnreadPromoted(d.unreadPromoted||0); }).catch(()=>{});
    poll();
    const t=setInterval(poll,30*1000);
    return()=>clearInterval(t);
  },[user]);

  useEffect(()=>{
    if(!user)return;
    const poll=()=>api('/dashboard/changelog/unread').then(d=>setUnreadChangelog(d.unread||0)).catch(()=>{});
    poll();
    const t=setInterval(poll,60*1000);
    return()=>clearInterval(t);
  },[user]);

  useEffect(()=>{
    if(!user)return;
    const poll=()=>api('/tools/nachrichten/unread').then(d=>setUnreadMsgs(d.count||0)).catch(()=>{});
    poll();
    const t=setInterval(poll,30*1000);
    return()=>clearInterval(t);
  },[user]);

  const toast=useCallback((msg,type='success')=>{
    setToastMsg({msg,type});
    setTimeout(()=>setToastMsg({msg:'',type:'success'}),3500);
  },[]);

  if(splash) return <SplashScreen onDone={()=>{ setSplash(false); sessionStorage.setItem('splash_shown','1'); }}/>;
  if(!authChecked) return <div style={{background:'#0d0d0f',minHeight:'100vh'}}/>;
  if(!user) return <Login onLogin={u=>setUser(u)}/>;

  const activeTool = TOOLS.find(t=>t.id===active);

  // Mobile padding bottom für fixed nav
  const isNachrichten = active === 'nachrichten';
  const mainStyle = {
    flex:1,
    overflowY: 'auto',
    ...(mobile ? { paddingBottom:'calc(56px + env(safe-area-inset-bottom, 0px))' } : {}),
  };

  return(
    <>
      <style>{`
        @import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');
        *{margin:0;padding:0;box-sizing:border-box;}
        html,body{background:#0d0d0f;-webkit-tap-highlight-color:transparent;}
        input,textarea,select{-webkit-appearance:none;appearance:none;}
        input[type=number]::-webkit-inner-spin-button{opacity:0.25;}
        @keyframes fadeIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}
        ::-webkit-scrollbar{width:3px;}::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.1);border-radius:2px;}
        select option,textarea{background:#111114;}
      `}</style>
      <div style={{display:'flex',minHeight:'100vh',background:'#111114'}}>
        {!mobile && (
          <Sidebar active={active} setActive={setActive} user={user}
            onLogout={()=>{localStorage.removeItem('sk_token');setUser(null);}}
           
            unreadMsgs={unreadMsgs}/>
        )}
        <main style={mainStyle}>
          {active==='dashboard' && <Dashboard toast={toast} mobile={mobile} setActive={setActive} setDevToolsNav={setDevToolsNav} unreadMsgs={unreadMsgs} unreadBoard={unreadBoard} unreadPromoted={unreadPromoted} onBoardRead={()=>{ setUnreadBoard(0); setUnreadPromoted(0); }} unreadChangelog={unreadChangelog} onChangelogRead={()=>setUnreadChangelog(0)} user={user}/>}
          {active==='admin' && <AdminPanel toast={toast} mobile={mobile} user={user} nav={devToolsNav}/>}
          {activeTool && <activeTool.component toast={toast} mobile={mobile} setActive={setActive} {...(activeTool.id==='devtools'?{nav:devToolsNav}:{})}/>}
        </main>
      </div>
      {mobile && (
        <BottomNav active={active} setActive={setActive} user={user}
          onLogout={()=>{localStorage.removeItem('sk_token');setUser(null);}}
         
          unreadMsgs={unreadMsgs}/>
      )}
      <Toast msg={toastMsg.msg} type={toastMsg.type}/>
    </>
  );
}
