import { useState, useRef, useEffect, useCallback } from 'react';
import { S, api } from '../lib.js';
import QRCode from 'qrcode';

const MONTHS   = ['Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'];
const WEEKDAYS = ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'];

function parseField(val) {
  val = String(val).trim();
  if (val==='*'||val==='?') return {type:'all'};
  if (val.startsWith('*/')) { const n=parseInt(val.slice(2)); return {type:'step',step:n}; }
  if (val.includes('/')&&!val.startsWith('*/')) { const [s,st]=val.split('/'); return {type:'stepFrom',from:parseInt(s),step:parseInt(st)}; }
  if (val.includes(',')) return {type:'list',vals:val.split(',').map(v=>parseInt(v))};
  if (val.includes('-')) { const [a,b]=val.split('-').map(Number); return {type:'range',from:a,to:b}; }
  const n=parseInt(val); if (!isNaN(n)) return {type:'single',val:n};
  return {type:'raw',val};
}

function describeField(f, names) {
  switch(f.type) {
    case 'all':      return null;
    case 'step':     return `alle ${f.step}`;
    case 'stepFrom': return `ab ${f.from} alle ${f.step}`;
    case 'list':     return f.vals.map(v=>names?.[v]||v).join(', ');
    case 'range':    return `${names?.[f.from]||f.from}–${names?.[f.to]||f.to}`;
    case 'single':   return names?.[f.val] || String(f.val);
    default:         return f.val;
  }
}

function explainCron(expr) {
  const parts = expr.trim().split(/\s+/);
  const specials = {'@yearly':'0 0 1 1 *','@annually':'0 0 1 1 *','@monthly':'0 0 1 * *',
    '@weekly':'0 0 * * 0','@daily':'0 0 * * *','@midnight':'0 0 * * *','@hourly':'0 * * * *'};
  if (parts.length===1) {
    if (parts[0]==='@reboot') return {ok:true,human:'Beim Systemstart',fields:null};
    if (specials[parts[0]]) return explainCron(specials[parts[0]]);
  }
  if (parts.length!==5) return {ok:false,error:'Ein Crontab besteht aus 5 Feldern: Minute Stunde Tag Monat Wochentag'};
  const [minP,hrP,domP,monP,dowP] = parts;
  const min=parseField(minP), hr=parseField(hrP), dom=parseField(domP), mon=parseField(monP), dow=parseField(dowP);
  const out = [];
  if (hr.type==='all'&&min.type==='all') out.push('Jede Minute');
  else if (hr.type==='all'&&min.type==='step') out.push(`Alle ${min.step} Minuten`);
  else if (hr.type==='all') out.push(`Jede Stunde um :${String(describeField(min)).padStart(2,'0')}`);
  else if (min.type==='all') out.push(`Jede Minute der Stunde(n) ${describeField(hr)}`);
  else { const m=describeField(min), h=describeField(hr); out.push(`${String(h).padStart(2,'0')}:${String(m).padStart(2,'0')} Uhr`); }
  if (dow.type!=='all') out.push(`jeden ${describeField(dow,WEEKDAYS)}`);
  if (dom.type!=='all') { const d=describeField(dom); out.push(`am ${d}. des Monats`); }
  if (mon.type!=='all') out.push(`im ${describeField(mon,MONTHS)}`);
  return {ok:true,human:out.join(', '),fields:{min,hr,dom,mon,dow},raw:parts};
}

function nextRuns(expr, count=5) {
  try {
    const parts = expr.trim().split(/\s+/);
    if (parts.length!==5) return [];
    const [minP,hrP,domP,monP,dowP] = parts;
    const fMin=parseField(minP), fHr=parseField(hrP), fDom=parseField(domP), fMon=parseField(monP), fDow=parseField(dowP);
    const match = (f, v) => {
      switch(f.type){
        case 'all': return true;
        case 'single': return v===f.val||(f.val===7&&v===0);
        case 'range': return v>=f.from&&v<=f.to;
        case 'step': return v%f.step===0;
        case 'stepFrom': return v>=f.from&&(v-f.from)%f.step===0;
        case 'list': return f.vals.includes(v);
        default: return false;
      }
    };
    const results=[], d=new Date();
    d.setSeconds(0); d.setMilliseconds(0); d.setMinutes(d.getMinutes()+1);
    for (let i=0;i<50000&&results.length<count;i++){
      if (match(fMon,d.getMonth()+1)&&match(fDom,d.getDate())&&
          (fDow.type==='all'||match(fDow,d.getDay()))&&
          match(fHr,d.getHours())&&match(fMin,d.getMinutes())) results.push(new Date(d));
      d.setMinutes(d.getMinutes()+1);
    }
    return results;
  } catch { return []; }
}

// ── Erklärer ──────────────────────────────────────────────────────────────────
function CronExplainer() {
  const [input, setInput] = useState('');
  const expr = input.trim();
  const result = expr ? explainCron(expr) : null;
  const runs = result?.ok ? nextRuns(expr) : [];
  const fmtDt = d => d.toLocaleString('de-DE',{weekday:'short',day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'});
  const EXAMPLES = [['0 8 * * 1','Mo 8:00'],['*/15 * * * *','alle 15 Min'],['0 0 1 * *','1. jeden Monat'],['30 18 * * 1-5','Mo–Fr 18:30'],['0 9,17 * * *','9 & 17 Uhr'],['0 0 * * 0','So 0:00']];
  return (
    <div>
      <div style={{marginBottom:10}}>
        <label style={{...S.head,display:'block',marginBottom:6,fontSize:10}}>CRONTAB AUSDRUCK</label>
        <input value={input} onChange={e=>setInput(e.target.value)} placeholder="z.B. 0 8 * * 1"
          autoCapitalize="none" style={{...S.inp,fontFamily:"'Courier New',monospace",fontSize:16}}/>
      </div>
      <div style={{display:'flex',gap:5,flexWrap:'wrap',marginBottom:16}}>
        {EXAMPLES.map(([ex,l])=>(
          <button key={ex} onClick={()=>setInput(ex)} style={{padding:'4px 10px',borderRadius:6,
            fontFamily:'monospace',fontSize:10,cursor:'pointer',
            background:'rgba(255,255,255,0.05)',border:'1px solid rgba(255,255,255,0.1)',color:'rgba(255,255,255,0.5)'}}>
            <code>{ex}</code> <span style={{color:'rgba(255,255,255,0.5)',fontSize:9}}>({l})</span>
          </button>
        ))}
      </div>
      {result && (result.ok ? (
        <div>
          <div style={{background:'rgba(78,205,196,0.08)',border:'1px solid rgba(78,205,196,0.2)',
            borderRadius:10,padding:'14px 16px',marginBottom:12}}>
            <div style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:9,marginBottom:5}}>BEDEUTET</div>
            <div style={{color:'#fff',fontFamily:'monospace',fontSize:16,fontWeight:700}}>{result.human}</div>
          </div>
          {result.fields && (
            <div style={{display:'grid',gridTemplateColumns:'repeat(5,1fr)',gap:8,marginBottom:12}}>
              {[['MIN',0],[' STD',1],['TAG',2],['MON',3],['WTAG',4]].map(([lbl,i])=>{
                const raw=expr.split(/\s+/)[i];
                const fNames=i===3?MONTHS:i===4?WEEKDAYS:null;
                const fd=Object.values(result.fields)[i];
                return (
                  <div key={lbl} style={{background:'rgba(255,255,255,0.04)',border:'1px solid rgba(255,255,255,0.07)',
                    borderRadius:8,padding:'8px 10px',textAlign:'center'}}>
                    <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:8,marginBottom:4}}>{lbl}</div>
                    <div style={{color:'#4ecdc4',fontFamily:"'Courier New',monospace",fontSize:14,fontWeight:700}}>{raw}</div>
                    <div style={{color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:9,marginTop:3}}>
                      {fd?.type==='all'?'jede/r':(describeField(fd,fNames)||'–')}
                    </div>
                  </div>
                );
              })}
            </div>
          )}
          {runs.length>0&&(
            <div>
              <div style={{...S.head,marginBottom:6,fontSize:10}}>NÄCHSTE AUSFÜHRUNGEN</div>
              {runs.map((d,i)=>(
                <div key={i} style={{display:'flex',alignItems:'center',gap:10,padding:'5px 0',borderBottom:'1px solid rgba(255,255,255,0.04)'}}>
                  <span style={{color:'rgba(78,205,196,0.5)',fontFamily:'monospace',fontSize:10,minWidth:16,textAlign:'right'}}>{i+1}.</span>
                  <span style={{color:'rgba(255,255,255,0.7)',fontFamily:'monospace',fontSize:11}}>{fmtDt(d)}</span>
                </div>
              ))}
            </div>
          )}
        </div>
      ) : (
        <div style={{background:'rgba(255,107,157,0.08)',border:'1px solid rgba(255,107,157,0.2)',
          borderRadius:8,padding:12,color:'#ff6b9d',fontFamily:'monospace',fontSize:11}}>⚠ {result.error}</div>
      ))}
    </div>
  );
}

// ── Builder ───────────────────────────────────────────────────────────────────
function CronBuilder() {
  const [minute, setMinute] = useState('0');
  const [hour,   setHour]   = useState('8');
  const [dom,    setDom]    = useState('*');
  const [month,  setMonth]  = useState('*');
  const [dow,    setDow]    = useState('*');
  const [preset, setPreset] = useState('');
  const [copied, setCopied] = useState(false);

  const cron = `${minute} ${hour} ${dom} ${month} ${dow}`;
  const result = explainCron(cron);
  const runs = result?.ok ? nextRuns(cron) : [];
  const fmtDt = d => d.toLocaleString('de-DE',{weekday:'short',day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'});

  const copy = async () => { try { await navigator.clipboard.writeText(cron); setCopied(true); setTimeout(()=>setCopied(false),1500); } catch {} };

  const PRESETS = [
    {k:'every_min',    l:'Jede Minute',   v:['*','*','*','*','*']},
    {k:'every_hour',   l:'Jede Stunde',   v:['0','*','*','*','*']},
    {k:'every_day_8',  l:'Tägl. 8:00',    v:['0','8','*','*','*']},
    {k:'every_mon',    l:'Montag 8:00',   v:['0','8','*','*','1']},
    {k:'every_fri',    l:'Freitag 17:00', v:['0','17','*','*','5']},
    {k:'weekdays_9',   l:'Mo–Fr 9:00',    v:['0','9','*','*','1-5']},
    {k:'first_month',  l:'1. des Monats', v:['0','0','1','*','*']},
    {k:'midnight',     l:'Mitternacht',   v:['0','0','*','*','*']},
  ];

  const applyPreset = p => {
    setPreset(p.k);
    const [mi,hr,dm,mo,dw] = p.v;
    setMinute(mi); setHour(hr); setDom(dm); setMonth(mo); setDow(dw);
  };

  const Lbl = ({c}) => <div style={{...S.head,marginBottom:4,fontSize:9}}>{c}</div>;
  const Sel = ({value,onChange,children}) => (
    <select value={value} onChange={e=>{onChange(e.target.value);setPreset('');}}
      style={{...S.inp,fontSize:12,padding:'6px 8px',width:'100%'}}>{children}</select>
  );

  return (
    <div>
      <div style={{marginBottom:14}}>
        <Lbl c="SCHNELLAUSWAHL"/>
        <div style={{display:'flex',gap:6,flexWrap:'wrap'}}>
          {PRESETS.map(p=>(
            <button key={p.k} onClick={()=>applyPreset(p)} style={{
              padding:'5px 11px',borderRadius:6,fontFamily:'monospace',fontSize:10,cursor:'pointer',
              background:preset===p.k?'rgba(78,205,196,0.15)':'rgba(255,255,255,0.05)',
              border:`1px solid ${preset===p.k?'rgba(78,205,196,0.3)':'rgba(255,255,255,0.1)'}`,
              color:preset===p.k?'#4ecdc4':'rgba(255,255,255,0.55)',
            }}>{p.l}</button>
          ))}
        </div>
      </div>

      <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:10,marginBottom:14}}>
        <div><Lbl c="MINUTE"/>
          <Sel value={minute} onChange={setMinute}>
            <option value="*">* jede</option>
            {[5,10,15,20,30].map(n=><option key={n} value={`*/${n}`}>alle {n} min</option>)}
            {Array.from({length:60},(_,i)=><option key={i} value={i}>{String(i).padStart(2,'0')}</option>)}
          </Sel>
        </div>
        <div><Lbl c="STUNDE"/>
          <Sel value={hour} onChange={setHour}>
            <option value="*">* jede</option>
            {Array.from({length:24},(_,i)=><option key={i} value={i}>{String(i).padStart(2,'0')}:00</option>)}
            <option value="9,17">9 & 17 Uhr</option>
            <option value="8,12,17">8, 12 & 17 Uhr</option>
          </Sel>
        </div>
        <div><Lbl c="WOCHENTAG"/>
          <Sel value={dow} onChange={setDow}>
            <option value="*">* jeder</option>
            <option value="1-5">Mo–Fr (Werktage)</option>
            <option value="6,0">Sa & So (Wochenende)</option>
            {WEEKDAYS.map((d,i)=><option key={i} value={i}>{d}</option>)}
          </Sel>
        </div>
        <div><Lbl c="TAG DES MONATS"/>
          <Sel value={dom} onChange={setDom}>
            <option value="*">* jeder</option>
            {Array.from({length:31},(_,i)=><option key={i+1} value={i+1}>{i+1}.</option>)}
            <option value="1,15">1. & 15.</option>
          </Sel>
        </div>
        <div><Lbl c="MONAT"/>
          <Sel value={month} onChange={setMonth}>
            <option value="*">* jeder</option>
            {MONTHS.map((m,i)=><option key={i+1} value={i+1}>{m}</option>)}
          </Sel>
        </div>
      </div>

      <div style={{background:'rgba(0,0,0,0.3)',border:'1px solid rgba(255,255,255,0.1)',borderRadius:10,padding:'14px 16px'}}>
        <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',marginBottom:10,flexWrap:'wrap',gap:8}}>
          <code style={{color:'#4ecdc4',fontFamily:"'Courier New',monospace",fontSize:22,fontWeight:700,letterSpacing:3}}>{cron}</code>
          <button onClick={copy} style={{
            background:copied?'rgba(78,205,196,0.15)':'rgba(255,255,255,0.06)',
            border:`1px solid ${copied?'rgba(78,205,196,0.3)':'rgba(255,255,255,0.1)'}`,
            borderRadius:7,color:copied?'#4ecdc4':'rgba(255,255,255,0.5)',
            cursor:'pointer',fontFamily:'monospace',fontSize:11,padding:'6px 14px'}}>
            {copied?'✓ Kopiert':'⎘ Kopieren'}
          </button>
        </div>
        {result?.ok&&<div style={{color:'rgba(255,255,255,0.65)',fontFamily:'monospace',fontSize:13,marginBottom:10}}>{result.human}</div>}
        {runs.length>0&&(
          <div style={{display:'flex',gap:6,flexWrap:'wrap'}}>
            {runs.slice(0,3).map((d,i)=>(
              <span key={i} style={{background:'rgba(255,255,255,0.04)',borderRadius:5,padding:'3px 8px',
                color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:10}}>{fmtDt(d)}</span>
            ))}
          </div>
        )}
      </div>
    </div>
  );
}

// ── Tool-Definitionen ─────────────────────────────────────────────────────────
// ── Elektro-Hilfskomponenten (AUSSERHALB damit keine Remounts) ────────────────
function ElektroInp({ vals, setVals, k, label, unit, placeholder }) {
  return (
    <div style={{ flex:1, minWidth:100 }}>
      <div style={{ color:'rgba(255,255,255,0.55)', fontFamily:'monospace', fontSize:9, marginBottom:3 }}>
        {label} [{unit}]
      </div>
      <input type="number" value={vals[k]||''} onChange={e=>setVals(p=>({...p,[k]:e.target.value}))}
        placeholder={placeholder||''}
        style={{ ...S.inp, fontSize:13, padding:'7px 9px', width:'100%', boxSizing:'border-box' }}/>
    </div>
  );
}

function ElektroResult({ label, value, highlight }) {
  return (
    <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center',
      padding:'8px 12px', marginBottom:5, borderRadius:8,
      background: highlight ? 'rgba(78,205,196,0.1)' : 'rgba(255,255,255,0.03)',
      border: `1px solid ${highlight ? 'rgba(78,205,196,0.25)' : 'rgba(255,255,255,0.07)'}` }}>
      <span style={{ color:'rgba(255,255,255,0.55)', fontFamily:'monospace', fontSize:11 }}>{label}</span>
      <span style={{ color: highlight ? '#4ecdc4' : '#fff', fontFamily:"'Courier New',monospace",
        fontSize:14, fontWeight:700 }}>{value}</span>
    </div>
  );
}

function ElektroSection({ title, children }) {
  return (
    <div style={{ marginBottom:20 }}>
      <div style={{ ...S.head, marginBottom:10, color:'#4ecdc4' }}>{title}</div>
      {children}
    </div>
  );
}

// ── Elektro-Rechner ───────────────────────────────────────────────────────────
function ElektroRechner({ mobile }) {
  const [vals, setVals] = useState({});
  const [preis, setPreis] = useState('0.38');

  const n   = k => { const v = parseFloat(vals[k]); return isNaN(v) ? null : v; };
  const fmt = (v, unit, digits=3) => v == null ? '—' : `${parseFloat(v.toPrecision(digits))} ${unit}`;

  // Res und Sec als direkte Aliase (kein Render-Problem, keine Inputs)

  const cols = mobile ? '1fr' : '1fr 1fr';

  // ── Ohmsches Gesetz ──
  const U1=n('u1'), I1=n('i1'), R1=n('r1');
  const ohmRes = {
    U: U1 ?? (I1!=null&&R1!=null ? I1*R1 : null),
    I: I1 ?? (U1!=null&&R1!=null ? U1/R1 : null),
    R: R1 ?? (U1!=null&&I1!=null ? U1/I1 : null),
  };

  // ── Leistung P = U * I ──
  const U2=n('u2'), I2=n('i2'), P2=n('p2'), R2=n('r2');
  const leistRes = (() => {
    if (U2!=null&&I2!=null) return { P:U2*I2, U:U2, I:I2, R:U2/I2 };
    if (P2!=null&&U2!=null) return { P:P2, I:P2/U2, U:U2, R:U2*U2/P2 };
    if (P2!=null&&I2!=null) return { P:P2, U:P2/I2, I:I2, R:P2/(I2*I2) };
    if (P2!=null&&R2!=null) return { P:P2, U:Math.sqrt(P2*R2), I:Math.sqrt(P2/R2), R:R2 };
    if (U2!=null&&R2!=null) return { P:U2*U2/R2, U:U2, I:U2/R2, R:R2 };
    if (I2!=null&&R2!=null) return { P:I2*I2*R2, U:I2*R2, I:I2, R:R2 };
    return null;
  })();

  // ── Energie & Kosten ──
  const Pw=n('pw'), Std=n('std'), Tage=n('tage');
  const kwhTag  = Pw!=null&&Std!=null  ? (Pw/1000)*Std : null;
  const kwhMonat= kwhTag!=null&&Tage!=null ? kwhTag*Tage : (kwhTag!=null ? kwhTag*30 : null);
  const kwhJahr = kwhTag!=null ? kwhTag*365 : null;
  const ep = parseFloat(preis);
  const kostenTag   = kwhTag!=null    ? kwhTag*ep    : null;
  const kostenMonat = kwhMonat!=null  ? kwhMonat*ep  : null;
  const kostenJahr  = kwhJahr!=null   ? kwhJahr*ep   : null;

  // ── Wirkungsgrad ──
  const Pa=n('pa'), Pe=n('pe');
  const eta = Pa!=null&&Pe!=null&&Pe>0 ? (Pa/Pe)*100 : null;
  const verlust = Pa!=null&&Pe!=null ? Pe-Pa : null;

  // ── Serienschaltung Widerstände ──
  const r_ser = ['rs1','rs2','rs3','rs4'].map(k=>n(k)).filter(v=>v!=null);
  const R_ser = r_ser.reduce((a,b)=>a+b, 0) || null;

  // ── Parallelschaltung Widerstände ──
  const r_par = ['rp1','rp2','rp3','rp4'].map(k=>n(k)).filter(v=>v!=null);
  const R_par = r_par.length ? 1/(r_par.reduce((a,b)=>a+1/b, 0)) : null;

  // ── Kondensator Ladestrom ──
  const Cap=n('cap'), Uc=n('uc'), Tc=n('tc');
  const Ic = Cap!=null&&Uc!=null&&Tc!=null ? Cap*(Uc/Tc) : null;

  return (
    <div>
      {/* Info */}
      <div style={{ color:'rgba(255,255,255,0.5)', fontFamily:'monospace', fontSize:9,
        marginBottom:16, lineHeight:1.8 }}>
        Felder ausfüllen → Ergebnis erscheint automatisch · Mindestens 2 bekannte Größen eingeben
      </div>

      <div style={{ display:'grid', gridTemplateColumns:cols, gap:20 }}>
        <div>
          {/* Ohmsches Gesetz */}
          <ElektroSection title="⚡ Ohmsches Gesetz  U = R · I">
            <div style={{ display:'flex', gap:8, marginBottom:10, flexWrap:'wrap' }}>
              <ElektroInp vals={vals} setVals={setVals} k="u1" label="Spannung" unit="V" placeholder="230"/>
              <ElektroInp vals={vals} setVals={setVals} k="i1" label="Strom"    unit="A" placeholder="2"/>
              <ElektroInp vals={vals} setVals={setVals} k="r1" label="Widerstand" unit="Ω" placeholder="115"/>
            </div>
            <ElektroResult label="U – Spannung"    value={fmt(ohmRes.U,'V')}  highlight={!U1&&ohmRes.U!=null}/>
            <ElektroResult label="I – Strom"       value={fmt(ohmRes.I,'A')}  highlight={!I1&&ohmRes.I!=null}/>
            <ElektroResult label="R – Widerstand"  value={fmt(ohmRes.R,'Ω')}  highlight={!R1&&ohmRes.R!=null}/>
          </ElektroSection>

          {/* Leistung */}
          <ElektroSection title="🔋 Leistung  P = U · I = U²/R = I²·R">
            <div style={{ display:'flex', gap:8, marginBottom:10, flexWrap:'wrap' }}>
              <ElektroInp vals={vals} setVals={setVals} k="u2" label="Spannung"   unit="V"/>
              <ElektroInp vals={vals} setVals={setVals} k="i2" label="Strom"      unit="A"/>
              <ElektroInp vals={vals} setVals={setVals} k="p2" label="Leistung"   unit="W"/>
              <ElektroInp vals={vals} setVals={setVals} k="r2" label="Widerstand" unit="Ω"/>
            </div>
            {leistRes ? <>
              <ElektroResult label="P – Leistung"   value={fmt(leistRes.P,'W')}   highlight={!P2}/>
              <ElektroResult label="U – Spannung"   value={fmt(leistRes.U,'V')}   highlight={!U2}/>
              <ElektroResult label="I – Strom"      value={fmt(leistRes.I,'A')}   highlight={!I2}/>
              <ElektroResult label="R – Widerstand" value={fmt(leistRes.R,'Ω')}   highlight={!R2}/>
            </> : <div style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:11}}>Mindestens 2 Werte eingeben</div>}
          </ElektroSection>

          {/* Wirkungsgrad */}
          <ElektroSection title="🔄 Wirkungsgrad  η = P_ab / P_zu">
            <div style={{ display:'flex', gap:8, marginBottom:10 }}>
              <ElektroInp vals={vals} setVals={setVals} k="pa" label="Abgabe-Leistung" unit="W" placeholder="800"/>
              <ElektroInp vals={vals} setVals={setVals} k="pe" label="Aufnahme-Leistung" unit="W" placeholder="1000"/>
            </div>
            <ElektroResult label="η – Wirkungsgrad" value={eta!=null ? `${eta.toFixed(1)} %` : '—'} highlight={eta!=null}/>
            <ElektroResult label="Verlustleistung"  value={fmt(verlust,'W')}/>
          </ElektroSection>
        </div>

        <div>
          {/* Stromkosten */}
          <ElektroSection title="💶 Stromkosten">
            <div style={{ display:'flex', gap:8, marginBottom:10, flexWrap:'wrap' }}>
              <ElektroInp vals={vals} setVals={setVals} k="pw"   label="Leistung"       unit="W"   placeholder="100"/>
              <ElektroInp vals={vals} setVals={setVals} k="std"  label="Stunden/Tag"    unit="h"   placeholder="5"/>
              <ElektroInp vals={vals} setVals={setVals} k="tage" label="Tage/Monat"     unit="d"   placeholder="30"/>
            </div>
            <div style={{ marginBottom:8 }}>
              <div style={{ color:'rgba(255,255,255,0.55)', fontFamily:'monospace', fontSize:9, marginBottom:3 }}>
                Strompreis [€/kWh]
              </div>
              <input type="number" value={preis} onChange={e=>setPreis(e.target.value)}
                step="0.01" min="0"
                style={{ ...S.inp, fontSize:13, padding:'7px 9px', width:'100%', boxSizing:'border-box' }}/>
            </div>
            <ElektroResult label="kWh/Tag"          value={kwhTag!=null   ? `${kwhTag.toFixed(3)} kWh`     : '—'}/>
            <ElektroResult label="Kosten/Tag"        value={kostenTag!=null  ? `${kostenTag.toFixed(3)} €`   : '—'}/>
            <ElektroResult label="kWh/Monat"         value={kwhMonat!=null ? `${kwhMonat.toFixed(2)} kWh`    : '—'}/>
            <ElektroResult label="Kosten/Monat"      value={kostenMonat!=null ? `${kostenMonat.toFixed(2)} €`: '—'} highlight={kostenMonat!=null}/>
            <ElektroResult label="kWh/Jahr"          value={kwhJahr!=null  ? `${kwhJahr.toFixed(1)} kWh`    : '—'}/>
            <ElektroResult label="Kosten/Jahr"       value={kostenJahr!=null ? `${kostenJahr.toFixed(2)} €` : '—'} highlight={kostenJahr!=null}/>
          </ElektroSection>

          {/* Widerstände */}
          <ElektroSection title="🔗 Widerstandsschaltung">
            <div style={{ marginBottom:8 }}>
              <div style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:9, marginBottom:5 }}>SERIE (R = R1+R2+…)</div>
              <div style={{ display:'flex', gap:6, flexWrap:'wrap', marginBottom:8 }}>
                {['rs1','rs2','rs3','rs4'].map(k=><ElektroInp vals={vals} setVals={setVals} key={k} k={k} label={`R${k.slice(2)}`} unit="Ω"/>)}
              </div>
              <ElektroResult label="Gesamtwiderstand (Serie)" value={r_ser.length>=2 ? fmt(R_ser,'Ω') : '—'} highlight={r_ser.length>=2}/>
            </div>
            <div>
              <div style={{ color:'rgba(255,255,255,0.3)', fontFamily:'monospace', fontSize:9, marginBottom:5 }}>PARALLEL (1/R = 1/R1+1/R2+…)</div>
              <div style={{ display:'flex', gap:6, flexWrap:'wrap', marginBottom:8 }}>
                {['rp1','rp2','rp3','rp4'].map(k=><ElektroInp vals={vals} setVals={setVals} key={k} k={k} label={`R${k.slice(2)}`} unit="Ω"/>)}
              </div>
              <ElektroResult label="Gesamtwiderstand (Parallel)" value={r_par.length>=2 ? fmt(R_par,'Ω') : '—'} highlight={r_par.length>=2}/>
            </div>
          </ElektroSection>

          {/* Kondensator */}
          <ElektroSection title="⚡ Kondensator  I = C · (ΔU/Δt)">
            <div style={{ display:'flex', gap:8, flexWrap:'wrap', marginBottom:10 }}>
              <ElektroInp vals={vals} setVals={setVals} k="cap" label="Kapazität"  unit="F"   placeholder="0.001"/>
              <ElektroInp vals={vals} setVals={setVals} k="uc"  label="Spannung"   unit="V"   placeholder="230"/>
              <ElektroInp vals={vals} setVals={setVals} k="tc"  label="Zeit Δt"    unit="s"   placeholder="0.02"/>
            </div>
            <ElektroResult label="I – Ladestrom" value={fmt(Ic,'A')} highlight={Ic!=null}/>
          </ElektroSection>
        </div>
      </div>

      {/* Reset */}
      <button onClick={()=>setVals({})}
        style={{ ...S.btn('#ff6b9d',true), marginTop:8, fontSize:11, padding:'6px 16px' }}>
        ✕ Alle Felder leeren
      </button>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────── Datetime ──
// Moseszeit-Formel: moses + 94687200 = Unix-Timestamp (Sekunden)
// Anzeige immer als Europe/Berlin Lokalzeit (CEST=+2, CET=+1)
const MOSES_TO_UNIX_OFFSET = 94687200; // Sekunden

function pad2(n){ return String(n).padStart(2,'0'); }

function mosesToUnixMs(moses) {
  return (moses + MOSES_TO_UNIX_OFFSET) * 1000;
}

function unixMsToMoses(unixMs) {
  return Math.floor(unixMs / 1000) - MOSES_TO_UNIX_OFFSET;
}

// Date -> Berlin-Lokalzeit-String "DD.MM.YYYY HH:MM:SS"
function formatBerlin(d) {
  return d.toLocaleString('de-DE', {
    timeZone: 'Europe/Berlin',
    year:'numeric', month:'2-digit', day:'2-digit',
    hour:'2-digit', minute:'2-digit', second:'2-digit',
    hour12: false,
  }).replace(',','');
}

// Date -> ISO-String in Berlin-Zeit für datetime-local input (YYYY-MM-DDTHH:MM:SS)
function toLocalISO(d) {
  const s = d.toLocaleString('sv-SE', { timeZone: 'Europe/Berlin' }); // sv-SE gibt YYYY-MM-DD HH:MM:SS
  return s.replace(' ', 'T');
}

function formatDE(d){
  return `${pad2(d.getDate())}.${pad2(d.getMonth()+1)}.${d.getFullYear()} ${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
}

function DtRow({ label, value, muted }) {
  return (
    <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',
      padding:'7px 10px',borderRadius:7,background:'rgba(255,255,255,0.03)',marginBottom:4}}>
      <span style={{color:'rgba(255,255,255,0.55)',fontFamily:'monospace',fontSize:11}}>{label}</span>
      <code style={{color: muted ? 'rgba(255,255,255,0.55)' : '#4ecdc4',fontFamily:"'Courier New',monospace",fontSize:13,fontWeight:700}}>{value}</code>
    </div>
  );
}

function DatetimeRechner({ mobile }) {
  const now = new Date();

  // ── Tab 1: Umrechner ──
  const [conv, setConv] = useState({ unix:'', local:'', de:'' });
  const [convResult, setConvResult] = useState(null);

  function convFromUnix(val) {
    const n = parseInt(val);
    if (isNaN(n)) { setConvResult(null); return; }
    const unixMs = n * 1000;
    const d = new Date(unixMs);
    const moses = unixMsToMoses(unixMs);
    setConvResult({
      de:    formatBerlin(d),
      local: toLocalISO(d),
      unix:  String(n),
      moses: String(moses),
      wday:  d.toLocaleDateString('de-DE', { timeZone:'Europe/Berlin', weekday:'long' }),
      kw:    getKW(d),
    });
  }

  function convFromMoses(val) {
    const n = parseInt(val);
    if (isNaN(n)) { setConvResult(null); return; }
    const unixMs = mosesToUnixMs(n);
    const d = new Date(unixMs);
    setConvResult({
      de:    formatBerlin(d),
      local: toLocalISO(d),
      unix:  String(Math.floor(unixMs/1000)),
      moses: String(n),
      wday:  d.toLocaleDateString('de-DE', { timeZone:'Europe/Berlin', weekday:'long' }),
      kw:    getKW(d),
    });
  }

  function convFromLocal(val) {
    if (!val) { setConvResult(null); return; }
    // datetime-local gibt lokale Browser-Zeit — wir interpretieren als Europe/Berlin
    const d = new Date(val); // browser local
    if (isNaN(d)) { setConvResult(null); return; }
    const unixMs = d.getTime();
    setConvResult({
      de:    formatBerlin(d),
      local: val,
      unix:  String(Math.floor(unixMs/1000)),
      moses: String(unixMsToMoses(unixMs)),
      wday:  d.toLocaleDateString('de-DE', { timeZone:'Europe/Berlin', weekday:'long' }),
      kw:    getKW(d),
    });
  }

  function getKW(d) {
    const tmp = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
    tmp.setUTCDate(tmp.getUTCDate() + 4 - (tmp.getUTCDay()||7));
    const y = new Date(Date.UTC(tmp.getUTCFullYear(),0,1));
    return Math.ceil((((tmp-y)/86400000)+1)/7);
  }

  // ── Tab 2: Geburtstag ──
  const [bday, setBday] = useState('');
  const [bdayResult, setBdayResult] = useState(null);

  function calcBday(val) {
    setBday(val);
    if (!val) { setBdayResult(null); return; }
    const birth = new Date(val);
    if (isNaN(birth)) { setBdayResult(null); return; }
    const today = new Date();
    let ageY = today.getFullYear() - birth.getFullYear();
    let ageM = today.getMonth() - birth.getMonth();
    let ageD = today.getDate() - birth.getDate();
    if (ageD < 0) { ageM--; const prev = new Date(today.getFullYear(), today.getMonth(), 0); ageD += prev.getDate(); }
    if (ageM < 0) { ageY--; ageM += 12; }

    const nextBday = new Date(today.getFullYear(), birth.getMonth(), birth.getDate());
    if (nextBday <= today) nextBday.setFullYear(today.getFullYear()+1);
    const diffMs = nextBday - today;
    const diffD  = (diffMs / 86400000).toFixed(1);
    const totalD = ((today - birth) / 86400000).toFixed(1);

    setBdayResult({
      ageStr: `${ageY} Jahre, ${ageM} Monate, ${ageD} Tage`,
      diffD, totalD,
      wday: WEEKDAYS[birth.getDay()],
      geburtstag: `${pad2(birth.getDate())}.${pad2(birth.getMonth()+1)}.${birth.getFullYear()}`,
      nextDate: `${pad2(nextBday.getDate())}.${pad2(nextBday.getMonth()+1)}.${nextBday.getFullYear()} (${WEEKDAYS[nextBday.getDay()]})`,
    });
  }

  // ── Tab 4: In X Stunden/Minuten ──
  const [inH, setInH] = useState('');
  const [inM, setInM] = useState('');
  const inResult = (() => {
    const h = parseInt(inH) || 0;
    const m = parseInt(inM) || 0;
    if (h === 0 && m === 0) return null;
    const future = new Date(Date.now() + (h * 60 + m) * 60 * 1000);
    return {
      de:   future.toLocaleDateString('de-DE', { timeZone:'Europe/Berlin', weekday:'long', day:'2-digit', month:'2-digit', year:'numeric' })
          + ', ' + future.toLocaleTimeString('de-DE', { timeZone:'Europe/Berlin', hour:'2-digit', minute:'2-digit' }) + ' Uhr',
      unix: Math.floor(future.getTime() / 1000),
      total: h * 60 + m,
    };
  })();

  // ── Tab 3: Zeitdifferenz ──
  const [dtA, setDtA] = useState('');
  const [dtATime, setDtATime] = useState('');
  const [dtB, setDtB] = useState('');
  const [dtBTime, setDtBTime] = useState('');
  const [diffResult, setDiffResult] = useState(null);

  function calcDiff(a, aTime, b, bTime) {
    if (!a || !b) { setDiffResult(null); return; }
    const strA = aTime ? `${a}T${aTime}:00` : `${a}T00:00:00`;
    const strB = bTime ? `${b}T${bTime}:00` : `${b}T00:00:00`;
    const dA = new Date(strA), dB = new Date(strB);
    if (isNaN(dA)||isNaN(dB)) { setDiffResult(null); return; }
    const hasTime = !!(aTime || bTime);
    const ms = Math.abs(dB - dA);
    const s  = (ms / 1000).toFixed(1);
    const m  = (ms / 60000).toFixed(1);
    const h  = (ms / 3600000).toFixed(2);
    const d  = (ms / 86400000).toFixed(2);
    const weeks  = (ms / (86400000*7)).toFixed(2);
    const dA2 = new Date(Math.min(dA,dB)), dB2 = new Date(Math.max(dA,dB));
    const months = Math.abs(dB2.getMonth()-dA2.getMonth() + (dB2.getFullYear()-dA2.getFullYear())*12);
    setDiffResult({ ms, s, m, h, d, weeks, months, hasTime });
  }

  const inp = { ...S.inp, fontSize:13, padding:'7px 9px', width:'100%', boxSizing:'border-box' };
  const lbl = { color:'rgba(255,255,255,0.55)', fontFamily:'monospace', fontSize:10, marginBottom:4 };

  const [tab, setTab] = useState('conv');
  const tabs = [{id:'conv',label:'⇄ Umrechnen'},{id:'bday',label:'🎂 Geburtstag'},{id:'diff',label:'⏱ Differenz'},{id:'in',label:'⏩ In X Zeit'}];

  return (
    <div>
      <div style={{display:'flex',gap:6,marginBottom:18,flexWrap:'wrap'}}>
        {tabs.map(t=>(
          <button key={t.id} onClick={()=>setTab(t.id)} style={{
            padding:'6px 16px',borderRadius:20,fontFamily:'monospace',fontSize:11,cursor:'pointer',
            background:tab===t.id?'#4ecdc4':'rgba(255,255,255,0.05)',
            color:      tab===t.id?'#0d0d0f':'rgba(255,255,255,0.65)',
            border:     tab===t.id?'none':'1px solid rgba(255,255,255,0.12)',
            fontWeight: tab===t.id?700:400}}>{t.label}</button>
        ))}
      </div>

      {tab==='conv' && (
        <div>
          <div style={{display:'grid',gridTemplateColumns:mobile?'1fr':'1fr 1fr 1fr',gap:12,marginBottom:16}}>
            <div>
              <div style={lbl}>Moseszeit</div>
              <input style={inp} type="number" placeholder="z.B. 1685554093"
                value={conv.moses||''} onChange={e=>{ setConv(p=>({...p,moses:e.target.value})); convFromMoses(e.target.value); }}/>
            </div>
            <div>
              <div style={lbl}>Unix-Timestamp</div>
              <input style={inp} type="number" placeholder="z.B. 1780248072"
                value={conv.unix||''} onChange={e=>{ setConv(p=>({...p,unix:e.target.value})); convFromUnix(e.target.value); }}/>
            </div>
            <div>
              <div style={lbl}>Datum & Uhrzeit (Berlin)</div>
              <input style={inp} type="datetime-local"
                value={conv.local||''} onChange={e=>{ setConv(p=>({...p,local:e.target.value})); convFromLocal(e.target.value); }}/>
            </div>
          </div>
          <button style={{...S.btn('#ff6b9d',false), fontSize:11, padding:'6px 14px', marginBottom:16}}
            onClick={()=>{ setConv({unix:'',moses:'',local:''}); setConvResult(null); }}>
            ✕ Felder leeren
          </button>
          {convResult && (
            <div>
              <DtRow label="Datum/Uhrzeit (Berlin)" value={convResult.de}/>
              <DtRow label="Unix-Timestamp" value={convResult.unix}/>
              <DtRow label="Moseszeit" value={convResult.moses}/>
              <DtRow label="Wochentag" value={convResult.wday}/>
              <DtRow label="Kalenderwoche" value={`KW ${convResult.kw}`} muted/>
            </div>
          )}
          {!convResult && (
            <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:11,padding:'10px 0'}}>
              Einen der drei Werte eingeben → alle anderen werden berechnet
            </div>
          )}
          <div style={{marginTop:14,padding:'10px 12px',borderRadius:8,background:'rgba(255,255,255,0.03)',
            border:'1px solid rgba(255,255,255,0.07)'}}>
            <div style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:9,marginBottom:6}}>FORMEL</div>
            <div style={{color:'rgba(255,255,255,0.6)',fontFamily:'monospace',fontSize:11}}>
              Moses + <code style={{color:'#4ecdc4'}}>94.687.200</code> = Unix &nbsp;·&nbsp;
              Moseszeit <code style={{color:'#4ecdc4'}}>1685554093</code> = Unix <code style={{color:'#4ecdc4'}}>1780241293</code> = <code style={{color:'#4ecdc4'}}>31.05.2026 17:28:13</code>
            </div>
          </div>
        </div>
      )}

      {tab==='bday' && (
        <div>
          <div style={{display:'flex',alignItems:'flex-end',gap:10,marginBottom:16,flexWrap:'wrap'}}>
            <div>
              <div style={lbl}>Geburtsdatum</div>
              <input style={{...inp, maxWidth:200}} type="date" value={bday}
                onChange={e=>calcBday(e.target.value)}/>
            </div>
            {!!bday && <button style={{...S.btn('#ff6b9d',false), fontSize:11, padding:'6px 14px'}}
              onClick={()=>{ setBday(''); setBdayResult(null); }}>✕ Leeren</button>}
          </div>
          {bdayResult && (
            <div>
              <DtRow label="Geburtsdatum" value={`${bdayResult.geburtstag} (${bdayResult.wday})`}/>
              <DtRow label="Alter" value={bdayResult.ageStr}/>
              <DtRow label="Nächster Geburtstag" value={bdayResult.nextDate}/>
              <DtRow label="Tage bis Geburtstag" value={`${bdayResult.diffD} Tage`}/>
              <DtRow label="Gelebte Tage" value={`${Number(bdayResult.totalD).toLocaleString('de-DE')} Tage`} muted/>
            </div>
          )}
          {!bdayResult && (
            <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:11}}>
              Geburtsdatum eingeben
            </div>
          )}
        </div>
      )}

      {tab==='diff' && (
        <div>
          <div style={{display:'grid',gridTemplateColumns:mobile?'1fr':'1fr 1fr',gap:16,marginBottom:16}}>
            <div style={{display:'flex',flexDirection:'column',gap:8}}>
              <div>
                <div style={lbl}>Von – Datum</div>
                <input style={inp} type="date" value={dtA}
                  onChange={e=>{ setDtA(e.target.value); calcDiff(e.target.value, dtATime, dtB, dtBTime); }}/>
              </div>
              <div>
                <div style={lbl}>Von – Uhrzeit (optional)</div>
                <input style={inp} type="time" value={dtATime}
                  onChange={e=>{ setDtATime(e.target.value); calcDiff(dtA, e.target.value, dtB, dtBTime); }}/>
              </div>
            </div>
            <div style={{display:'flex',flexDirection:'column',gap:8}}>
              <div>
                <div style={lbl}>Bis – Datum</div>
                <input style={inp} type="date" value={dtB}
                  onChange={e=>{ setDtB(e.target.value); calcDiff(dtA, dtATime, e.target.value, dtBTime); }}/>
              </div>
              <div>
                <div style={lbl}>Bis – Uhrzeit (optional)</div>
                <input style={inp} type="time" value={dtBTime}
                  onChange={e=>{ setDtBTime(e.target.value); calcDiff(dtA, dtATime, dtB, e.target.value); }}/>
              </div>
            </div>
          </div>
          {!!(dtA||dtB||dtATime||dtBTime) && <button style={{...S.btn('#ff6b9d',false), fontSize:11, padding:'6px 14px', marginBottom:16}}
            onClick={()=>{ setDtA(''); setDtB(''); setDtATime(''); setDtBTime(''); setDiffResult(null); }}>✕ Felder leeren</button>}
          {diffResult && (
            <div>
              {!!diffResult.hasTime && <DtRow label="Millisekunden" value={Number(diffResult.ms).toLocaleString('de-DE')} muted/>}
              {!!diffResult.hasTime && <DtRow label="Sekunden"      value={Number(diffResult.s).toLocaleString('de-DE')}/>}
              {!!diffResult.hasTime && <DtRow label="Minuten"       value={Number(diffResult.m).toLocaleString('de-DE')}/>}
              {!!diffResult.hasTime && <DtRow label="Stunden"       value={Number(diffResult.h).toLocaleString('de-DE')}/>}
              <DtRow label="Tage"          value={Number(diffResult.d).toLocaleString('de-DE')}/>
              <DtRow label="Wochen"        value={Number(diffResult.weeks).toLocaleString('de-DE')} muted/>
              <DtRow label="Monate (ca.)"  value={`~${diffResult.months}`} muted/>
            </div>
          )}
          {!diffResult && (
            <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:11}}>
              Zwei Datumswerte eingeben · Uhrzeit ist optional
            </div>
          )}
        </div>
      )}

      {tab==='in' && (
        <div>
          <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:11,marginBottom:14}}>
            Ab jetzt in wie vielen Stunden/Minuten?
          </div>
          <div style={{display:'flex',gap:10,alignItems:'center',marginBottom:16,flexWrap:'wrap'}}>
            <div style={{display:'flex',alignItems:'center',gap:6}}>
              <input
                type="number" min="0" max="999"
                value={inH} onChange={e=>setInH(e.target.value)}
                placeholder="0"
                style={{...inp, width:70, textAlign:'center'}}
              />
              <span style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:12}}>Stunden</span>
            </div>
            <div style={{display:'flex',alignItems:'center',gap:6}}>
              <input
                type="number" min="0" max="59"
                value={inM} onChange={e=>setInM(e.target.value)}
                placeholder="0"
                style={{...inp, width:70, textAlign:'center'}}
              />
              <span style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:12}}>Minuten</span>
            </div>
          </div>
          {inResult && (
            <div style={{background:'rgba(255,255,255,0.03)',border:'1px solid rgba(255,255,255,0.08)',
              borderRadius:10,padding:'14px 16px'}}>
              <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10,marginBottom:6}}>
                IN {inResult.total} MINUTEN ({Math.floor(inResult.total/60)}h {inResult.total%60}m) IST ES:
              </div>
              <div style={{color:'#4ecdc4',fontFamily:'monospace',fontSize:18,fontWeight:700,marginBottom:8}}>
                {inResult.de}
              </div>
              <div style={{display:'flex',gap:8,alignItems:'center'}}>
                <span style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:11}}>
                  Unix: {inResult.unix}
                </span>
                <button onClick={()=>navigator.clipboard?.writeText(inResult.de)}
                  style={{background:'rgba(78,205,196,0.1)',border:'1px solid rgba(78,205,196,0.3)',
                    borderRadius:6,padding:'3px 10px',color:'#4ecdc4',fontFamily:'monospace',fontSize:10,cursor:'pointer'}}>
                  📋 Kopieren
                </button>
              </div>
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────── Key-Gen ──

const KEY_TYPES = [
  { id: 'laravel',   label: 'Laravel APP_KEY',        desc: 'base64, 32 Byte',          example: 'APP_KEY= in .env bei Laravel-Projekten – verschlüsselt Sessions & Daten',
    gen: () => 'base64:' + btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(32)))) },
  { id: 'jwt_hs256', label: 'JWT Secret (HS256)',      desc: '64 Byte hex',              example: 'JWT_SECRET= in Node.js/Express-APIs – signiert Login-Tokens',
    gen: () => hex(64) },
  { id: 'jwt_hs512', label: 'JWT Secret (HS512)',      desc: '128 Byte hex',             example: 'Wie HS256, aber stärker – für sicherheitskritische APIs empfohlen',
    gen: () => hex(128) },
  { id: 'aes256',    label: 'AES-256 Key',             desc: '32 Byte base64',           example: 'Symmetrische Verschlüsselung von Dateien oder Datenbank-Feldern',
    gen: () => b64(32) },
  { id: 'aes128',    label: 'AES-128 Key',             desc: '16 Byte base64',           example: 'Leichtgewichtige Verschlüsselung, z.B. für eingebettete Systeme',
    gen: () => b64(16) },
  { id: 'secret32',  label: 'Generic Secret (32B)',    desc: '32 Byte hex',              example: 'NEXTAUTH_SECRET=, SESSION_SECRET= oder ähnliche Umgebungsvariablen',
    gen: () => hex(32) },
  { id: 'secret64',  label: 'Generic Secret (64B)',    desc: '64 Byte hex',              example: 'Webhook-Signaturen (z.B. GitHub, Stripe) oder API-Signing-Keys',
    gen: () => hex(64) },
  { id: 'passwd',    label: 'Passwort (stark)',         desc: '20 Zeichen, alle Typen',   example: 'Admin-Passwörter, Service-Accounts oder initiale DB-Root-Passwörter',
    gen: () => genPasswd(20) },
  { id: 'passwd16',  label: 'Passwort (kurz)',          desc: '16 Zeichen, alle Typen',   example: 'Nutzer-Initialpasswörter oder Zugänge mit Zeichenlimit',
    gen: () => genPasswd(16) },
  { id: 'uuid',      label: 'UUID v4',                  desc: 'zufällige UUID',           example: 'Eindeutige IDs für Datenbank-Einträge, Dateinamen oder API-Ressourcen',
    gen: () => crypto.randomUUID() },
  { id: 'nanoid',    label: 'NanoID (21)',              desc: 'URL-safe, 21 Zeichen',     example: 'Kurze IDs in URLs (z.B. share-Links) – kompakter als UUID',
    gen: () => nanoid(21) },
  { id: 'nanoid32',  label: 'NanoID (32)',              desc: 'URL-safe, 32 Zeichen',     example: 'Session-IDs oder Token in URLs ohne Sonderzeichen-Probleme',
    gen: () => nanoid(32) },
  { id: 'docker_pw', label: 'DB-Passwort (Docker)',     desc: '24 Zeichen, alnum+sonder', example: 'MYSQL_ROOT_PASSWORD=, POSTGRES_PASSWORD= in docker-compose.yml',
    gen: () => genPasswd(24) },
  { id: 'hex16',     label: 'Hex Token (16B)',          desc: '32 Hex-Zeichen',           example: 'Kurze API-Keys, CSRF-Tokens oder Einmal-Codes',
    gen: () => hex(16) },
  { id: 'hex32',     label: 'Hex Token (32B)',          desc: '64 Hex-Zeichen',           example: 'Passwort-Reset-Links, E-Mail-Verifikations-Tokens',
    gen: () => hex(32) },
  { id: 'b64_32',    label: 'Base64 Token (32B)',       desc: '32 Byte base64',           example: 'Cookie-Signing-Keys oder kompakte Secrets in YAML/JSON-Configs',
    gen: () => b64(32) },
  { id: 'b64_64',    label: 'Base64 Token (64B)',       desc: '64 Byte base64',           example: 'VAPID-Keys (Web Push), OAuth Client Secrets',
    gen: () => b64(64) },
  { id: 'b64url_32', label: 'Base64url Token (32B)',    desc: '32 Byte base64url',        example: 'OAuth2 state/code_verifier, JWT-kompatible Token ohne Padding',
    gen: () => b64url(32) },
];

function hex(bytes) {
  return Array.from(crypto.getRandomValues(new Uint8Array(bytes))).map(b=>b.toString(16).padStart(2,'0')).join('');
}
function b64(bytes) {
  return btoa(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(bytes))));
}
function b64url(bytes) {
  return b64(bytes).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'');
}
function nanoid(len) {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
  const rnd = crypto.getRandomValues(new Uint8Array(len));
  return Array.from(rnd).map(b => chars[b % 64]).join('');
}
function genPasswd(len) {
  const sets = ['ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz','0123456789','!@#$%^&*()-_=+[]{}'];
  const all  = sets.join('');
  const rnd  = crypto.getRandomValues(new Uint8Array(len + 8));
  let result = sets.map(s => s[rnd[sets.indexOf(s)] % s.length]); // guarantee 1 from each set
  let idx = 4;
  while (result.length < len) { result.push(all[rnd[idx++ % rnd.length] % all.length]); }
  // shuffle
  for (let i = result.length - 1; i > 0; i--) {
    const j = rnd[(i * 3) % rnd.length] % (i + 1);
    [result[i], result[j]] = [result[j], result[i]];
  }
  return result.join('');
}

function KeyGen({ toast }) {
  const [keys, setKeys] = useState({});
  const [copied, setCopied] = useState(null);

  function generate(id) {
    const kt = KEY_TYPES.find(k => k.id === id);
    if (!kt) return;
    setKeys(p => ({ ...p, [id]: kt.gen() }));
  }

  function generateAll() {
    const all = {};
    KEY_TYPES.forEach(kt => { all[kt.id] = kt.gen(); });
    setKeys(all);
  }

  function clearAll() { setKeys({}); }

  async function copyKey(id, val) {
    try {
      await navigator.clipboard.writeText(val);
      setCopied(id);
      setTimeout(() => setCopied(null), 1800);
    } catch { toast('Kopieren fehlgeschlagen'); }
  }

  return (
    <div>
      <div style={{display:'flex',gap:8,marginBottom:20,flexWrap:'wrap'}}>
        <button style={{...S.btn('#4ecdc4',false), fontSize:11, padding:'7px 16px'}}
          onClick={generateAll}>⟳ Alle generieren</button>
        {!!Object.keys(keys).length && (
          <button style={{...S.btn('#ff6b9d',false), fontSize:11, padding:'7px 16px'}}
            onClick={clearAll}>✕ Alle leeren</button>
        )}
      </div>

      <div style={{display:'flex',flexDirection:'column',gap:8}}>
        {KEY_TYPES.map(kt => (
          <div key={kt.id} style={{
            borderRadius:9, background:'rgba(255,255,255,0.03)',
            border:'1px solid rgba(255,255,255,0.07)', overflow:'hidden'}}>
            <div style={{display:'flex',alignItems:'center',gap:10,padding:'8px 12px',
              borderBottom: keys[kt.id] ? '1px solid rgba(255,255,255,0.06)' : 'none'}}>
              <div style={{flex:1,minWidth:0}}>
                <span style={{color:'#fff',fontFamily:'monospace',fontSize:12,fontWeight:600}}>{kt.label}</span>
                <span style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10,marginLeft:8}}>{kt.desc}</span>
                <div style={{color:'rgba(255,255,255,0.45)',fontSize:11,marginTop:2,fontFamily:'sans-serif'}}>{kt.example}</div>
              </div>
              <button style={{...S.btn('#4ecdc4',false), fontSize:10, padding:'4px 12px', flexShrink:0}}
                onClick={()=>generate(kt.id)}>⟳ Neu</button>
              {!!keys[kt.id] && (
                <button style={{...S.btn('#ff6b9d',false), fontSize:10, padding:'4px 10px', flexShrink:0}}
                  onClick={()=>setKeys(p=>({...p,[kt.id]:undefined}))}>✕</button>
              )}
            </div>
            {!!keys[kt.id] && (
              <div style={{display:'flex',alignItems:'center',gap:8,padding:'8px 12px',cursor:'pointer'}}
                onClick={()=>copyKey(kt.id, keys[kt.id])} title="Klicken zum Kopieren">
                <code style={{
                  flex:1,minWidth:0,
                  color: copied===kt.id ? '#4ecdc4' : 'rgba(255,255,255,0.85)',
                  fontFamily:"'Courier New',monospace", fontSize:11,
                  wordBreak:'break-all', lineHeight:1.5}}>
                  {keys[kt.id]}
                </code>
                <span style={{
                  color: copied===kt.id ? '#4ecdc4' : 'rgba(255,255,255,0.35)',
                  fontSize:16, flexShrink:0, transition:'color 0.2s'}}>
                  {copied===kt.id ? '✓' : '⎘'}
                </span>
              </div>
            )}
          </div>
        ))}
      </div>
      <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:10,marginTop:14,lineHeight:1.7}}>
        Alle Keys werden lokal im Browser generiert (Web Crypto API) · Nichts wird gespeichert · Beim Seitenwechsel verschwinden sie
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────── Text Diff ──

function diffLines(a, b) {
  const aLines = a.split('\n');
  const bLines = b.split('\n');
  const m = aLines.length, n = bLines.length;
  const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));
  for (let i = m-1; i >= 0; i--)
    for (let j = n-1; j >= 0; j--)
      dp[i][j] = aLines[i] === bLines[j] ? dp[i+1][j+1]+1 : Math.max(dp[i+1][j], dp[i][j+1]);
  // Build rows: same / del / add — consecutive del+add stay paired (same display line number)
  const rows = []; // {type, lineA, lineB, text}
  let i = 0, j = 0, lnA = 0, lnB = 0;
  while (i < m || j < n) {
    if (i < m && j < n && aLines[i] === bLines[j]) {
      lnA++; lnB++;
      rows.push({type:'same', ln: lnA, text: aLines[i]});
      i++; j++;
    } else if (j < n && (i >= m || dp[i][j+1] >= dp[i+1][j])) {
      lnB++;
      rows.push({type:'add', ln: lnB, text: bLines[j]});
      j++;
    } else {
      lnA++;
      rows.push({type:'del', ln: lnA, text: aLines[i]});
      i++;
    }
  }
  return rows;
}

// Which line numbers (1-based) in text A/B are changed
function changedLineNums(rows, side) {
  const set = new Set();
  rows.forEach(r => {
    if (r.type === 'del' && side === 'a') set.add(r.ln);
    if (r.type === 'add' && side === 'b') set.add(r.ln);
  });
  return set;
}

function TextDiff({ mobile }) {
  const [left,  setLeft]  = useState('');
  const [right, setRight] = useState('');
  const [diff,  setDiff]  = useState(null);

  function compare() {
    if (!left.trim() && !right.trim()) return;
    setDiff(diffLines(left, right));
  }

  function clear() { setLeft(''); setRight(''); setDiff(null); }

  function loadFile(e, setter) {
    const file = e.target.files?.[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = ev => { setter(ev.target.result); setDiff(null); };
    reader.readAsText(file);
    e.target.value = '';
  }

  const added   = diff ? diff.filter(d=>d.type==='add').length : 0;
  const removed = diff ? diff.filter(d=>d.type==='del').length : 0;
  const same    = diff ? diff.filter(d=>d.type==='same').length : 0;

  const changedA = diff ? changedLineNums(diff, 'a') : new Set();
  const changedB = diff ? changedLineNums(diff, 'b') : new Set();

  const ta = { ...S.inp, fontSize:12, fontFamily:"'Courier New',monospace", width:'100%',
    boxSizing:'border-box', minHeight:180, resize:'vertical', lineHeight:'1.6', padding:'7px 8px',
    whiteSpace:'pre', overflowX:'auto', overflowWrap:'normal' };

  function LineNumCol({ text, changedSet }) {
    return (
      <div style={{
        padding:'7px 6px', minWidth:36, textAlign:'right', userSelect:'none',
        fontFamily:"'Courier New',monospace", fontSize:12, lineHeight:'1.6',
        background:'rgba(0,0,0,0.2)', borderRight:'1px solid rgba(255,255,255,0.07)',
        flexShrink:0, overflowY:'hidden'}}>
        {(text||' ').split('\n').map((_,i) => (
          <div key={i} style={{
            height:'1.6em',
            color: changedSet.has(i+1) ? '#ff6b9d' : 'rgba(255,255,255,0.25)',
            background: changedSet.has(i+1) ? 'rgba(255,107,157,0.12)' : 'transparent',
          }}>{i+1}</div>
        ))}
      </div>
    );
  }

  return (
    <div>
      <div style={{display:'grid', gridTemplateColumns: mobile?'1fr':'1fr 1fr', gap:12, marginBottom:12}}>
        {([['left','Text A (Original)', left, setLeft, changedA], ['right','Text B (Neu)', right, setRight, changedB]]).map(([side, label, val, setter, cset])=>(
          <div key={side}>
            <div style={{display:'flex', alignItems:'center', gap:8, marginBottom:6}}>
              <span style={{color:'rgba(255,255,255,0.55)', fontFamily:'monospace', fontSize:10, flex:1}}>{label}</span>
              <label style={{...S.btn('#4ecdc4',false), fontSize:10, padding:'3px 10px', cursor:'pointer'}}>
                📂 Datei laden
                <input type="file" accept=".txt,.md,.log,.csv,.json,.xml,.yaml,.yml,.js,.ts,.jsx,.tsx,.py,.php,.sh,.env"
                  style={{display:'none'}} onChange={e=>loadFile(e,setter)}/>
              </label>
            </div>
            <div style={{display:'flex', borderRadius:6, overflow:'hidden', border:'1px solid rgba(255,255,255,0.1)', background:'rgba(255,255,255,0.04)'}}>
              <LineNumCol text={val} changedSet={cset}/>
              <textarea style={{...ta, border:'none', borderRadius:0, background:'transparent', flex:1, resize:'vertical'}}
                wrap="off" value={val} placeholder="Text hier einfügen…"
                onChange={e=>{ setter(e.target.value); setDiff(null); }}/>
            </div>
          </div>
        ))}
      </div>

      <div style={{display:'flex', gap:8, marginBottom:16, flexWrap:'wrap'}}>
        <button style={{...S.btn('#4ecdc4',true), fontSize:12, padding:'8px 20px'}}
          onClick={compare}>Vergleichen</button>
        {!!(left||right||diff) && (
          <button style={{...S.btn('#ff6b9d',false), fontSize:11, padding:'8px 16px'}}
            onClick={clear}>✕ Leeren</button>
        )}
        {!!diff && (
          <div style={{display:'flex', gap:10, alignItems:'center', flexWrap:'wrap'}}>
            <span style={{background:'rgba(255,107,157,0.15)', color:'#ff6b9d', fontFamily:'monospace',
              fontSize:11, padding:'4px 10px', borderRadius:6}}>− {removed} entfernt</span>
            <span style={{background:'rgba(78,205,196,0.15)', color:'#4ecdc4', fontFamily:'monospace',
              fontSize:11, padding:'4px 10px', borderRadius:6}}>+ {added} hinzugefügt</span>
            <span style={{background:'rgba(255,255,255,0.05)', color:'rgba(255,255,255,0.45)', fontFamily:'monospace',
              fontSize:11, padding:'4px 10px', borderRadius:6}}>= {same} gleich</span>
          </div>
        )}
      </div>

      {!!diff && (
        <div style={{borderRadius:9, border:'1px solid rgba(255,255,255,0.08)',
          overflow:'auto', maxHeight:520, background:'rgba(0,0,0,0.2)'}}>
          <table style={{width:'100%', borderCollapse:'collapse', fontFamily:"'Courier New',monospace", fontSize:12}}>
            <tbody>
              {diff.map((d,i) => (
                <tr key={i} style={{
                  background: d.type==='add' ? 'rgba(78,205,196,0.1)' : d.type==='del' ? 'rgba(255,107,157,0.1)' : 'transparent',
                  borderBottom:'1px solid rgba(255,255,255,0.03)'}}>
                  <td style={{width:36, textAlign:'right', padding:'2px 8px', userSelect:'none',
                    color:'rgba(255,255,255,0.2)', fontSize:11,
                    borderRight:'1px solid rgba(255,255,255,0.05)'}}>
                    {d.ln}
                  </td>
                  <td style={{width:20, textAlign:'center', padding:'2px 4px', userSelect:'none',
                    color: d.type==='add'?'#4ecdc4': d.type==='del'?'#ff6b9d':'rgba(255,255,255,0.15)',
                    fontWeight:700, fontSize:14,
                    borderRight:'1px solid rgba(255,255,255,0.05)'}}>
                    {d.type==='add'?'+': d.type==='del'?'−':''}
                  </td>
                  <td style={{padding:'3px 10px', whiteSpace:'pre-wrap', wordBreak:'break-all',
                    color: d.type==='add'?'#a8f0eb': d.type==='del'?'#ffb3cc':'rgba(255,255,255,0.65)'}}>
                    {d.text || ' '}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
      {!diff && (
        <div style={{color:'rgba(255,255,255,0.35)', fontFamily:'monospace', fontSize:11,
          padding:'20px 0', textAlign:'center'}}>
          Texte eingeben oder Dateien laden → Vergleichen klicken
        </div>
      )}
    </div>
  );
}

// ──────────────────────────────────────────────────────────── Netzwerk ──

function NetzwerkRechner({ mobile }) {
  const [nTab, setNTab] = useState('speed');

  // ── Geschwindigkeit ──
  const [speedVal, setSpeedVal] = useState('');
  const [speedUnit, setSpeedUnit] = useState('mbps');
  const [customSize, setCustomSize] = useState('');
  const [customSizeUnit, setCustomSizeUnit] = useState('mb');

  const SPEED_UNITS = [
    { id:'bps',   label:'bit/s',   toBps: 1 },
    { id:'kbps',  label:'kbit/s',  toBps: 1e3 },
    { id:'mbps',  label:'Mbit/s',  toBps: 1e6 },
    { id:'gbps',  label:'Gbit/s',  toBps: 1e9 },
    { id:'bBps',  label:'B/s',     toBps: 8 },
    { id:'kBps',  label:'kB/s',    toBps: 8e3 },
    { id:'mBps',  label:'MB/s',    toBps: 8e6 },
    { id:'gBps',  label:'GB/s',    toBps: 8e9 },
  ];

  function fmtSpeed(bps, u) {
    const v = bps / u.toBps;
    if (v >= 1000) return v.toLocaleString('de-DE', {maximumFractionDigits:2}) + ' ' + u.label;
    return parseFloat(v.toPrecision(5)).toLocaleString('de-DE') + ' ' + u.label;
  }

  const speedBps = parseFloat(speedVal) * (SPEED_UNITS.find(u=>u.id===speedUnit)?.toBps||1e6);
  const speedValid = !isNaN(speedBps) && speedVal !== '';

  // Download-Zeit
  function dlTime(bps, sizeBytes) {
    const s = (sizeBytes * 8) / bps;
    if (s < 60) return `${s.toFixed(1)} Sek.`;
    if (s < 3600) return `${(s/60).toFixed(1)} Min.`;
    return `${(s/3600).toFixed(2)} Std.`;
  }

  // ── IP / Subnetz ──
  const [ipInput, setIpInput] = useState('');
  const [subnetResult, setSubnetResult] = useState(null);
  const [subnetErr, setSubnetErr] = useState('');

  function parseIP(str) {
    const parts = str.trim().split('.').map(Number);
    if (parts.length !== 4 || parts.some(p=>isNaN(p)||p<0||p>255)) return null;
    return parts.reduce((acc,p)=>(acc<<8)|p, 0) >>> 0;
  }

  function calcSubnet(input) {
    setIpInput(input);
    setSubnetErr('');
    setSubnetResult(null);
    if (!input.trim()) return;
    let ip, prefix;

    if (input.includes('/')) {
      const [ipPart, pfx] = input.split('/');
      ip = parseIP(ipPart);
      prefix = parseInt(pfx);
      if (ip === null || isNaN(prefix) || prefix < 0 || prefix > 32) { setSubnetErr('Ungültige CIDR-Notation'); return; }
    } else {
      ip = parseIP(input);
      if (ip === null) { setSubnetErr('Ungültige IP-Adresse'); return; }
      prefix = 24;
    }

    const mask   = prefix === 0 ? 0 : (~0 << (32 - prefix)) >>> 0;
    const net    = (ip & mask) >>> 0;
    const bcast  = (net | (~mask >>> 0)) >>> 0;
    const hosts  = prefix >= 31 ? (prefix===32?1:2) : Math.pow(2, 32-prefix) - 2;
    const first  = prefix >= 31 ? net : (net + 1) >>> 0;
    const last   = prefix >= 31 ? bcast : (bcast - 1) >>> 0;

    function i2ip(n) { return [(n>>>24)&0xff,(n>>>16)&0xff,(n>>>8)&0xff,n&0xff].join('.'); }
    function i2mask(n) { return i2ip(n); }

    // Klasse
    const first8 = (ip>>>24)&0xff;
    const klasse = first8 < 128?'A': first8<192?'B': first8<224?'C': first8<240?'D (Multicast)':'E (Reserviert)';
    const privat = (first8===10)||(first8===172&&((ip>>>16)&0xff)>=16&&((ip>>>16)&0xff)<=31)||
                   (first8===192&&((ip>>>16)&0xff)===168);

    setSubnetResult({
      ip: i2ip(ip), cidr: `${i2ip(net)}/${prefix}`,
      mask: i2mask(mask), maskBin: mask.toString(2).padStart(32,'0').match(/.{8}/g).join('.'),
      net: i2ip(net), bcast: i2ip(bcast),
      first: i2ip(first), last: i2ip(last),
      hosts: hosts.toLocaleString('de-DE'),
      prefix, klasse, privat,
      wildcard: i2ip(~mask>>>0),
    });
  }

  const CIDR_TABLE = [
    {pfx:'/8', mask:'255.0.0.0', hosts:'16.777.214', bsp:'Klasse A – Großnetz'},
    {pfx:'/16',mask:'255.255.0.0',hosts:'65.534',bsp:'Klasse B – Mittelnetz'},
    {pfx:'/24',mask:'255.255.255.0',hosts:'254',bsp:'Typisches Heimnetz'},
    {pfx:'/25',mask:'255.255.255.128',hosts:'126',bsp:'Hälfte eines /24'},
    {pfx:'/26',mask:'255.255.255.192',hosts:'62',bsp:'Viertel eines /24'},
    {pfx:'/27',mask:'255.255.255.224',hosts:'30',bsp:'Kleines Subnetz'},
    {pfx:'/28',mask:'255.255.255.240',hosts:'14',bsp:'VLAN, kleine Abteilung'},
    {pfx:'/29',mask:'255.255.255.248',hosts:'6',bsp:'Point-to-Point + Reserve'},
    {pfx:'/30',mask:'255.255.255.252',hosts:'2',bsp:'Point-to-Point Link'},
    {pfx:'/32',mask:'255.255.255.255',hosts:'1',bsp:'Einzelner Host / Loopback'},
  ];

  const nTabs = [{id:'speed',label:'⚡ Geschwindigkeit'},{id:'subnet',label:'🌐 IP / Subnetz'},{id:'cidr',label:'📋 CIDR-Tabelle'}];
  const lbl = {color:'rgba(255,255,255,0.55)',fontFamily:'monospace',fontSize:10,marginBottom:4};
  const inp = {...S.inp, fontSize:13, padding:'7px 9px', width:'100%', boxSizing:'border-box'};

  function NRow({label,value,muted,mono}) {
    return (
      <div style={{display:'flex',justifyContent:'space-between',alignItems:'center',
        padding:'6px 10px',borderRadius:6,background:'rgba(255,255,255,0.03)',marginBottom:3}}>
        <span style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:11}}>{label}</span>
        <code style={{color:muted?'rgba(255,255,255,0.45)':'#4ecdc4',
          fontFamily:mono===false?'inherit':"'Courier New',monospace",fontSize:12,fontWeight:700,
          textAlign:'right',maxWidth:'60%',wordBreak:'break-all'}}>{value}</code>
      </div>
    );
  }

  return (
    <div>
      <div style={{display:'flex',gap:6,marginBottom:18,flexWrap:'wrap'}}>
        {nTabs.map(t=>(
          <button key={t.id} onClick={()=>setNTab(t.id)} style={{
            padding:'6px 14px',borderRadius:20,fontFamily:'monospace',fontSize:11,cursor:'pointer',
            background:nTab===t.id?'#4ecdc4':'rgba(255,255,255,0.05)',
            color:nTab===t.id?'#0d0d0f':'rgba(255,255,255,0.65)',
            border:nTab===t.id?'none':'1px solid rgba(255,255,255,0.12)',
            fontWeight:nTab===t.id?700:400}}>{t.label}</button>
        ))}
      </div>

      {nTab==='speed' && (
        <div>
          <div style={{display:'grid',gridTemplateColumns:mobile?'1fr':'2fr 1fr',gap:12,marginBottom:16}}>
            <div>
              <div style={lbl}>Wert eingeben</div>
              <input style={inp} type="number" min="0" placeholder="z.B. 100"
                value={speedVal} onChange={e=>setSpeedVal(e.target.value)}/>
            </div>
            <div>
              <div style={lbl}>Einheit</div>
              <select style={{...inp,cursor:'pointer'}} value={speedUnit} onChange={e=>setSpeedUnit(e.target.value)}>
                <optgroup label="Bit-basiert">
                  <option value="bps">bit/s</option>
                  <option value="kbps">kbit/s</option>
                  <option value="mbps">Mbit/s</option>
                  <option value="gbps">Gbit/s</option>
                </optgroup>
                <optgroup label="Byte-basiert">
                  <option value="bBps">B/s</option>
                  <option value="kBps">kB/s</option>
                  <option value="mBps">MB/s</option>
                  <option value="gBps">GB/s</option>
                </optgroup>
              </select>
            </div>
          </div>
          {speedValid && (
            <>
              <div style={{marginBottom:10}}>
                {SPEED_UNITS.map(u=>(
                  <NRow key={u.id} label={u.label} value={fmtSpeed(speedBps,u)} muted={u.id===speedUnit}/>
                ))}
              </div>
              <div style={{marginTop:14,padding:'10px 12px',borderRadius:8,
                background:'rgba(255,255,255,0.03)',border:'1px solid rgba(255,255,255,0.07)'}}>
                <div style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:9,marginBottom:8}}>DOWNLOAD-ZEITEN BEI DIESER GESCHWINDIGKEIT</div>
                {[['700 MB CD',700e6],['4,7 GB DVD',4.7e9],['25 GB Blu-ray',25e9],['50 GB Spiel',50e9],['100 GB',100e9]].map(([lbl,b])=>(
                  <NRow key={lbl} label={lbl} value={dlTime(speedBps,b)} muted/>
                ))}
                <div style={{borderTop:'1px solid rgba(255,255,255,0.07)',marginTop:8,paddingTop:8}}>
                  <div style={{display:'flex',gap:8,alignItems:'center',flexWrap:'wrap',marginBottom:6}}>
                    <span style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:11,minWidth:80}}>Eigene Größe</span>
                    <input style={{...inp,width:90,flex:'none'}} type="number" min="0" placeholder="z.B. 8"
                      value={customSize} onChange={e=>setCustomSize(e.target.value)}/>
                    <select style={{...inp,width:70,flex:'none',cursor:'pointer'}} value={customSizeUnit} onChange={e=>setCustomSizeUnit(e.target.value)}>
                      <option value="kb">kB</option>
                      <option value="mb">MB</option>
                      <option value="gb">GB</option>
                      <option value="tb">TB</option>
                    </select>
                    {!!customSize && <span style={{color:'#4ecdc4',fontFamily:"'Courier New',monospace",fontSize:13,fontWeight:700}}>
                      {dlTime(speedBps, parseFloat(customSize) * ({kb:1e3,mb:1e6,gb:1e9,tb:1e12}[customSizeUnit]||1e6))}
                    </span>}
                  </div>
                </div>
              </div>
              <div style={{marginTop:10,padding:'8px 12px',borderRadius:7,
                background:'rgba(255,255,255,0.02)',border:'1px solid rgba(255,255,255,0.06)'}}>
                <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10,lineHeight:1.8}}>
                  💡 Faustregel: Mbit/s ÷ 8 = MB/s &nbsp;·&nbsp; Internetanbieter werben mit Mbit/s, Dateimanager zeigt MB/s
                </div>
              </div>
            </>
          )}
          {!speedValid && (
            <div style={{color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:11,padding:'10px 0'}}>
              Geschwindigkeit eingeben → alle Einheiten werden berechnet
            </div>
          )}
        </div>
      )}

      {nTab==='subnet' && (
        <div>
          <div style={{marginBottom:16}}>
            <div style={lbl}>IP-Adresse oder CIDR (z.B. 192.168.1.100/24)</div>
            <div style={{display:'flex',gap:8,flexWrap:'wrap'}}>
              <input style={{...inp,flex:1,minWidth:200}} placeholder="192.168.1.100/24"
                value={ipInput} onChange={e=>calcSubnet(e.target.value)}/>
              {!!ipInput && <button style={{...S.btn('#ff6b9d',false),fontSize:11,padding:'7px 14px'}}
                onClick={()=>{setIpInput('');setSubnetResult(null);setSubnetErr('');}}>✕</button>}
            </div>
            {!!subnetErr && <div style={{color:'#ff6b9d',fontFamily:'monospace',fontSize:11,marginTop:6}}>{subnetErr}</div>}
          </div>
          {subnetResult && (
            <div>
              <NRow label="IP-Adresse" value={subnetResult.ip}/>
              <NRow label="CIDR-Notation" value={subnetResult.cidr}/>
              <NRow label="Subnetzmaske" value={subnetResult.mask}/>
              <NRow label="Maske binär" value={subnetResult.maskBin}/>
              <NRow label="Wildcard-Maske" value={subnetResult.wildcard} muted/>
              <NRow label="Netzadresse" value={subnetResult.net}/>
              <NRow label="Broadcast" value={subnetResult.bcast}/>
              <NRow label="Erster Host" value={subnetResult.first}/>
              <NRow label="Letzter Host" value={subnetResult.last}/>
              <NRow label="Nutzbare Hosts" value={subnetResult.hosts}/>
              <NRow label="IP-Klasse" value={subnetResult.klasse} muted/>
              <NRow label="Privater Bereich" value={subnetResult.privat?'✓ Ja (RFC 1918)':'✗ Öffentlich'} muted/>
              <div style={{marginTop:10,padding:'8px 12px',borderRadius:7,
                background:'rgba(255,255,255,0.02)',border:'1px solid rgba(255,255,255,0.06)'}}>
                <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10,lineHeight:1.8}}>
                  💡 Subnetzmaske: 1er-Bits = Netzanteil, 0er-Bits = Hostanteil &nbsp;·&nbsp;
                  Broadcast = letzte Adresse &nbsp;·&nbsp; Netz = erste Adresse
                </div>
              </div>
            </div>
          )}
          {!subnetResult && !subnetErr && (
            <div style={{color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:11,padding:'10px 0'}}>
              IP eingeben (mit oder ohne /Präfix) → Subnetz wird berechnet · Standard: /24
            </div>
          )}
        </div>
      )}

      {nTab==='cidr' && (
        <div>
          <div style={{overflowX:'auto'}}>
            <table style={{width:'100%',borderCollapse:'collapse',fontFamily:'monospace',fontSize:12}}>
              <thead>
                <tr style={{borderBottom:'1px solid rgba(255,255,255,0.1)'}}>
                  {['Präfix','Subnetzmaske','Nutzbare Hosts','Typischer Einsatz'].map(h=>(
                    <th key={h} style={{padding:'7px 10px',textAlign:'left',color:'rgba(255,255,255,0.45)',fontSize:10,fontWeight:600}}>{h}</th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {CIDR_TABLE.map((r,i)=>(
                  <tr key={r.pfx} style={{background:i%2?'rgba(255,255,255,0.02)':'transparent',
                    borderBottom:'1px solid rgba(255,255,255,0.04)'}}>
                    <td style={{padding:'7px 10px',color:'#4ecdc4',fontWeight:700}}>{r.pfx}</td>
                    <td style={{padding:'7px 10px',color:'rgba(255,255,255,0.75)'}}>{r.mask}</td>
                    <td style={{padding:'7px 10px',color:'rgba(255,255,255,0.75)',textAlign:'right'}}>{r.hosts}</td>
                    <td style={{padding:'7px 10px',color:'rgba(255,255,255,0.5)',fontSize:11}}>{r.bsp}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          <div style={{marginTop:14,padding:'10px 12px',borderRadius:8,
            background:'rgba(255,255,255,0.03)',border:'1px solid rgba(255,255,255,0.07)'}}>
            <div style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:9,marginBottom:6}}>PRIVATE ADRESSBEREICHE (RFC 1918)</div>
            {[
              ['10.0.0.0/8','10.0.0.0 – 10.255.255.255','Klasse A, große Firmennetzwerke'],
              ['172.16.0.0/12','172.16.0.0 – 172.31.255.255','Klasse B, mittlere Netzwerke'],
              ['192.168.0.0/16','192.168.0.0 – 192.168.255.255','Klasse C, Heimnetzwerke'],
            ].map(([cidr,range,desc])=>(
              <div key={cidr} style={{display:'flex',gap:12,padding:'4px 0',borderBottom:'1px solid rgba(255,255,255,0.04)',flexWrap:'wrap'}}>
                <code style={{color:'#4ecdc4',minWidth:120}}>{cidr}</code>
                <span style={{color:'rgba(255,255,255,0.6)',minWidth:200}}>{range}</span>
                <span style={{color:'rgba(255,255,255,0.4)',fontSize:11}}>{desc}</span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

// ──────────────────────────────────────────────────────────── Regex ──

const RULE_TYPES = [
  { id:'contains',     label:'Enthält',              fields:['text'],            hint:'abc' },
  { id:'notcontains',  label:'Enthält nicht',         fields:['text'],            hint:'abc' },
  { id:'startswith',   label:'Beginnt mit',           fields:['text'],            hint:'KD-' },
  { id:'endswith',     label:'Endet mit',             fields:['text'],            hint:'.pdf' },
  { id:'exact',        label:'Ist genau',             fields:['text'],            hint:'hallo' },
  { id:'digits_n',     label:'Genau N Ziffern',       fields:['count'],           hint:'6' },
  { id:'digits_min',   label:'Mindestens N Ziffern',  fields:['count'],           hint:'3' },
  { id:'digits_range', label:'Ziffern (von–bis)',     fields:['min','max'],       hint:'' },
  { id:'letters_n',    label:'Genau N Buchstaben',    fields:['count'],           hint:'5' },
  { id:'alnum_n',      label:'Genau N Alphanumerisch',fields:['count'],           hint:'8' },
  { id:'minlength',    label:'Mindestlänge',          fields:['count'],           hint:'5' },
  { id:'maxlength',    label:'Höchstlänge',           fields:['count'],           hint:'20' },
  { id:'numrange',      label:'Zahlenbereich (enthält)',      fields:['min','max'], hint:'' },
  { id:'numrange_not',  label:'Zahlenbereich (enthält nicht)',fields:['min','max'], hint:'' },
  { id:'numrange_exact',label:'Zahlenbereich (exakt)',        fields:['min','max'], hint:'' },
  { id:'onlynums',     label:'Nur Zahlen',            fields:[],                  hint:'' },
  { id:'onlyletters',  label:'Nur Buchstaben',        fields:[],                  hint:'' },
  { id:'onlyalnum',    label:'Nur Alphanumerisch',    fields:[],                  hint:'' },
  { id:'date_de',      label:'Datum (TT.MM.JJJJ)',    fields:[],                  hint:'' },
  { id:'date_iso',     label:'Datum (JJJJ-MM-TT)',    fields:[],                  hint:'' },
  { id:'time',         label:'Uhrzeit (HH:MM)',       fields:[],                  hint:'' },
  { id:'email',        label:'E-Mail-Adresse',        fields:[],                  hint:'' },
  { id:'url',          label:'URL (http/https)',      fields:[],                  hint:'' },
  { id:'plz',          label:'PLZ (5 Ziffern)',       fields:[],                  hint:'' },
  { id:'ipv4',         label:'IPv4-Adresse',          fields:[],                  hint:'' },
  { id:'hex_color',    label:'Hex-Farbe (#rrggbb)',   fields:[],                  hint:'' },
  { id:'custom',       label:'Benutzerdefiniert',     fields:['pattern'],         hint:'\\d+' },
];

function escapeRegex(s) {
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

// Build regex fragment + description for a single rule
function ruleToRegex(rule) {
  const t = rule.type, f = rule.fields || {};
  const esc = escapeRegex(f.text || '');
  switch (t) {
    case 'contains':     return { pat: esc,                                          desc: `enthält "${f.text}"` };
    case 'notcontains':  return { pat: `(?!.*${esc})`,                               desc: `enthält NICHT "${f.text}"` };
    case 'startswith':   return { pat: `^${esc}`,                                    desc: `beginnt mit "${f.text}"` };
    case 'endswith':     return { pat: `${esc}$`,                                    desc: `endet mit "${f.text}"` };
    case 'exact':        return { pat: `^${esc}$`,                                   desc: `ist genau "${f.text}"` };
    case 'digits_n':     return { pat: `\\d{${f.count||1}}`,                         desc: `genau ${f.count||1} Ziffer(n)` };
    case 'digits_min':   return { pat: `\\d{${f.count||1},}`,                        desc: `mindestens ${f.count||1} Ziffer(n)` };
    case 'digits_range': return { pat: `\\d{${f.min||1},${f.max||9}}`,               desc: `${f.min||1}–${f.max||9} Ziffern` };
    case 'letters_n':    return { pat: `[a-zA-ZäöüÄÖÜß]{${f.count||1}}`,            desc: `genau ${f.count||1} Buchstabe(n)` };
    case 'alnum_n':      return { pat: `[a-zA-Z0-9]{${f.count||1}}`,                 desc: `genau ${f.count||1} alphanumerische Zeichen` };
    case 'minlength':    return { pat: `.{${f.count||1},}`,                          desc: `mindestens ${f.count||1} Zeichen lang` };
    case 'maxlength':    return { pat: `.{0,${f.count||999}}`,                       desc: `höchstens ${f.count||999} Zeichen lang` };
    case 'numrange':       return { pat: `(?:${buildNumRange(Number(f.min||0), Number(f.max||9))})`,           desc: `enthält eine Zahl zwischen ${f.min||0} und ${f.max||9}` };
    case 'numrange_not':   return { pat: `^(?!.*(?:${buildNumRange(Number(f.min||0), Number(f.max||9))})).*$`, desc: `enthält KEINE Zahl zwischen ${f.min||0} und ${f.max||9}` };
    case 'numrange_exact': return { pat: `^(?:${buildNumRange(Number(f.min||0), Number(f.max||9))})$`,         desc: `ist exakt eine Zahl zwischen ${f.min||0} und ${f.max||9}` };
    case 'onlynums':     return { pat: `^\\d+$`,                                     desc: `nur Ziffern (0–9)` };
    case 'onlyletters':  return { pat: `^[a-zA-ZäöüÄÖÜß]+$`,                        desc: `nur Buchstaben` };
    case 'onlyalnum':    return { pat: `^[a-zA-Z0-9]+$`,                             desc: `nur Buchstaben und Ziffern` };
    case 'date_de':      return { pat: `^(0[1-9]|[12]\\d|3[01])\\.(0[1-9]|1[0-2])\\.\\d{4}$`, desc: `Datum im Format TT.MM.JJJJ` };
    case 'date_iso':     return { pat: `^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$`,     desc: `Datum im Format JJJJ-MM-TT` };
    case 'time':         return { pat: `^([01]\\d|2[0-3]):[0-5]\\d$`,               desc: `Uhrzeit im Format HH:MM` };
    case 'email':        return { pat: `^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$`,            desc: `gültige E-Mail-Adresse` };
    case 'url':          return { pat: `^https?:\\/\\/[^\\s/$.?#].[^\\s]*$`,        desc: `URL beginnend mit http:// oder https://` };
    case 'plz':          return { pat: `^\\d{5}$`,                                  desc: `deutsche Postleitzahl (5 Ziffern)` };
    case 'ipv4':         return { pat: `^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$`, desc: `IPv4-Adresse` };
    case 'hex_color':    return { pat: `^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$`,       desc: `Hex-Farbe (#rgb oder #rrggbb)` };
    case 'custom':       return { pat: f.pattern || '',                              desc: `benutzerdefiniert: ${f.pattern||''}` };
    default:             return { pat: '', desc: '' };
  }
}

// Simple numeric range pattern builder
function buildNumRange(lo, hi) {
  if (isNaN(lo) || isNaN(hi)) return '\\d+';
  if (lo === hi) return String(lo);
  if (lo > hi) return '\\d+';
  if (hi - lo > 9999) return '\\d+'; // too large, fallback
  const vals = [];
  for (let i = lo; i <= hi; i++) vals.push(String(i));
  return vals.join('|');
}

// Combine all rules into final regex
function buildRegex(rules) {
  if (!rules.length) return '';
  const parts = rules.map(r => ruleToRegex(r).pat).filter(Boolean);
  // If any rule has ^ or $ anchors at both ends alone → just combine sequentially
  // Wrap in ^ ... $ only if all rules together form a complete match pattern
  const hasFullAnchors = parts.some(p => /^\^/.test(p) && /\$$/.test(p));
  if (rules.length === 1) return parts[0];
  // Multiple rules: concat sequentially (user is building a sequence)
  const combined = parts.join('');
  return combined;
}

function buildDescription(rules) {
  if (!rules.length) return '';
  const descs = rules.map(r => ruleToRegex(r).desc).filter(Boolean);
  if (descs.length === 1) return `Der Text ${descs[0]}.`;
  return `Der Text ${descs.slice(0,-1).join(', dann ')} und ${descs[descs.length-1]}.`;
}

// ── Regex Erklärung (Reverse) ──
function explainRegex(pattern) {
  if (!pattern) return [];
  const explanations = [];
  let p = pattern;

  const rules = [
    [/^\^/, 'Muss am Anfang beginnen'],
    [/\$$/, 'Muss am Ende enden'],
    [/\\\./g, 'Literaler Punkt (.)'],
    [/\\d\+/g, 'Eine oder mehr Ziffern (0–9)'],
    [/\\d\*/g, 'Beliebig viele Ziffern (0–9)'],
    [/\\d\?/g, 'Optionale Ziffer (0–9)'],
    [/\\d\{(\d+)\}/g, (m,n)=>`Genau ${n} Ziffer(n)`],
    [/\\d\{(\d+),(\d+)\}/g, (m,a,b)=>`${a} bis ${b} Ziffern`],
    [/\\d\{(\d+),\}/g, (m,n)=>`Mindestens ${n} Ziffer(n)`],
    [/\\d/g, 'Eine Ziffer (0–9)'],
    [/\\w\+/g, 'Ein oder mehr Wortzeichen (a-z, A-Z, 0-9, _)'],
    [/\\w/g, 'Ein Wortzeichen (a-z, A-Z, 0-9, _)'],
    [/\\s/g, 'Ein Leerzeichen/Whitespace'],
    [/\\S/g, 'Kein Leerzeichen'],
    [/\.\+/g, 'Ein oder mehr beliebige Zeichen'],
    [/\.\*/g, 'Beliebig viele beliebige Zeichen (auch keine)'],
    [/\./g, 'Ein beliebiges Zeichen'],
    [/\[([^\]]+)\]/g, (m,c)=>`Eines der Zeichen: ${c}`],
    [/\(([^)]+)\)\?/g, (m,c)=>`Optional: ${c}`],
    [/\(([^)]+)\)\+/g, (m,c)=>`Eines oder mehr von: ${c}`],
    [/\(([^)]+)\)\*/g, (m,c)=>`Beliebig viele von: ${c}`],
    [/\(([^)]+)\)/g, (m,c)=>`Gruppe: ${c}`],
    [/\{(\d+)\}/g, (m,n)=>`Genau ${n} Mal wiederholt`],
    [/\{(\d+),(\d+)\}/g, (m,a,b)=>`${a} bis ${b} Mal`],
    [/\{(\d+),\}/g, (m,n)=>`Mindestens ${n} Mal`],
    [/\?/g, 'Optional (0 oder 1 Mal)'],
    [/\+/g, '1 oder mehr Mal'],
    [/\*/g, '0 oder mehr Mal'],
    [/\|/g, 'ODER-Verknüpfung'],
    [/\(\?!/g, 'Negativer Lookahead: das Folgende darf NICHT vorkommen'],
    [/\(\?=/g, 'Positiver Lookahead: das Folgende muss vorkommen'],
    [/\\b/g, 'Wortgrenze'],
    [/i$/, 'Groß-/Kleinschreibung ignorieren (Flag: i)'],
    [/g$/, 'Alle Treffer suchen (Flag: g)'],
  ];

  // Tokenize step by step
  const tokens = [];
  let i = 0;
  while (i < p.length) {
    // Skip anchors
    if ((p[i]==='^'||p[i]==='$') && (i===0||i===p.length-1)) {
      tokens.push({raw:p[i], label: p[i]==='^'?'Anfang des Textes':'Ende des Textes'});
      i++; continue;
    }
    // Escaped sequences
    if (p[i]==='\\' && i+1<p.length) {
      const seq = p[i]+p[i+1];
      const next2 = p[i+2];
      let quant = '';
      let qLabel = '';
      let j = i+2;
      if (next2==='{') {
        const end = p.indexOf('}', j);
        if (end>j) { quant = p.slice(j,end+1); j=end+1; }
      } else if (next2==='+'||next2==='*'||next2==='?') { quant=next2; j=i+3; }
      const labels = {'\\d':'Ziffer (0–9)','\\w':'Wortzeichen','\\s':'Whitespace','\\S':'kein Whitespace','\\b':'Wortgrenze','\\n':'Zeilenumbruch','\\t':'Tab','\\r':'CR','\\+':'Plus (+)','\\*':'Stern (*)','\\?':'Fragezeichen (?)','\\(':'Klammer auf','\\)':'Klammer zu','\\[':'Eckige Klammer [','\\]':'Eckige Klammer ]','\\{':'Geschweifte Klammer {','\\}':'Geschweifte Klammer }','\\^':'Dach (^)','\\$':'Dollar ($)','\\|':'Pipe (|)','\\\\':'Backslash (\\)','\\-':'Bindestrich (-)','\\@':'@-Zeichen','\\.':'Punkt (.)'};
      let lbl = labels[seq] || `Zeichen: ${seq}`;
      if (quant==='{') lbl += ` — quantifier:${quant}`;
      else if (quant) {
        const qmap = {'+':'ein oder mehrfach','*':'beliebig oft (auch 0)','?':'optional'};
        lbl += ` (${qmap[quant]||quant})`;
        // Parse {n} or {n,m}
        const qm = quant.match(/^\{(\d+),(\d+)\}$/);
        const qm1 = quant.match(/^\{(\d+)\}$/);
        const qm2 = quant.match(/^\{(\d+),\}$/);
        if (qm) lbl += ` — ${qm[1]} bis ${qm[2]} Mal`;
        else if (qm1) lbl += ` — genau ${qm1[1]} Mal`;
        else if (qm2) lbl += ` — mindestens ${qm2[1]} Mal`;
      }
      tokens.push({raw:seq+(quant||''), label:lbl});
      i = j; continue;
    }
    // Character class
    if (p[i]==='[') {
      const end = p.indexOf(']',i);
      if (end>i) {
        const cls = p.slice(i,end+1);
        const neg = cls[1]==='!';
        const inner = cls.slice(neg?2:1,-1);
        tokens.push({raw:cls, label:`${neg?'Kein':'Eines der'} Zeichen: ${inner}`});
        i = end+1; continue;
      }
    }
    // Group
    if (p[i]==='(') {
      // find matching )
      let depth=1, j=i+1;
      while (j<p.length && depth>0) { if(p[j]==='(') depth++; else if(p[j]===')') depth--; j++; }
      const group = p.slice(i,j);
      let label = `Gruppe`;
      if (group.startsWith('(?!')) label = `Negativer Lookahead (darf NICHT folgen)`;
      else if (group.startsWith('(?=')) label = `Positiver Lookahead (muss folgen)`;
      else if (group.startsWith('(?:')) label = `Nicht-fangende Gruppe`;
      tokens.push({raw:group, label});
      i=j; continue;
    }
    // Quantifiers standalone
    if (p[i]==='+') { tokens.push({raw:'+', label:'Das Vorherige: 1 oder mehr Mal'}); i++; continue; }
    if (p[i]==='*') { tokens.push({raw:'*', label:'Das Vorherige: 0 oder mehr Mal'}); i++; continue; }
    if (p[i]==='?') { tokens.push({raw:'?', label:'Das Vorherige: optional'}); i++; continue; }
    if (p[i]==='|') { tokens.push({raw:'|', label:'ODER — eine der Alternativen'}); i++; continue; }
    if (p[i]==='.') { tokens.push({raw:'.', label:'Ein beliebiges Zeichen (außer Zeilenumbruch)'}); i++; continue; }
    // Literal
    tokens.push({raw:p[i], label:`Literales Zeichen: "${p[i]}"`}); i++;
  }
  return tokens;
}

// RuleCard als Top-Level-Komponente (nicht innerhalb von RegexBuilder — vermeidet Fokus-Bug)
const FIELD_LABELS_RC = {text:'Text', count:'Anzahl', min:'Von', max:'Bis', pattern:'Muster'};
const LBL_STYLE = {color:'rgba(255,255,255,0.55)', fontFamily:'monospace', fontSize:10, marginBottom:3};

function RuleCard({rule, idx, onUpdate, onDelete, onMove, inpStyle}) {
  const rt = RULE_TYPES.find(r=>r.id===rule.type);
  const preview = ruleToRegex(rule);
  return (
    <div style={{borderRadius:9, background:'rgba(255,255,255,0.04)',
      border:'1px solid rgba(255,255,255,0.09)', padding:'10px 12px', marginBottom:8}}>
      <div style={{display:'flex', gap:8, alignItems:'flex-start', flexWrap:'wrap'}}>
        {/* Move buttons */}
        <div style={{display:'flex', flexDirection:'column', gap:2, paddingTop:2}}>
          <button onClick={()=>onMove(rule.id,-1)} style={{background:'rgba(255,255,255,0.05)',border:'none',
            borderRadius:4,color:'rgba(255,255,255,0.4)',cursor:'pointer',padding:'1px 6px',fontSize:11}}>▲</button>
          <button onClick={()=>onMove(rule.id,+1)} style={{background:'rgba(255,255,255,0.05)',border:'none',
            borderRadius:4,color:'rgba(255,255,255,0.4)',cursor:'pointer',padding:'1px 6px',fontSize:11}}>▼</button>
        </div>
        {/* Rule number badge */}
        <div style={{minWidth:22,height:22,borderRadius:11,background:'rgba(78,205,196,0.15)',
          color:'#4ecdc4',fontFamily:'monospace',fontSize:10,fontWeight:700,
          display:'flex',alignItems:'center',justifyContent:'center',flexShrink:0,marginTop:2}}>
          {idx+1}
        </div>
        {/* Type select */}
        <div style={{flex:'0 0 auto'}}>
          <div style={LBL_STYLE}>Regeltyp</div>
          <select style={{...inpStyle, width:'auto', cursor:'pointer'}} value={rule.type}
            onChange={e=>onUpdate(rule.id,{type:e.target.value, fields:{}})}>
            {RULE_TYPES.map(rt=>(
              <option key={rt.id} value={rt.id}>{rt.label}</option>
            ))}
          </select>
        </div>
        {/* Dynamic fields */}
        {rt?.fields.map(f=>(
          <div key={f} style={{flex:'1 1 80px', minWidth:80}}>
            <div style={LBL_STYLE}>{FIELD_LABELS_RC[f]||f}</div>
            <input style={{...inpStyle, width:'100%'}}
              placeholder={rt.hint||''}
              value={rule.fields[f]||''}
              onChange={e=>onUpdate(rule.id,{fields:{...rule.fields,[f]:e.target.value}})}/>
          </div>
        ))}
        {/* Preview */}
        <div style={{flex:'1 1 100px', minWidth:100}}>
          <div style={LBL_STYLE}>Regex-Teil</div>
          <code style={{display:'block',background:'rgba(0,0,0,0.3)',borderRadius:6,
            padding:'7px 9px',color:'#4ecdc4',fontFamily:"'Courier New',monospace",
            fontSize:12,wordBreak:'break-all',minHeight:34}}>
            {preview.pat || <span style={{color:'rgba(255,255,255,0.2)'}}>—</span>}
          </code>
        </div>
        {/* Delete */}
        <button onClick={()=>onDelete(rule.id)}
          style={{background:'rgba(255,107,157,0.12)',border:'1px solid rgba(255,107,157,0.2)',
            borderRadius:6,color:'#ff6b9d',cursor:'pointer',padding:'4px 10px',fontSize:13,
            marginTop:18,flexShrink:0}}>✕</button>
      </div>
      <div style={{marginTop:6,marginLeft:60,color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10}}>
        {preview.desc}
      </div>
    </div>
  );
}

function RegexBuilder({ toast, mobile }) {
  const [mode, setMode] = useState('build'); // 'build' | 'explain'
  // ── Build mode ──
  const [rules, setRules] = useState([]);
  const [testVal, setTestVal] = useState('');
  const [testExact, setTestExact] = useState(true);
  const [copied, setCopied] = useState(false);

  function addRule() {
    setRules(r => [...r, {id: Date.now(), type: 'contains', fields: {text:''}}]);
  }

  function updateRule(id, changes) {
    setRules(r => r.map(rule => rule.id===id ? {...rule, ...changes} : rule));
  }

  function deleteRule(id) {
    setRules(r => r.filter(rule => rule.id !== id));
  }

  function moveRule(id, dir) {
    setRules(r => {
      const idx = r.findIndex(x=>x.id===id);
      const next = idx+dir;
      if (next<0||next>=r.length) return r;
      const arr = [...r];
      [arr[idx], arr[next]] = [arr[next], arr[idx]];
      return arr;
    });
  }

  const regex = buildRegex(rules);
  const description = buildDescription(rules);

  let testResult = null;
  if (regex && testVal !== '') {
    try {
      const pat = testExact ? `^(?:${regex})$` : regex;
      testResult = new RegExp(pat).test(testVal);
    } catch(e) { testResult = null; }
  }

  async function copyRegex() {
    if (!regex) return;
    try { await navigator.clipboard.writeText(regex); setCopied(true); setTimeout(()=>setCopied(false),1800); }
    catch { toast('Kopieren fehlgeschlagen'); }
  }

  // ── Explain mode ──
  const [explainInput, setExplainInput] = useState('');
  const [explainTestVal, setExplainTestVal] = useState('');
  const [explainExact, setExplainExact] = useState(false);
  const explained = explainInput ? explainRegex(explainInput) : [];
  let explainTestResult = null;
  if (explainInput && explainTestVal !== '') {
    try {
      const pat = explainExact ? `^(?:${explainInput})$` : explainInput;
      explainTestResult = new RegExp(pat).test(explainTestVal);
    } catch {}
  }

  const inp = {...S.inp, fontSize:13, padding:'7px 9px', boxSizing:'border-box'};
  const lbl = {color:'rgba(255,255,255,0.55)', fontFamily:'monospace', fontSize:10, marginBottom:3};
  return (
    <div>
      {/* Mode tabs */}
      <div style={{display:'flex',gap:6,marginBottom:20}}>
        {[['build','🔨 Regex bauen'],['explain','🔍 Regex erklären']].map(([id,tabLbl])=>(
          <button key={id} onClick={()=>setMode(id)} style={{
            padding:'7px 18px',borderRadius:20,fontFamily:'monospace',fontSize:11,cursor:'pointer',
            background:mode===id?'#4ecdc4':'rgba(255,255,255,0.05)',
            color:mode===id?'#0d0d0f':'rgba(255,255,255,0.65)',
            border:mode===id?'none':'1px solid rgba(255,255,255,0.12)',
            fontWeight:mode===id?700:400}}>{tabLbl}</button>
        ))}
      </div>

      {mode==='build' && (
        <div>
          {/* Regel-Karten */}
          {rules.map((rule,idx)=>(
            <RuleCard key={rule.id} rule={rule} idx={idx}
              onUpdate={updateRule} onDelete={deleteRule} onMove={moveRule} inpStyle={inp}/>
          ))}

          <button style={{...S.btn('#4ecdc4',false), fontSize:12, padding:'8px 18px', marginBottom:20}}
            onClick={addRule}>+ Regel hinzufügen</button>

          {!!rules.length && (
            <>
              {/* Regex-Ausgabe */}
              <div style={{borderRadius:9,background:'rgba(255,255,255,0.03)',
                border:'1px solid rgba(255,255,255,0.08)',padding:'12px 14px',marginBottom:12}}>
                <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:8}}>
                  <span style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:10}}>GENERIERTER REGEX</span>
                  <button onClick={copyRegex} style={{
                    background:'rgba(78,205,196,0.1)',border:'1px solid rgba(78,205,196,0.2)',
                    borderRadius:6,color:copied?'#4ecdc4':'rgba(255,255,255,0.5)',
                    cursor:'pointer',padding:'2px 10px',fontSize:11}}>
                    {copied?'✓ Kopiert':'⎘ Kopieren'}
                  </button>
                </div>
                <code style={{display:'block',color:'#4ecdc4',fontFamily:"'Courier New',monospace",
                  fontSize:15,fontWeight:700,wordBreak:'break-all',lineHeight:1.5}}>
                  {regex || <span style={{color:'rgba(255,255,255,0.2)'}}>—</span>}
                </code>
                {!!description && (
                  <div style={{marginTop:8,color:'rgba(255,255,255,0.6)',fontSize:12,lineHeight:1.6}}>
                    {description}
                  </div>
                )}
              </div>

              {/* Test-Bereich */}
              <div style={{borderRadius:9,background:'rgba(255,255,255,0.03)',
                border:'1px solid rgba(255,255,255,0.08)',padding:'12px 14px'}}>
                <div style={{display:'flex',alignItems:'center',gap:12,marginBottom:8,flexWrap:'wrap'}}>
                  <span style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:10}}>TESTEN</span>
                  <label style={{display:'flex',alignItems:'center',gap:6,cursor:'pointer'}}>
                    <input type="checkbox" checked={testExact} onChange={e=>setTestExact(e.target.checked)}/>
                    <span style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:10}}>
                      Exakt (^…$) — ganzer Text muss passen
                    </span>
                  </label>
                </div>
                <div style={{display:'flex',gap:10,alignItems:'center',flexWrap:'wrap'}}>
                  <input style={{...inp,flex:1,minWidth:160}} placeholder="Testwert eingeben…"
                    value={testVal} onChange={e=>setTestVal(e.target.value)}/>
                  {testResult===true  && <span style={{color:'#4ecdc4',fontFamily:'monospace',fontSize:14,fontWeight:700}}>✓ Treffer</span>}
                  {testResult===false && <span style={{color:'#ff6b9d',fontFamily:'monospace',fontSize:14,fontWeight:700}}>✗ Kein Treffer</span>}
                </div>
              </div>
            </>
          )}

          {!rules.length && (
            <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:11,
              padding:'16px 0',textAlign:'center'}}>
              Noch keine Regeln — klicke „+ Regel hinzufügen"
            </div>
          )}
        </div>
      )}

      {mode==='explain' && (
        <div>
          <div style={{marginBottom:14}}>
            <div style={{...lbl,fontSize:11,marginBottom:6}}>Regex eingeben</div>
            <input style={{...inp,width:'100%',boxSizing:'border-box',fontSize:14,fontFamily:"'Courier New',monospace"}}
              placeholder="z.B. ^[a-zA-Z0-9]{6,20}$"
              value={explainInput} onChange={e=>setExplainInput(e.target.value)}/>
          </div>

          {!!explained.length && (
            <>
              <div style={{marginBottom:14}}>
                <div style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:10,marginBottom:8}}>ERKLÄRUNG BAUSTEIN FÜR BAUSTEIN</div>
                {explained.map((tok,i)=>(
                  <div key={i} style={{display:'flex',alignItems:'center',gap:10,padding:'6px 10px',
                    borderRadius:7,background:'rgba(255,255,255,0.03)',marginBottom:4,
                    border:'1px solid rgba(255,255,255,0.06)'}}>
                    <code style={{color:'#4ecdc4',fontFamily:"'Courier New',monospace",fontSize:13,
                      fontWeight:700,minWidth:60,flexShrink:0}}>{tok.raw}</code>
                    <span style={{color:'rgba(255,255,255,0.7)',fontSize:12}}>{tok.label}</span>
                  </div>
                ))}
              </div>

              {/* Test */}
              <div style={{borderRadius:9,background:'rgba(255,255,255,0.03)',
                border:'1px solid rgba(255,255,255,0.08)',padding:'12px 14px'}}>
                <div style={{display:'flex',alignItems:'center',gap:12,marginBottom:8,flexWrap:'wrap'}}>
                  <span style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:10}}>TESTEN</span>
                  <label style={{display:'flex',alignItems:'center',gap:6,cursor:'pointer'}}>
                    <input type="checkbox" checked={explainExact} onChange={e=>setExplainExact(e.target.checked)}/>
                    <span style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:10}}>
                      Exakt (^…$)
                    </span>
                  </label>
                </div>
                <div style={{display:'flex',gap:10,alignItems:'center',flexWrap:'wrap'}}>
                  <input style={{...inp,flex:1,minWidth:160}} placeholder="Testwert eingeben…"
                    value={explainTestVal} onChange={e=>setExplainTestVal(e.target.value)}/>
                  {explainTestResult===true  && <span style={{color:'#4ecdc4',fontFamily:'monospace',fontSize:14,fontWeight:700}}>✓ Treffer</span>}
                  {explainTestResult===false && <span style={{color:'#ff6b9d',fontFamily:'monospace',fontSize:14,fontWeight:700}}>✗ Kein Treffer</span>}
                </div>
              </div>
            </>
          )}

          {!explainInput && (
            <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:11,padding:'16px 0',textAlign:'center'}}>
              Einen bestehenden Regex oben eingeben → jeder Baustein wird erklärt
            </div>
          )}
        </div>
      )}
    </div>
  );
}


// ──────────────────────────────────────────────────────────── JSON Tool ──

// ──────────────────────────────────────────────────────────── JSON / XML Tool ──

// Typografische Anführungszeichen und andere häufige Probleme beheben
function sanitizeJson(raw) {
  let s = raw
    // Alle typografischen/deutschen Anführungszeichen → gerade "
    .replace(/[\u201C\u201D\u201E\u201F\u00AB\u00BB\u2039\u203A]/g, '"')
    // Einfache typografische → gerade "
    .replace(/[\u2018\u2019\u02BC\u0060]/g, '"')
    // Trailing commas vor } oder ]
    .replace(/,\s*([}\]])/g, '$1')
    // Unquoted keys: {key: ...} → {"key": ...}
    .replace(/([{,]\s*)([a-zA-Z_\u00C0-\u024F][a-zA-Z0-9_\u00C0-\u024F]*)\s*:/g, '$1"$2":');

  const trimmed = s.trim();

  // Falls kein vollständiges JSON-Objekt/Array → versuche als Objekt-Fragment wrappen
  // z.B. "Mitarbeiter": [...] → {"Mitarbeiter": [...]}
  if (trimmed && !trimmed.startsWith('{') && !trimmed.startsWith('[')
      && !trimmed.startsWith('"') && trimmed !== 'null'
      && trimmed !== 'true' && trimmed !== 'false') {
    s = '{' + trimmed + '}';
  } else if (trimmed && trimmed.startsWith('"') && trimmed.includes(':')) {
    // Quoted key at root level: "key": value → {"key": value}
    s = '{' + trimmed + '}';
  }

  return s;
}

// JSON → XML string
function jsonToXml(data, tag = 'root', indent = 0) {
  const pad = '  '.repeat(indent);
  if (data === null) return `${pad}<${tag} null="true"/>`;
  if (typeof data !== 'object') {
    return `${pad}<${tag}>${String(data).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}</${tag}>`;
  }
  if (Array.isArray(data)) {
    return data.map((v,i) => jsonToXml(v, 'item', indent)).join('\n');
  }
  const children = Object.entries(data)
    .map(([k,v]) => jsonToXml(v, k.replace(/\s+/g,'_'), indent+1))
    .join('\n');
  return `${pad}<${tag}>\n${children}\n${pad}</${tag}>`;
}

// XML → JSON object (basic parser)
function xmlToJson(xml) {
  const parser = new DOMParser();
  const doc = parser.parseFromString(xml.trim(), 'application/xml');
  const err = doc.querySelector('parsererror');
  if (err) throw new Error('Ungültiges XML: ' + err.textContent.split('\n')[0]);
  function nodeToObj(node) {
    if (node.nodeType === 3) { // text
      const t = node.textContent.trim();
      return t === '' ? null : t;
    }
    if (node.childNodes.length === 0) return null;
    if (node.childNodes.length === 1 && node.firstChild.nodeType === 3) {
      return node.firstChild.textContent;
    }
    const obj = {};
    const arr = {};
    node.childNodes.forEach(child => {
      if (child.nodeType !== 1) return;
      const val = nodeToObj(child);
      const name = child.nodeName;
      if (name in arr) { arr[name]++; } else { arr[name] = 1; }
    });
    // build result
    const seen = {};
    node.childNodes.forEach(child => {
      if (child.nodeType !== 1) return;
      const name = child.nodeName;
      const val = nodeToObj(child);
      if (arr[name] > 1) {
        if (!obj[name]) obj[name] = [];
        obj[name].push(val);
      } else {
        obj[name] = val;
      }
    });
    return obj;
  }
  return nodeToObj(doc.documentElement);
}

// ── JSON Viewer ──
function JsonNode({ data, depth, keyName }) {
  const [open, setOpen] = useState(depth < 2);
  const indent = depth * 16;
  const isObj  = data !== null && typeof data === 'object' && !Array.isArray(data);
  const isArr  = Array.isArray(data);
  const isPrim = !isObj && !isArr;

  const typeColor = { string:'#a8f0eb', number:'#ffe66d', boolean:'#ff6b9d', null:'rgba(255,255,255,0.3)' };
  function valColor(v) { if (v===null) return typeColor.null; return typeColor[typeof v]||'#fff'; }
  function valLabel(v) { if (v===null) return 'null'; if (typeof v==='string') return `"${v}"`; return String(v); }
  function typeTag(v) {
    if (v===null) return 'null';
    if (Array.isArray(v)) return `Array[${v.length}]`;
    if (typeof v==='object') return `Objekt {${Object.keys(v).length} Felder}`;
    return typeof v;
  }

  const keyStyle = {color:'#7eb8ff', fontFamily:"'Courier New',monospace", fontSize:12};
  const lineBase = {display:'flex', alignItems:'baseline', gap:4, padding:'1px 0', marginLeft:indent, lineHeight:1.7};

  if (isPrim) return (
    <div style={lineBase}>
      {keyName!==undefined && <><span style={keyStyle}>"{keyName}"</span><span style={{color:'rgba(255,255,255,0.3)'}}>:</span></>}
      <span style={{color:valColor(data), fontFamily:"'Courier New',monospace", fontSize:12}}>{valLabel(data)}</span>
      <span style={{color:'rgba(255,255,255,0.2)', fontSize:10, marginLeft:4}}>{typeof data==='object'?'null':typeof data}</span>
    </div>
  );

  const entries = isArr ? data.map((v,i)=>[i,v]) : Object.entries(data);
  const [o,c] = isArr ? ['[',']'] : ['{','}'];

  return (
    <div>
      <div style={{...lineBase, cursor:'pointer', userSelect:'none'}} onClick={()=>setOpen(x=>!x)}>
        <span style={{color:'rgba(255,255,255,0.3)', fontSize:11, minWidth:14}}>{open?'▾':'▸'}</span>
        {keyName!==undefined && <><span style={keyStyle}>"{keyName}"</span><span style={{color:'rgba(255,255,255,0.3)'}}>:</span></>}
        <span style={{color:'rgba(255,255,255,0.5)', fontFamily:"'Courier New',monospace", fontSize:12}}>{o}</span>
        {!open && <span style={{color:'rgba(255,255,255,0.25)', fontFamily:"'Courier New',monospace", fontSize:11}}>{isArr?`${entries.length} Einträge`:`${entries.length} Felder`}</span>}
        {!open && <span style={{color:'rgba(255,255,255,0.5)', fontFamily:"'Courier New',monospace", fontSize:12}}>{c}</span>}
        <span style={{color:'rgba(255,255,255,0.2)', fontSize:10, marginLeft:4}}>{typeTag(data)}</span>
      </div>
      {open && <>
        {entries.map(([k,v]) => <JsonNode key={k} data={v} depth={depth+1} keyName={k}/>)}
        <div style={{marginLeft:indent, color:'rgba(255,255,255,0.5)', fontFamily:"'Courier New',monospace", fontSize:12}}>{c}</div>
      </>}
    </div>
  );
}

// ── JSON Builder ──
let _nid = 1;
function newNode(type='string') { return {id:_nid++, type, key:'', value:'', children:[]}; }

function nodeToJson(node) {
  if (node.type==='object') { const o={}; node.children.forEach(c=>{o[c.key||`feld${c.id}`]=nodeToJson(c);}); return o; }
  if (node.type==='array')  { return node.children.map(c=>nodeToJson(c)); }
  if (node.type==='number') return isNaN(Number(node.value))?0:Number(node.value);
  if (node.type==='boolean') return node.value!=='false';
  if (node.type==='null')   return null;
  return node.value;
}

const TC = {object:'#4ecdc4', array:'#ffe66d', string:'#a8f0eb', number:'#ff6b9d', boolean:'#c3b1e1', null:'rgba(255,255,255,0.4)'};

function BuilderNode({node, depth, onChange, onDelete}) {
  const inp = {...S.inp, fontSize:12, padding:'4px 7px', boxSizing:'border-box'};
  const canKids = node.type==='object'||node.type==='array';
  const isRoot = depth===0;

  function addChild(type) { onChange({...node, children:[...node.children, newNode(type)]}); }
  function updChild(u)    { onChange({...node, children:node.children.map(c=>c.id===u.id?u:c)}); }
  function delChild(id)   { onChange({...node, children:node.children.filter(c=>c.id!==id)}); }

  return (
    <div style={{marginLeft:depth*18, marginBottom:4}}>
      <div style={{display:'flex', alignItems:'center', gap:6, flexWrap:'wrap'}}>
        {!isRoot && (
          <input style={{...inp, width:90, color:'#7eb8ff'}} placeholder="Key / Name"
            value={node.key} onChange={e=>onChange({...node, key:e.target.value})}/>
        )}
        {!isRoot && <span style={{color:'rgba(255,255,255,0.25)', fontSize:11}}>:</span>}
        <select style={{...inp, width:'auto', color:TC[node.type], cursor:'pointer'}}
          value={node.type} onChange={e=>onChange({...node, type:e.target.value, children:[], value:''})}>
          <option value="string">Text (string)</option>
          <option value="number">Zahl (number)</option>
          <option value="boolean">Ja/Nein (boolean)</option>
          <option value="null">Leer (null)</option>
          <option value="object">Objekt {'{ }'}</option>
          <option value="array">Liste [ ]</option>
        </select>
        {node.type==='string'  && <input style={{...inp,flex:1,minWidth:80}} placeholder="Wert eingeben" value={node.value} onChange={e=>onChange({...node,value:e.target.value})}/>}
        {node.type==='number'  && <input style={{...inp,width:100}} type="number" placeholder="0" value={node.value} onChange={e=>onChange({...node,value:e.target.value})}/>}
        {node.type==='boolean' && <select style={{...inp,width:'auto',cursor:'pointer'}} value={node.value} onChange={e=>onChange({...node,value:e.target.value})}><option value="true">true (ja)</option><option value="false">false (nein)</option></select>}
        {node.type==='null'    && <span style={{color:'rgba(255,255,255,0.3)',fontSize:11,fontFamily:'monospace'}}>null (kein Wert)</span>}
        {canKids && (
          <select style={{...inp,width:'auto',cursor:'pointer',color:'#4ecdc4'}} value="" onChange={e=>{if(e.target.value)addChild(e.target.value);}}>
            <option value="">+ Feld hinzufügen</option>
            <option value="string">Text</option>
            <option value="number">Zahl</option>
            <option value="boolean">Ja/Nein</option>
            <option value="null">Leer (null)</option>
            <option value="object">Objekt</option>
            <option value="array">Liste</option>
          </select>
        )}
        {!isRoot && <button onClick={onDelete} style={{background:'rgba(255,107,157,0.12)',border:'none',borderRadius:5,color:'#ff6b9d',cursor:'pointer',padding:'3px 8px',fontSize:12}}>✕</button>}
      </div>
      {canKids && node.children.map(child=>(
        <BuilderNode key={child.id} node={child} depth={depth+1} onChange={updChild} onDelete={()=>delChild(child.id)}/>
      ))}
    </div>
  );
}

// ── Main Tool ──
function JsonTool({ toast, mobile }) {
  const [tab, setTab]   = useState('view');
  const [fmt, setFmt]   = useState('json'); // 'json' | 'xml'

  // View / Convert
  const [rawInput,  setRawInput]  = useState('');
  const [parsed,    setParsed]    = useState(null);
  const [parseErr,  setParseErr]  = useState('');
  const [xmlOut,    setXmlOut]    = useState('');
  const [copiedOut, setCopiedOut] = useState(false);

  function doParseJson(val) {
    setRawInput(val);
    setParseErr(''); setParsed(null); setXmlOut('');
    if (!val.trim()) return;
    const fixed = sanitizeJson(val);
    try {
      const p = JSON.parse(fixed);
      setParsed(p);
      setXmlOut(jsonToXml(p));
    } catch(e) {
      setParseErr(e.message);
    }
  }

  function doParseXml(val) {
    setRawInput(val);
    setParseErr(''); setParsed(null); setXmlOut('');
    if (!val.trim()) return;
    try {
      const obj = xmlToJson(val);
      setParsed(obj);
      setXmlOut(val);
    } catch(e) {
      setParseErr(e.message);
    }
  }

  function handleInput(val) {
    if (fmt==='json') doParseJson(val);
    else doParseXml(val);
  }

  function formatJson() {
    if (!parsed) return;
    setRawInput(JSON.stringify(parsed, null, 2));
  }

  async function copyText(text) {
    try { await navigator.clipboard.writeText(text); setCopiedOut(true); setTimeout(()=>setCopiedOut(false),1800); }
    catch { toast('Kopieren fehlgeschlagen'); }
  }

  // Builder
  const [root,      setRoot]      = useState(() => ({...newNode('object'), key:'root'}));
  const [buildFmt,  setBuildFmt]  = useState('json');
  const [copiedBld, setCopiedBld] = useState(false);

  const builtJson = JSON.stringify(nodeToJson(root), null, 2);
  const builtXml  = (() => { try { return jsonToXml(nodeToJson(root)); } catch { return ''; } })();
  const builtOut  = buildFmt==='json' ? builtJson : builtXml;

  async function copyBuilt() {
    try { await navigator.clipboard.writeText(builtOut); setCopiedBld(true); setTimeout(()=>setCopiedBld(false),1800); }
    catch { toast('Kopieren fehlgeschlagen'); }
  }

  const inp  = {...S.inp, fontSize:13, padding:'7px 9px', boxSizing:'border-box'};
  const tabs = [{id:'view',label:'👁 Ansehen & Konvertieren'},{id:'build',label:'🔨 Struktur bauen'}];

  const FmtToggle = ({value, onChange}) => (
    <div style={{display:'flex',gap:4}}>
      {['json','xml'].map(f=>(
        <button key={f} onClick={()=>onChange(f)} style={{
          padding:'4px 12px',borderRadius:16,fontFamily:'monospace',fontSize:11,cursor:'pointer',
          background:value===f?'#4ecdc4':'rgba(255,255,255,0.05)',
          color:value===f?'#0d0d0f':'rgba(255,255,255,0.55)',
          border:value===f?'none':'1px solid rgba(255,255,255,0.1)',
          fontWeight:value===f?700:400}}>{f.toUpperCase()}</button>
      ))}
    </div>
  );

  return (
    <div>
      <div style={{display:'flex',gap:6,marginBottom:18,flexWrap:'wrap'}}>
        {tabs.map(t=>(
          <button key={t.id} onClick={()=>setTab(t.id)} style={{
            padding:'6px 16px',borderRadius:20,fontFamily:'monospace',fontSize:11,cursor:'pointer',
            background:tab===t.id?'#4ecdc4':'rgba(255,255,255,0.05)',
            color:tab===t.id?'#0d0d0f':'rgba(255,255,255,0.65)',
            border:tab===t.id?'none':'1px solid rgba(255,255,255,0.12)',
            fontWeight:tab===t.id?700:400}}>{t.label}</button>
        ))}
      </div>

      {tab==='view' && (
        <div>
          {/* Anleitung */}
          <div style={{padding:'10px 12px',borderRadius:8,background:'rgba(78,205,196,0.06)',
            border:'1px solid rgba(78,205,196,0.15)',marginBottom:14}}>
            <div style={{color:'#4ecdc4',fontFamily:'monospace',fontSize:10,fontWeight:700,marginBottom:6}}>SO FUNKTIONIERT ES</div>
            <div style={{color:'rgba(255,255,255,0.65)',fontSize:12,lineHeight:1.8}}>
              JSON oder XML einfügen → der Baum zeigt die Struktur auf/zuklappbar. Pfeile ▸ anklicken.<br/>
              <span style={{color:'rgba(255,255,255,0.4)',fontSize:11}}>
                Typografische Anführungszeichen „" werden automatisch korrigiert. Unvollständige Fragmente werden soweit möglich automatisch ergänzt.
              </span>
            </div>
            <div style={{marginTop:10,display:'grid',gridTemplateColumns:mobile?'1fr':'1fr 1fr',gap:8}}>
              {[
                ['Objekt { }','Zusammengehörige Felder eines Eintrags.\nBeispiel: eine Person mit Name und Alter.',
                 '{\n  "name": "Max",\n  "alter": 30\n}'],
                ['Array [ ]','Eine Liste gleichartiger Einträge.\nBeispiel: mehrere Personen.',
                 '[\n  {"name": "Max"},\n  {"name": "Lisa"}\n]'],
              ].map(([title,desc,ex])=>(
                <div key={title} style={{background:'rgba(0,0,0,0.2)',borderRadius:7,padding:'8px 10px'}}>
                  <div style={{color:'#4ecdc4',fontFamily:'monospace',fontSize:11,fontWeight:700,marginBottom:3}}>{title}</div>
                  <div style={{color:'rgba(255,255,255,0.55)',fontSize:11,lineHeight:1.6,marginBottom:6,whiteSpace:'pre-line'}}>{desc}</div>
                  <pre style={{margin:0,color:'rgba(255,255,255,0.7)',fontFamily:"'Courier New',monospace",fontSize:10,lineHeight:1.5}}>{ex}</pre>
                </div>
              ))}
            </div>
          </div>

          <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:8,flexWrap:'wrap'}}>
            <span style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:10}}>EINGABE-FORMAT:</span>
            <FmtToggle value={fmt} onChange={f=>{setFmt(f);setRawInput('');setParsed(null);setParseErr('');setXmlOut('');}}/>
            {!!rawInput && <button style={{...S.btn('#ff6b9d',false),fontSize:10,padding:'4px 12px'}} onClick={()=>{setRawInput('');setParsed(null);setParseErr('');setXmlOut('');}}>✕ Leeren</button>}
          </div>

          <textarea style={{...inp, width:'100%', minHeight:130, fontFamily:"'Courier New',monospace",
            fontSize:12, resize:'vertical', lineHeight:1.5,
            border: parseErr?'1px solid rgba(255,107,157,0.5)':undefined}}
            placeholder={fmt==='json'
              ? '{\n  "name": "Max",\n  "alter": 30,\n  "aktiv": true\n}'
              : '<person>\n  <name>Max</name>\n  <alter>30</alter>\n</person>'}
            value={rawInput} onChange={e=>handleInput(e.target.value)}/>

          {!!parseErr && <div style={{color:'#ff6b9d',fontFamily:'monospace',fontSize:11,marginTop:4,padding:'6px 10px',background:'rgba(255,107,157,0.08)',borderRadius:6}}>
            ⚠ {parseErr}
          </div>}

          {!!rawInput && !parseErr && fmt==='json' && (
            <button style={{...S.btn('#4ecdc4',false),fontSize:11,padding:'5px 14px',marginTop:8}}
              onClick={formatJson}>⟳ Formatieren / Einrücken</button>
          )}

          {parsed !== null && (
            <>
              {/* Interaktiver Baum */}
              <div style={{marginTop:14,borderRadius:9,background:'rgba(0,0,0,0.2)',
                border:'1px solid rgba(255,255,255,0.08)',padding:'10px 14px',overflowX:'auto',marginBottom:14}}>
                <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10,marginBottom:8}}>
                  STRUKTUR — Pfeile anklicken zum Auf-/Zuklappen
                </div>
                <JsonNode data={parsed} depth={0}/>
              </div>

              {/* Konvertierung */}
              <div style={{borderRadius:9,background:'rgba(255,255,255,0.03)',
                border:'1px solid rgba(255,255,255,0.08)',padding:'12px 14px'}}>
                <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:8,flexWrap:'wrap'}}>
                  <span style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10}}>
                    KONVERTIERT ALS {fmt==='json'?'XML':'JSON'}
                  </span>
                  <button onClick={()=>copyText(fmt==='json'?xmlOut:JSON.stringify(parsed,null,2))} style={{
                    background:'rgba(78,205,196,0.1)',border:'1px solid rgba(78,205,196,0.2)',
                    borderRadius:6,color:copiedOut?'#4ecdc4':'rgba(255,255,255,0.5)',
                    cursor:'pointer',padding:'2px 10px',fontSize:11}}>
                    {copiedOut?'✓ Kopiert':'⎘ Kopieren'}
                  </button>
                </div>
                <pre style={{margin:0,color:'rgba(255,255,255,0.75)',fontFamily:"'Courier New',monospace",
                  fontSize:12,lineHeight:1.6,overflowX:'auto',whiteSpace:'pre-wrap',wordBreak:'break-word',
                  maxHeight:300}}>
                  {fmt==='json' ? xmlOut : JSON.stringify(parsed,null,2)}
                </pre>
              </div>
            </>
          )}

          {!rawInput && (
            <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:11,
              padding:'16px 0',textAlign:'center'}}>
              {fmt==='json'?'JSON':'XML'} oben einfügen → interaktiver Baum + Konvertierung erscheint
            </div>
          )}
        </div>
      )}

      {tab==='build' && (
        <div>
          {/* Anleitung */}
          <div style={{padding:'10px 12px',borderRadius:8,background:'rgba(78,205,196,0.06)',
            border:'1px solid rgba(78,205,196,0.15)',marginBottom:14}}>
            <div style={{color:'#4ecdc4',fontFamily:'monospace',fontSize:10,fontWeight:700,marginBottom:6}}>SO BAUST DU EINE STRUKTUR</div>
            <div style={{color:'rgba(255,255,255,0.65)',fontSize:12,lineHeight:1.8,marginBottom:10}}>
              Klicke <strong style={{color:'#4ecdc4'}}>„+ Feld hinzufügen"</strong> und wähle einen Typ. Felder können beliebig tief verschachtelt werden.
            </div>
            <div style={{display:'grid',gridTemplateColumns:mobile?'1fr':'1fr 1fr 1fr',gap:8,marginBottom:10}}>
              {[
                ['Text (string)','#a8f0eb','Normaler Text.\nz.B. Name, Adresse, E-Mail','„Max Mustermann"'],
                ['Zahl (number)','#ffe66d','Eine Zahl.\nz.B. Alter, Preis, Menge','42 oder 3.14'],
                ['Ja/Nein (boolean)','#ff6b9d','Wahrheitswert.\nz.B. aktiv, sichtbar','true oder false'],
                ['Objekt { }','#4ecdc4','Zusammengehörige Felder.\nz.B. eine Person mit Name + Alter','{ name, alter }'],
                ['Liste [ ]','#ffe66d','Mehrere gleichartige Einträge.\nz.B. eine Mitarbeiterliste','[ {...}, {...} ]'],
                ['Leer (null)','rgba(255,255,255,0.4)','Kein Wert vorhanden.\nz.B. optionales Feld','null'],
              ].map(([title,color,desc,ex])=>(
                <div key={title} style={{background:'rgba(0,0,0,0.2)',borderRadius:7,padding:'7px 9px'}}>
                  <div style={{color,fontFamily:'monospace',fontSize:10,fontWeight:700,marginBottom:2}}>{title}</div>
                  <div style={{color:'rgba(255,255,255,0.5)',fontSize:11,lineHeight:1.5,whiteSpace:'pre-line',marginBottom:3}}>{desc}</div>
                  <code style={{color:'rgba(255,255,255,0.35)',fontSize:10}}>{ex}</code>
                </div>
              ))}
            </div>
            <div style={{color:'rgba(255,255,255,0.4)',fontSize:11,lineHeight:1.6}}>
              💡 Beispiel „Mitarbeiterliste": Root-Objekt → Feld „Mitarbeiter" als <span style={{color:'#ffe66d'}}>Liste [ ]</span> → in der Liste Einträge als <span style={{color:'#4ecdc4'}}>Objekt {'{ }'}</span> mit Feldern „vorname" und „nachname" als <span style={{color:'#a8f0eb'}}>Text</span>.
            </div>
          </div>

          <div style={{borderRadius:9,background:'rgba(255,255,255,0.03)',
            border:'1px solid rgba(255,255,255,0.08)',padding:'12px 14px',marginBottom:14,overflowX:'auto'}}>
            <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10,marginBottom:10}}>
              STRUKTUR AUFBAUEN — Felder hinzufügen, benennen, verschachteln
            </div>
            <BuilderNode node={root} depth={0} onChange={setRoot} onDelete={()=>{}}/>
          </div>

          <div style={{borderRadius:9,background:'rgba(0,0,0,0.2)',
            border:'1px solid rgba(255,255,255,0.08)',padding:'12px 14px'}}>
            <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:8,flexWrap:'wrap'}}>
              <span style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10}}>AUSGABE:</span>
              <FmtToggle value={buildFmt} onChange={setBuildFmt}/>
              <button onClick={copyBuilt} style={{
                background:'rgba(78,205,196,0.1)',border:'1px solid rgba(78,205,196,0.2)',
                borderRadius:6,color:copiedBld?'#4ecdc4':'rgba(255,255,255,0.5)',
                cursor:'pointer',padding:'2px 10px',fontSize:11}}>
                {copiedBld?'✓ Kopiert':'⎘ Kopieren'}
              </button>
              <button onClick={()=>{setTab('view');setFmt('json');doParseJson(builtJson);}} style={{
                background:'rgba(255,255,255,0.05)',border:'1px solid rgba(255,255,255,0.1)',
                borderRadius:6,color:'rgba(255,255,255,0.5)',cursor:'pointer',padding:'2px 10px',fontSize:11}}>
                👁 Im Viewer öffnen
              </button>
            </div>
            <pre style={{margin:0,color:'rgba(255,255,255,0.8)',fontFamily:"'Courier New',monospace",
              fontSize:12,lineHeight:1.6,overflowX:'auto',whiteSpace:'pre-wrap',wordBreak:'break-word',
              maxHeight:400}}>
              {builtOut}
            </pre>
          </div>
        </div>
      )}
    </div>
  );
}



// ──────────────────────────────────────────────────────────── Farb-Rechner ──

// ── Konvertierungsfunktionen ──
function hexToRgb(hex) {
  hex = hex.replace(/^#/, '');
  if (hex.length === 3) hex = hex.split('').map(c=>c+c).join('');
  if (hex.length === 4) hex = hex.split('').map(c=>c+c).join(''); // #rgba
  const n = parseInt(hex.slice(0,6), 16);
  const a = hex.length === 8 ? parseInt(hex.slice(6,8),16)/255 : 1;
  return { r:(n>>16)&255, g:(n>>8)&255, b:n&255, a };
}

function rgbToHex({r,g,b,a=1}) {
  const h = [r,g,b].map(v=>Math.round(v).toString(16).padStart(2,'0')).join('');
  const ah = a < 1 ? Math.round(a*255).toString(16).padStart(2,'0') : '';
  return '#' + h + ah;
}

function rgbToHsl({r,g,b,a=1}) {
  r/=255; g/=255; b/=255;
  const max=Math.max(r,g,b), min=Math.min(r,g,b);
  let h=0,s=0,l=(max+min)/2;
  if (max!==min) {
    const d=max-min;
    s=l>0.5?d/(2-max-min):d/(max+min);
    switch(max){
      case r: h=((g-b)/d+(g<b?6:0))/6; break;
      case g: h=((b-r)/d+2)/6; break;
      case b: h=((r-g)/d+4)/6; break;
    }
  }
  return { h:Math.round(h*360), s:Math.round(s*100), l:Math.round(l*100), a };
}

function hslToRgb({h,s,l,a=1}) {
  s/=100; l/=100;
  const k=n=>(n+h/30)%12;
  const c=s*Math.min(l,1-l);
  const f=n=>l-c*Math.max(-1,Math.min(k(n)-3,Math.min(9-k(n),1)));
  return {r:Math.round(f(0)*255), g:Math.round(f(8)*255), b:Math.round(f(4)*255), a};
}

function rgbToHsv({r,g,b,a=1}) {
  r/=255; g/=255; b/=255;
  const max=Math.max(r,g,b), min=Math.min(r,g,b), d=max-min;
  let h=0;
  if (d!==0){
    switch(max){
      case r: h=((g-b)/d)%6; break;
      case g: h=(b-r)/d+2; break;
      case b: h=(r-g)/d+4; break;
    }
    h=Math.round(h*60); if(h<0) h+=360;
  }
  return {h, s:max===0?0:Math.round(d/max*100), v:Math.round(max*100), a};
}

function rgbToCmyk({r,g,b}) {
  r/=255; g/=255; b/=255;
  const k=1-Math.max(r,g,b);
  if (k===1) return {c:0,m:0,y:0,k:100};
  return {
    c:Math.round((1-r-k)/(1-k)*100),
    m:Math.round((1-g-k)/(1-k)*100),
    y:Math.round((1-b-k)/(1-k)*100),
    k:Math.round(k*100)
  };
}

// Relative luminance (WCAG)
function luminance({r,g,b}) {
  const c = [r,g,b].map(v => {
    v/=255;
    return v<=0.03928 ? v/12.92 : Math.pow((v+0.055)/1.055,2.4);
  });
  return 0.2126*c[0]+0.7152*c[1]+0.0722*c[2];
}

function contrastRatio(lum1, lum2) {
  const [l,d] = lum1>lum2 ? [lum1,lum2] : [lum2,lum1];
  return (l+0.05)/(d+0.05);
}

// Parse any color string input → {r,g,b,a} or null
function parseColor(raw) {
  if (!raw) return null;
  raw = raw.trim();

  // hex
  if (/^#?[0-9a-fA-F]{3,8}$/.test(raw)) {
    try { return hexToRgb(raw.startsWith('#')?raw:'#'+raw); } catch {}
  }

  // rgb / rgba
  const rgbM = raw.match(/rgba?\(\s*([\d.]+)\s*[,\s]\s*([\d.]+)\s*[,\s]\s*([\d.]+)(?:\s*[,\/]\s*([\d.]+%?))?\s*\)/i);
  if (rgbM) {
    const a = rgbM[4] ? (rgbM[4].includes('%') ? parseFloat(rgbM[4])/100 : parseFloat(rgbM[4])) : 1;
    return {r:+rgbM[1],g:+rgbM[2],b:+rgbM[3],a};
  }

  // hsl / hsla
  const hslM = raw.match(/hsla?\(\s*([\d.]+)\s*[,\s]\s*([\d.]+)%?\s*[,\s]\s*([\d.]+)%?(?:\s*[,\/]\s*([\d.]+%?))?\s*\)/i);
  if (hslM) {
    const a = hslM[4] ? (hslM[4].includes('%')?parseFloat(hslM[4])/100:parseFloat(hslM[4])) : 1;
    return hslToRgb({h:+hslM[1],s:+hslM[2],l:+hslM[3],a});
  }

  // CSS color names (basic set)
  const NAMES = {red:[255,0,0],green:[0,128,0],blue:[0,0,255],white:[255,255,255],black:[0,0,0],
    yellow:[255,255,0],cyan:[0,255,255],magenta:[255,0,255],orange:[255,165,0],purple:[128,0,128],
    pink:[255,192,203],gray:[128,128,128],grey:[128,128,128],lime:[0,255,0],navy:[0,0,128],
    teal:[0,128,128],silver:[192,192,192],maroon:[128,0,0],olive:[128,128,0]};
  const name = NAMES[raw.toLowerCase()];
  if (name) return {r:name[0],g:name[1],b:name[2],a:1};

  return null;
}

function fmt2(n) { return Math.round(n*100)/100; }

function FarbRechner({ toast, mobile }) {
  const [input,    setInput]    = useState('');
  const [pickerHex,setPickerHex]= useState('#4ecdc4');
  const [color,    setColor]    = useState(null); // {r,g,b,a}
  const [copied,   setCopied]   = useState('');

  function applyColor(rgba) {
    setColor(rgba);
    // sync picker (ignore alpha for picker)
    setPickerHex(rgbToHex({...rgba,a:1}));
  }

  function handleInput(val) {
    setInput(val);
    const c = parseColor(val);
    if (c) applyColor(c);
    else if (!val.trim()) setColor(null);
  }

  function handlePicker(hex) {
    setPickerHex(hex);
    const c = hexToRgb(hex);
    // preserve alpha from current color
    const a = color?.a ?? 1;
    setInput(hex);
    applyColor({...c, a});
    setInput(hex);
  }

  async function copy(text, key) {
    try { await navigator.clipboard.writeText(text); setCopied(key); setTimeout(()=>setCopied(''),1800); }
    catch { toast('Kopieren fehlgeschlagen'); }
  }

  const inp = {...S.inp, fontSize:13, padding:'7px 9px', boxSizing:'border-box'};

  // Derived formats
  const formats = color ? (() => {
    const {r,g,b,a} = color;
    const hsl = rgbToHsl(color);
    const hsv = rgbToHsv(color);
    const cmyk = rgbToCmyk(color);
    const hex6 = rgbToHex({r,g,b,a:1});
    const hex8 = rgbToHex({r,g,b,a});
    const hasAlpha = a < 1;

    const lum = luminance({r,g,b});
    const contrastWhite = fmt2(contrastRatio(lum, 1));
    const contrastBlack = fmt2(contrastRatio(lum, 0));
    const wcagWhite = contrastWhite>=7?'AAA':contrastWhite>=4.5?'AA':contrastWhite>=3?'AA (groß)':'✗';
    const wcagBlack = contrastBlack>=7?'AAA':contrastBlack>=4.5?'AA':contrastBlack>=3?'AA (groß)':'✗';
    const textColor = lum > 0.179 ? '#000' : '#fff';

    return [
      {key:'hex6',  label:'HEX',           value: hex6,                                          copy: hex6},
      {key:'hex8',  label:'HEX + Alpha',   value: hex8,                                          copy: hex8,  hide: !hasAlpha},
      {key:'rgb',   label:'RGB',           value: `rgb(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)})`,                              copy: `rgb(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)})`},
      {key:'rgba',  label:'RGBA',          value: `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${fmt2(a)})`,                 copy: `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${fmt2(a)})`},
      {key:'hsl',   label:'HSL',           value: `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`,         copy: `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`},
      {key:'hsla',  label:'HSLA',          value: `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${fmt2(a)})`, copy: `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${fmt2(a)})`},
      {key:'hsv',   label:'HSV / HSB',     value: `hsv(${hsv.h}°, ${hsv.s}%, ${hsv.v}%)`,        copy: `hsv(${hsv.h}, ${hsv.s}%, ${hsv.v}%)`},
      {key:'cmyk',  label:'CMYK',          value: `cmyk(${cmyk.c}%, ${cmyk.m}%, ${cmyk.y}%, ${cmyk.k}%)`, copy: `cmyk(${cmyk.c}%, ${cmyk.m}%, ${cmyk.y}%, ${cmyk.k}%)`},
      {key:'css',   label:'CSS custom property', value: `--color: ${hex6};`,                   copy: `--color: ${hex6};`},
      {key:'r',     label:'R (0–255)',      value: String(Math.round(r)),                         copy: String(Math.round(r)), muted:true},
      {key:'g',     label:'G (0–255)',      value: String(Math.round(g)),                         copy: String(Math.round(g)), muted:true},
      {key:'b',     label:'B (0–255)',      value: String(Math.round(b)),                         copy: String(Math.round(b)), muted:true},
      {key:'a',     label:'Alpha (0–1)',    value: fmt2(a),                                       copy: String(fmt2(a)), muted:true},
      {key:'lum',   label:'Relative Luminanz', value: fmt2(lum),                                 copy: String(fmt2(lum)), muted:true},
      {key:'cw',    label:'Kontrast zu Weiß', value: `${contrastWhite}:1 — WCAG: ${wcagWhite}`,  copy: `${contrastWhite}:1`, muted:true},
      {key:'cb',    label:'Kontrast zu Schwarz', value: `${contrastBlack}:1 — WCAG: ${wcagBlack}`, copy: `${contrastBlack}:1`, muted:true},
    ].filter(f=>!f.hide);
  })() : [];

  const previewBg = color ? `rgb(${Math.round(color.r)},${Math.round(color.g)},${Math.round(color.b)})` : 'rgba(255,255,255,0.05)';
  const previewText = color ? (luminance(color) > 0.179 ? '#000' : '#fff') : 'rgba(255,255,255,0.3)';

  // Gradient bar stops for hue slider background
  const hueBar = 'linear-gradient(to right,#f00,#ff0,#0f0,#0ff,#00f,#f0f,#f00)';

  return (
    <div>
      {/* Eingabe-Zeile */}
      <div style={{display:'flex', gap:10, alignItems:'center', marginBottom:16, flexWrap:'wrap'}}>
        <div style={{flex:1, minWidth:160}}>
          <div style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:10,marginBottom:4}}>
            Farbe eingeben (HEX, RGB, HSL, Farbname…)
          </div>
          <input style={{...inp, width:'100%',
            borderColor: input && !color ? 'rgba(255,107,157,0.5)' : undefined}}
            placeholder="#4ecdc4 oder rgb(78,205,196) oder hsl(177,55%,55%)"
            value={input} onChange={e=>handleInput(e.target.value)}/>
        </div>
        {/* Color Picker */}
        <div>
          <div style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:10,marginBottom:4}}>
            Color Picker
          </div>
          <div style={{position:'relative', width:42, height:36}}>
            <div style={{width:42,height:36,borderRadius:7,background:pickerHex,
              border:'2px solid rgba(255,255,255,0.15)',cursor:'pointer',overflow:'hidden'}}>
              <input type="color" value={pickerHex}
                onChange={e=>handlePicker(e.target.value)}
                style={{opacity:0,position:'absolute',inset:0,width:'100%',height:'100%',cursor:'pointer',border:'none',padding:0}}/>
            </div>
          </div>
        </div>
        {/* Alpha Slider */}
        {!!color && (
          <div style={{flex:'0 0 140px'}}>
            <div style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:10,marginBottom:4}}>
              Alpha / Transparenz: {Math.round((color.a??1)*100)}%
            </div>
            <input type="range" min={0} max={1} step={0.01}
              value={color?.a??1}
              onChange={e=>applyColor({...color, a:parseFloat(e.target.value)})}
              style={{width:'100%', accentColor:'#4ecdc4'}}/>
          </div>
        )}
        {!!input && <button style={{...S.btn('#ff6b9d',false),fontSize:11,padding:'7px 14px'}}
          onClick={()=>{setInput('');setColor(null);}}>✕ Leeren</button>}
      </div>

      {/* Vorschau */}
      {color && (
        <div style={{borderRadius:10,overflow:'hidden',marginBottom:16,border:'1px solid rgba(255,255,255,0.08)'}}>
          {/* Checkerboard + Farbe */}
          <div style={{
            height:80,
            background:`linear-gradient(${previewBg},${previewBg}),
              repeating-conic-gradient(rgba(255,255,255,0.1) 0% 25%, transparent 0% 50%) 0 0 / 16px 16px`,
            display:'flex',alignItems:'center',justifyContent:'center'}}>
            <div style={{
              background: color.a < 1
                ? `rgba(${Math.round(color.r)},${Math.round(color.g)},${Math.round(color.b)},${color.a})`
                : previewBg,
              width:'100%', height:'100%', display:'flex',alignItems:'center',justifyContent:'center'}}>
              <span style={{color:previewText, fontFamily:'monospace', fontSize:13, fontWeight:700,
                textShadow: luminance(color)>0.5?'0 1px 2px rgba(0,0,0,0.2)':'0 1px 2px rgba(0,0,0,0.5)'}}>
                {rgbToHex({...color,a:1}).toUpperCase()}
              </span>
            </div>
          </div>
          {/* Komplementärfarbe Vorschau */}
          <div style={{display:'flex',height:32}}>
            {[0,30,60,90,120,150,180,210,240,270,300,330].map(dh=>{
              const hsl = rgbToHsl(color);
              const c2 = hslToRgb({...hsl, h:(hsl.h+dh)%360, a:1});
              return <div key={dh} style={{flex:1,background:`rgb(${c2.r},${c2.g},${c2.b})`}} title={`+${dh}°`}/>;
            })}
          </div>
          <div style={{padding:'4px 10px',background:'rgba(0,0,0,0.2)',
            color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:9}}>
            Farbrad-Vorschau (Töne in 30°-Schritten)
          </div>
        </div>
      )}

      {/* Format-Ausgaben */}
      {!!formats.length && (
        <div style={{display:'flex',flexDirection:'column',gap:4}}>
          {formats.map(f=>(
            <div key={f.key} onClick={()=>copy(f.copy, f.key)}
              style={{display:'flex',justifyContent:'space-between',alignItems:'center',
                padding:'7px 10px',borderRadius:7,cursor:'pointer',userSelect:'none',
                background:'rgba(255,255,255,0.03)',border:'1px solid rgba(255,255,255,0.05)',
                transition:'background 0.15s'}}
              onMouseEnter={e=>e.currentTarget.style.background='rgba(255,255,255,0.07)'}
              onMouseLeave={e=>e.currentTarget.style.background='rgba(255,255,255,0.03)'}>
              <span style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:11,minWidth:120}}>{f.label}</span>
              <code style={{color: f.muted?'rgba(255,255,255,0.55)':'#4ecdc4',
                fontFamily:"'Courier New',monospace",fontSize:12,fontWeight:700,
                flex:1,textAlign:'right',marginLeft:8,wordBreak:'break-all'}}>
                {f.value}
              </code>
              <span style={{color:copied===f.key?'#4ecdc4':'rgba(255,255,255,0.2)',
                fontSize:14,marginLeft:10,flexShrink:0,transition:'color 0.2s'}}>
                {copied===f.key?'✓':'⎘'}
              </span>
            </div>
          ))}
        </div>
      )}

      {!color && !input && (
        <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:11,
          padding:'20px 0',textAlign:'center'}}>
          Farbe eingeben oder im Color Picker auswählen → alle Formate werden berechnet
        </div>
      )}
      {!!input && !color && (
        <div style={{color:'#ff6b9d',fontFamily:'monospace',fontSize:11,padding:'8px 0'}}>
          Format nicht erkannt · Beispiele: <code>#ff6b9d</code> · <code>rgb(255, 107, 157)</code> · <code>hsl(340, 100%, 71%)</code>
        </div>
      )}
    </div>
  );
}

// ──────────────────────────────────────────────────────────── QR-Generator ──

const QR_SIZES   = [128, 192, 256, 320, 512];
const DOT_STYLES = [
  { id:'square',   label:'Quadrat' },
  { id:'rounded',  label:'Abgerundet' },
  { id:'dots',     label:'Punkte' },
  { id:'classy',   label:'Elegant' },
];
const CORNER_STYLES = [
  { id:'square',   label:'Quadrat' },
  { id:'rounded',  label:'Abgerundet' },
  { id:'dot',      label:'Punkt' },
];
const CAP_POS = [
  { id:'top',    label:'Oben' },
  { id:'bottom', label:'Unten' },
  { id:'none',   label:'Keine' },
];

function drawQrToCanvas(targetCanvas, data, opts) {
  const { size, fgColor, bgColor, margin, caption, captionPos, captionColor, captionSize } = opts;
  const capH   = caption && captionPos !== 'none' ? (Number(captionSize) + 16) : 0;
  const totalH = Number(size) + capH;

  // Step 1: generate QR to a temp canvas via qrcode library (browser-compatible)
  const tmp = document.createElement('canvas');
  return QRCode.toCanvas(tmp, data, {
    width:  Number(size),
    margin: Number(margin) || 1,
    color:  { dark: fgColor, light: bgColor },
    errorCorrectionLevel: 'M',
  }).then(() => {
    // Step 2: compose onto target canvas (with optional caption)
    targetCanvas.width  = Number(size);
    targetCanvas.height = totalH;
    const ctx = targetCanvas.getContext('2d');

    // Background
    ctx.fillStyle = bgColor;
    ctx.fillRect(0, 0, Number(size), totalH);

    // QR image
    const qrTop = captionPos === 'top' ? capH : 0;
    ctx.drawImage(tmp, 0, qrTop, Number(size), Number(size));

    // Caption
    if (caption && captionPos !== 'none') {
      ctx.fillStyle    = captionColor;
      ctx.font         = `${captionSize}px sans-serif`;
      ctx.textAlign    = 'center';
      ctx.textBaseline = 'middle';
      const cy = captionPos === 'top' ? capH / 2 : Number(size) + capH / 2;
      ctx.fillText(caption, Number(size) / 2, cy, Number(size) - 16);
    }
  }).catch(() => {
    // Show error on canvas
    targetCanvas.width  = Number(size);
    targetCanvas.height = Number(size);
    const ctx = targetCanvas.getContext('2d');
    ctx.fillStyle = bgColor;
    ctx.fillRect(0, 0, Number(size), Number(size));
    ctx.fillStyle = '#ff6b9d';
    ctx.font = '13px monospace';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText('Ungültige URL', Number(size)/2, Number(size)/2);
  });
}

function QrGenerator({ toast, mobile }) {
  const canvasRef  = useRef(null);
  const [tab,      setTab]      = useState('create');
  const [saved,    setSaved]    = useState([]);
  const [editing,  setEditing]  = useState(null);
  const [generated, setGenerated] = useState(false); // whether QR is shown

  const DEFAULTS = {
    label:'', url:'', size:256, fgColor:'#000000', bgColor:'#ffffff',
    margin:4, dotStyle:'square', cornerStyle:'square',
    caption:'', captionPos:'bottom', captionColor:'#000000', captionSize:14,
  };
  const [form, setForm] = useState(DEFAULTS);
  const [busy, setBusy] = useState(false);

  const set = (k, v) => { setForm(p => ({...p, [k]: v})); setGenerated(false); };

  const loadSaved = () => api('/tools/qrcodes').then(setSaved).catch(()=>{});
  useEffect(()=>{ loadSaved(); },[]);

  const generate = async () => {
    if (!form.url.trim()) { toast('URL erforderlich','error'); return; }
    if (!canvasRef.current) return;
    await drawQrToCanvas(canvasRef.current, form.url, {
      size:form.size, fgColor:form.fgColor, bgColor:form.bgColor,
      margin:form.margin, dotStyle:form.dotStyle, cornerStyle:form.cornerStyle,
      caption:form.caption, captionPos:form.captionPos,
      captionColor:form.captionColor, captionSize:form.captionSize,
    });
    setGenerated(true);
  };

  const download = () => {
    if (!canvasRef.current || !generated) return;
    const a = document.createElement('a');
    a.href = canvasRef.current.toDataURL('image/png');
    a.download = `qrcode-${form.label||'export'}.png`;
    a.click();
  };

  const save = async () => {
    if (!form.url.trim()) { toast('URL erforderlich','error'); return; }
    setBusy(true);
    try {
      const payload = { label:form.label, url:form.url, size:form.size,
        fg_color:form.fgColor, bg_color:form.bgColor, margin:form.margin,
        dot_style:form.dotStyle, corner_style:form.cornerStyle,
        caption:form.caption, caption_pos:form.captionPos,
        caption_color:form.captionColor, caption_size:form.captionSize };
      if (editing) {
        const r = await api(`/tools/qrcodes/${editing}`, {method:'PUT', body:payload});
        setSaved(p=>p.map(q=>q.id===r.id?r:q));
        setEditing(null);
        toast('Gespeichert ✓');
      } else {
        const r = await api('/tools/qrcodes', {body:payload});
        setSaved(p=>[r,...p]);
        toast('QR-Code gespeichert ✓');
      }
    } catch(e) { toast(e.message,'error'); }
    setBusy(false);
  };

  const deleteQr = async id => {
    await api(`/tools/qrcodes/${id}`,{method:'DELETE'});
    setSaved(p=>p.filter(q=>q.id!==id));
    toast('Gelöscht');
  };

  const editQr = qr => {
    setForm({ label:qr.label, url:qr.url, size:qr.size,
      fgColor:qr.fg_color, bgColor:qr.bg_color, margin:qr.margin,
      dotStyle:qr.dot_style, cornerStyle:qr.corner_style,
      caption:qr.caption, captionPos:qr.caption_pos,
      captionColor:qr.caption_color, captionSize:qr.caption_size });
    setEditing(qr.id);
    setTab('create');
  };

  const inp = {...S.inp, fontSize:13, padding:'7px 9px', boxSizing:'border-box'};
  const lbl = {color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:10,marginBottom:3};

  const tabs = [{id:'create',label:'✏️ Erstellen'},{id:'saved',label:`📋 Gespeichert (${saved.length})`}];

  return (
    <div>
      <div style={{display:'flex',gap:6,marginBottom:18}}>
        {tabs.map(t=>(
          <button key={t.id} onClick={()=>setTab(t.id)} style={{
            padding:'6px 16px',borderRadius:20,fontFamily:'monospace',fontSize:11,cursor:'pointer',
            background:tab===t.id?'#4ecdc4':'rgba(255,255,255,0.05)',
            color:tab===t.id?'#0d0d0f':'rgba(255,255,255,0.65)',
            border:tab===t.id?'none':'1px solid rgba(255,255,255,0.12)',
            fontWeight:tab===t.id?700:400}}>{t.label}</button>
        ))}
        {editing && <span style={{color:'#ffe66d',fontFamily:'monospace',fontSize:10,alignSelf:'center',marginLeft:4}}>● Bearbeitung</span>}
      </div>

      {tab==='create' && (
        <div style={{display:'grid',gridTemplateColumns:mobile?'1fr':'1fr 1fr',gap:16,alignItems:'start'}}>
          {/* Linke Spalte: Einstellungen */}
          <div>
            <div style={{marginBottom:10}}>
              <div style={lbl}>Name / Bezeichnung</div>
              <input style={{...inp,width:'100%'}} placeholder="z.B. Meine Webseite" value={form.label} onChange={e=>set('label',e.target.value)}/>
            </div>
            <div style={{marginBottom:10}}>
              <div style={lbl}>URL / Inhalt *</div>
              <input style={{...inp,width:'100%'}} placeholder="https://example.com" autoCapitalize="none" value={form.url} onChange={e=>set('url',e.target.value)}/>
            </div>
            <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:8,marginBottom:10}}>
              <div>
                <div style={lbl}>Größe (px)</div>
                <select style={{...inp,width:'100%',cursor:'pointer'}} value={form.size} onChange={e=>set('size',parseInt(e.target.value))}>
                  {QR_SIZES.map(s=><option key={s} value={s}>{s}×{s}</option>)}
                </select>
              </div>
              <div>
                <div style={lbl}>Rand (Module)</div>
                <input type="number" min={0} max={10} style={{...inp,width:'100%'}} value={form.margin} onChange={e=>set('margin',parseInt(e.target.value)||0)}/>
              </div>
            </div>
            <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:8,marginBottom:10}}>
              <div>
                <div style={lbl}>Punkte-Stil</div>
                <select style={{...inp,width:'100%',cursor:'pointer'}} value={form.dotStyle} onChange={e=>set('dotStyle',e.target.value)}>
                  {DOT_STYLES.map(d=><option key={d.id} value={d.id}>{d.label}</option>)}
                </select>
              </div>
              <div>
                <div style={lbl}>Ecken-Stil</div>
                <select style={{...inp,width:'100%',cursor:'pointer'}} value={form.cornerStyle} onChange={e=>set('cornerStyle',e.target.value)}>
                  {CORNER_STYLES.map(c=><option key={c.id} value={c.id}>{c.label}</option>)}
                </select>
              </div>
            </div>
            <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:8,marginBottom:10}}>
              <div>
                <div style={lbl}>Farbe (Punkte)</div>
                <div style={{display:'flex',gap:6,alignItems:'center'}}>
                  <input type="color" value={form.fgColor} onChange={e=>set('fgColor',e.target.value)}
                    style={{width:38,height:34,borderRadius:6,border:'1px solid rgba(255,255,255,0.15)',cursor:'pointer',padding:2,background:'transparent'}}/>
                  <input style={{...inp,flex:1,fontSize:11}} value={form.fgColor} onChange={e=>set('fgColor',e.target.value)}/>
                </div>
              </div>
              <div>
                <div style={lbl}>Hintergrund</div>
                <div style={{display:'flex',gap:6,alignItems:'center'}}>
                  <input type="color" value={form.bgColor} onChange={e=>set('bgColor',e.target.value)}
                    style={{width:38,height:34,borderRadius:6,border:'1px solid rgba(255,255,255,0.15)',cursor:'pointer',padding:2,background:'transparent'}}/>
                  <input style={{...inp,flex:1,fontSize:11}} value={form.bgColor} onChange={e=>set('bgColor',e.target.value)}/>
                </div>
              </div>
            </div>
            {/* Beschriftung */}
            <div style={{padding:'10px 12px',borderRadius:8,background:'rgba(255,255,255,0.03)',
              border:'1px solid rgba(255,255,255,0.07)',marginBottom:10}}>
              <div style={{...lbl,marginBottom:8}}>BESCHRIFTUNG</div>
              <div style={{marginBottom:8}}>
                <div style={lbl}>Position</div>
                <div style={{display:'flex',gap:5}}>
                  {CAP_POS.map(p=>(
                    <button key={p.id} onClick={()=>set('captionPos',p.id)} style={{
                      padding:'4px 12px',borderRadius:16,fontFamily:'monospace',fontSize:10,cursor:'pointer',
                      background:form.captionPos===p.id?'rgba(78,205,196,0.15)':'rgba(255,255,255,0.04)',
                      border:`1px solid ${form.captionPos===p.id?'#4ecdc4':'rgba(255,255,255,0.12)'}`,
                      color:form.captionPos===p.id?'#4ecdc4':'rgba(255,255,255,0.55)',fontWeight:form.captionPos===p.id?700:400}}>
                      {p.label}
                    </button>
                  ))}
                </div>
              </div>
              {form.captionPos !== 'none' && (
                <>
                  <input style={{...inp,width:'100%',marginBottom:8,boxSizing:'border-box'}} placeholder="Text der Beschriftung"
                    value={form.caption} onChange={e=>set('caption',e.target.value)}/>
                  <div style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:8}}>
                    <div>
                      <div style={lbl}>Farbe</div>
                      <div style={{display:'flex',gap:5,alignItems:'center'}}>
                        <input type="color" value={form.captionColor} onChange={e=>set('captionColor',e.target.value)}
                          style={{width:34,height:30,borderRadius:5,border:'1px solid rgba(255,255,255,0.15)',cursor:'pointer',padding:1,background:'transparent'}}/>
                        <input style={{...inp,flex:1,fontSize:10}} value={form.captionColor} onChange={e=>set('captionColor',e.target.value)}/>
                      </div>
                    </div>
                    <div>
                      <div style={lbl}>Schriftgröße</div>
                      <input type="number" min={8} max={36} style={{...inp,width:'100%'}}
                        value={form.captionSize} onChange={e=>set('captionSize',parseInt(e.target.value)||14)}/>
                    </div>
                  </div>
                </>
              )}
            </div>
            <div style={{display:'flex',gap:8}}>
              <button onClick={generate}
                style={{...S.btn('#4ecdc4'),flex:1,textAlign:'center',padding:'10px 0',fontWeight:700}}>
                ▶ Generieren
              </button>
              {editing && <button onClick={()=>{setEditing(null);setForm(DEFAULTS);setGenerated(false);}}
                style={{...S.btn('#ff6b9d',true),padding:'0 12px'}}>✕</button>}
            </div>
          </div>

          {/* Rechte Spalte: Vorschau */}
          <div style={{display:'flex',flexDirection:'column',alignItems:'center',gap:12}}>
            <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10}}>VORSCHAU</div>
            <canvas ref={canvasRef} style={{
              borderRadius:8, maxWidth:'100%',
              boxShadow:'0 4px 24px rgba(0,0,0,0.4)',
              border:'1px solid rgba(255,255,255,0.08)',
              display: generated ? 'block' : 'none'}}/>
            {!generated && (
              <div style={{width:200,height:200,borderRadius:8,background:'rgba(255,255,255,0.04)',
                border:'1px solid rgba(255,255,255,0.08)',display:'flex',flexDirection:'column',
                alignItems:'center',justifyContent:'center',gap:10}}>
                <span style={{fontSize:36,opacity:0.2}}>◻</span>
                <span style={{color:'rgba(255,255,255,0.2)',fontFamily:'monospace',fontSize:10,textAlign:'center'}}>
                  Einstellungen wählen{String.fromCharCode(10)}dann „Generieren"
                </span>
              </div>
            )}
            {generated && (
              <div style={{display:'flex',gap:8}}>
                <button onClick={download} style={{...S.btn('#ffe66d',true),fontSize:11,padding:'6px 14px'}}>
                  ↓ PNG laden
                </button>
                <button onClick={save} disabled={busy}
                  style={{...S.btn('#4ecdc4'),fontSize:11,padding:'6px 14px',opacity:busy?0.5:1}}>
                  {busy?'…':editing?'✓ Aktualisieren':'💾 Speichern'}
                </button>
              </div>
            )}
            <div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:9,textAlign:'center',lineHeight:1.6}}>
              Lokal generiert · Nichts wird ohne Speichern gespeichert
            </div>
          </div>
        </div>
      )}

      {tab==='saved' && (
        <div>
          {saved.length===0
            ? <div style={{color:'rgba(255,255,255,0.35)',fontFamily:'monospace',fontSize:11,padding:'20px 0',textAlign:'center'}}>
                Noch keine QR-Codes gespeichert
              </div>
            : <div style={{display:'flex',flexDirection:'column',gap:8}}>
                {saved.map(qr=>(
                  <SavedQrCard key={qr.id} qr={qr} onEdit={editQr} onDelete={deleteQr} toast={toast}/>
                ))}
              </div>
          }
        </div>
      )}
    </div>
  );
}

function SavedQrCard({ qr, onEdit, onDelete, toast }) {
  const canvasRef = useRef(null);
  const [expanded, setExpanded] = useState(false);

  useEffect(()=>{
    if (!expanded || !canvasRef.current) return;
    drawQrToCanvas(canvasRef.current, qr.url, {
      size: Math.min(qr.size, 192), fgColor:qr.fg_color, bgColor:qr.bg_color,
      margin:qr.margin, dotStyle:qr.dot_style, cornerStyle:qr.corner_style,
      caption:qr.caption, captionPos:qr.caption_pos,
      captionColor:qr.caption_color, captionSize:qr.caption_size,
    });
  },[expanded, qr]);

  const download = () => {
    if (!canvasRef.current) return;
    // Re-draw in full size for download
    const tmp = document.createElement('canvas');
    drawQrToCanvas(tmp, qr.url, {
      size:qr.size, fgColor:qr.fg_color, bgColor:qr.bg_color,
      margin:qr.margin, dotStyle:qr.dot_style, cornerStyle:qr.corner_style,
      caption:qr.caption, captionPos:qr.caption_pos,
      captionColor:qr.caption_color, captionSize:qr.caption_size,
    }).then(()=>{
      const a = document.createElement('a');
      a.href = tmp.toDataURL('image/png');
      a.download = `qrcode-${qr.label||qr.id}.png`;
      a.click();
    });
  };

  return (
    <div style={{borderRadius:9,background:'rgba(255,255,255,0.03)',border:'1px solid rgba(255,255,255,0.07)',overflow:'hidden'}}>
      <div style={{display:'flex',alignItems:'center',gap:10,padding:'10px 12px',cursor:'pointer'}}
        onClick={()=>setExpanded(p=>!p)}>
        <span style={{color:'rgba(255,255,255,0.3)',fontSize:11,width:14}}>{expanded?'▾':'▸'}</span>
        <div style={{flex:1,minWidth:0}}>
          <div style={{color:'#fff',fontFamily:'monospace',fontSize:13,fontWeight:700,
            overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>
            {qr.label||'QR-Code'}
          </div>
          <div style={{color:'rgba(78,205,196,0.6)',fontFamily:'monospace',fontSize:10,
            overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{qr.url}</div>
        </div>
        <div style={{display:'flex',gap:4}} onClick={e=>e.stopPropagation()}>
          <button onClick={download} style={{...S.btn('#ffe66d',true),fontSize:10,padding:'4px 8px'}}>↓</button>
          <button onClick={()=>onEdit(qr)} style={{...S.btn('#4ecdc4',true),fontSize:10,padding:'4px 8px'}}>✎</button>
          <button onClick={()=>onDelete(qr.id)} style={{...S.btn('#ff6b9d',true),fontSize:10,padding:'4px 8px'}}>✕</button>
        </div>
      </div>
      {expanded && (
        <div style={{padding:'0 12px 12px',display:'flex',gap:14,flexWrap:'wrap',alignItems:'flex-start'}}>
          <canvas ref={canvasRef} style={{borderRadius:6,flexShrink:0,
            boxShadow:'0 2px 12px rgba(0,0,0,0.3)',border:'1px solid rgba(255,255,255,0.08)',maxWidth:192}}/>
          <div style={{fontFamily:'monospace',fontSize:10,color:'rgba(255,255,255,0.45)',lineHeight:2}}>
            {[['Größe',`${qr.size}×${qr.size}px`],['Punkte',qr.dot_style],['Ecken',qr.corner_style],
              ['Rand',qr.margin],['Beschriftung',qr.caption||'—'],['Position',qr.caption_pos]].map(([k,v])=>(
              <div key={k}><span style={{color:'rgba(255,255,255,0.25)'}}>{k}:</span> {v}</div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}


const TOOL_DEFS = [
  {id:'cron',    label:'⏱ Crontab',   desc:'Crontab erklären und erstellen', ready:true,
   tabs:[{id:'explain',label:'Erklären'},{id:'build',label:'Erstellen'}],
   components:{explain:CronExplainer,build:CronBuilder}},
  {id:'elektro', label:'⚡ Elektro',   desc:'Ohm, Leistung, Kosten, Widerstände', ready:true, component:ElektroRechner},
  {id:'json',      label:'{ } JSON',       desc:'JSON ansehen & bauen',         ready:true, component:JsonTool},
  {id:'regex',     label:'🔍 Regex',       desc:'Regex bauen & erklären',       ready:true,  component:RegexBuilder},
  {id:'diff',      label:'± Text Diff',    desc:'Zwei Texte vergleichen',               ready:true,  component:TextDiff},
  {id:'netzwerk',  label:'🌐 Netzwerk',    desc:'IP, Subnetz, Geschwindigkeit, CIDR',   ready:true,  component:NetzwerkRechner},
  {id:'datetime',  label:'📅 Datetime',   desc:'Zeiten umrechnen · Geburtstag · Differenz', ready:true, component:DatetimeRechner},
  {id:'farben',    label:'🎨 Farben',     desc:'HEX, RGB, HSL, OKLCH umrechnen',  ready:true, component:FarbRechner},
  {id:'keygen',    label:'🔐 Key-Gen',    desc:'Sichere Keys & Secrets generieren',  ready:true, component:KeyGen},
  {id:'qrcode',    label:'◻ QR-Code',     desc:'QR-Codes erstellen & verwalten',     ready:true, component:QrGenerator},
];

export default function DevTools({ toast, mobile, nav }) {
  const [activeTool, setActiveTool] = useState('cron');
  const [activeTab,  setActiveTab]  = useState('explain');

  // Von außen navigieren (z.B. über Suche)
  useEffect(() => {
    if (nav?.tool) {
      setActiveTool(nav.tool);
      setActiveTab('');
    }
  }, [nav?.ts]);
  const chipScrollRef = useRef(null);
  const [chipScroll, setChipScroll] = useState({ left: false, right: true });

  const onChipScroll = () => {
    const el = chipScrollRef.current;
    if (!el) return;
    setChipScroll({
      left:  el.scrollLeft > 4,
      right: el.scrollLeft < el.scrollWidth - el.clientWidth - 4,
    });
  };
  const active = TOOL_DEFS.find(t=>t.id===activeTool);
  const ActiveComp = active?.tabs ? active.components[activeTab] : active?.component;

  // Erstes Zeichen als Icon, Rest als Text (handles Emoji korrekt via spread)
  const getIcon = lbl => [...lbl][0] ?? '🔧';
  const getText = lbl => { const c=[...lbl]; return c.slice(1).join('').trim() || lbl; };

  return (
    <div style={{padding:mobile?'14px 14px 90px':'36px 44px',maxWidth:860}}>
      <div style={{display:'flex',alignItems:'center',gap:12,marginBottom:16,flexWrap:'wrap'}}>
        <h1 style={{color:'#fff',fontFamily:"'Space Mono',monospace",fontSize:mobile?17:22,margin:0}}>Dev-Tools</h1>
        <span style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:10}}>Konverter · Formatter · Helfer</span>
      </div>

      {/* Tool-Auswahl: Mobile = horizontal scroll mit Pfeil-Hints, Desktop = 2-Spalten-Grid */}
      {mobile ? (
        <div style={{marginBottom:16}}>
          <div style={{display:'flex',alignItems:'center',gap:4}}>
            {chipScroll.left && (
              <button onClick={()=>{ chipScrollRef.current?.scrollBy({left:-160,behavior:'smooth'}); }}
                style={{flexShrink:0,background:'rgba(255,255,255,0.06)',border:'1px solid rgba(255,255,255,0.12)',
                  borderRadius:20,color:'rgba(255,255,255,0.6)',cursor:'pointer',
                  padding:'6px 8px',fontSize:14,lineHeight:1}}>‹</button>
            )}
            <div ref={chipScrollRef} onScroll={onChipScroll}
              style={{display:'flex',gap:6,overflowX:'auto',flex:1,
                padding:'3px 2px 6px',
                scrollbarWidth:'none',WebkitOverflowScrolling:'touch'}}>
              {TOOL_DEFS.map(t=>(
                <button key={t.id}
                  onClick={()=>{ if(t.ready){setActiveTool(t.id);setActiveTab(t.tabs?.[0]?.id||'');} else toast('Kommt bald 🚧'); }}
                  style={{
                    display:'flex', alignItems:'center', gap:6, flexShrink:0,
                    padding:'7px 12px', borderRadius:20, fontFamily:'monospace', fontSize:11,
                    cursor:t.ready?'pointer':'default', whiteSpace:'nowrap',
                    background: activeTool===t.id ? 'rgba(78,205,196,0.15)' : 'rgba(255,255,255,0.05)',
                    border: activeTool===t.id ? '1px solid #4ecdc4' : '1px solid rgba(255,255,255,0.1)',
                    color: activeTool===t.id ? '#4ecdc4' : t.ready ? 'rgba(255,255,255,0.75)' : 'rgba(255,255,255,0.3)',
                    fontWeight: activeTool===t.id ? 700 : 400,
                  }}>
                  <span style={{fontSize:15}}>{getIcon(t.label)}</span>
                  <span>{getText(t.label)}</span>
                </button>
              ))}
            </div>
            {chipScroll.right && (
              <button onClick={()=>{ chipScrollRef.current?.scrollBy({left:160,behavior:'smooth'}); }}
                style={{flexShrink:0,background:'rgba(255,255,255,0.06)',border:'1px solid rgba(255,255,255,0.12)',
                  borderRadius:20,color:'rgba(255,255,255,0.6)',cursor:'pointer',
                  padding:'6px 8px',fontSize:14,lineHeight:1}}>›</button>
            )}
          </div>
        </div>
      ) : (
        <div style={{display:'grid',gridTemplateColumns:'repeat(2,1fr)',gap:5,marginBottom:16}}>
          {TOOL_DEFS.map(t=>(
            <button key={t.id} title={t.desc}
              onClick={()=>{ if(t.ready){setActiveTool(t.id);setActiveTab(t.tabs?.[0]?.id||'');} else toast('Kommt bald 🚧'); }}
              style={{
                display:'flex', alignItems:'center', gap:9,
                padding:'8px 11px', borderRadius:9, fontFamily:'monospace',
                cursor:t.ready?'pointer':'default', textAlign:'left', border:'none',
                background: activeTool===t.id ? 'rgba(78,205,196,0.15)' : t.ready ? 'rgba(255,255,255,0.04)' : 'rgba(255,255,255,0.02)',
                outline: activeTool===t.id ? '1px solid #4ecdc4' : 'none',
              }}>
              <span style={{fontSize:17,flexShrink:0,lineHeight:1,minWidth:22,textAlign:'center'}}>{getIcon(t.label)}</span>
              <div style={{minWidth:0,flex:1}}>
                <div style={{
                  color: activeTool===t.id ? '#4ecdc4' : t.ready ? 'rgba(255,255,255,0.85)' : 'rgba(255,255,255,0.3)',
                  fontSize:12, fontWeight: activeTool===t.id?700:500,
                  overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'}}>
                  {getText(t.label)}
                </div>
                <div style={{color:'rgba(255,255,255,0.28)',fontSize:9,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap',marginTop:1}}>
                  {t.desc}
                </div>
              </div>
            </button>
          ))}
        </div>
      )}

      <div style={{...S.card}}>
        <div style={{display:'flex',alignItems:'center',gap:10,marginBottom:14,flexWrap:'wrap'}}>
          <div style={{...S.head,marginBottom:0}}>{active?.label}</div>
          <span style={{color:'rgba(255,255,255,0.5)',fontFamily:'monospace',fontSize:10}}>{active?.desc}</span>
        </div>
        {active?.tabs&&(
          <div style={{display:'flex',gap:6,marginBottom:16}}>
            {active.tabs.map(tab=>(
              <button key={tab.id} onClick={()=>setActiveTab(tab.id)} style={{
                padding:'6px 18px',borderRadius:20,fontFamily:'monospace',fontSize:11,cursor:'pointer',
                background:activeTab===tab.id?'#4ecdc4':'rgba(255,255,255,0.05)',
                color:      activeTab===tab.id?'#0d0d0f':'rgba(255,255,255,0.5)',
                border:     activeTab===tab.id?'none':'1px solid rgba(255,255,255,0.1)',
                fontWeight: activeTab===tab.id?700:400}}>
                {tab.label}
              </button>
            ))}
          </div>
        )}
        {ActiveComp&&<ActiveComp toast={toast} mobile={mobile}/>}
      </div>
    </div>
  );
}
