/* Pages: Home, Anmeldung, Meldeliste, Info */

const { useState: uS, useEffect: uE } = React;

// Optional-Marker "(freiwillig)" in Labels optisch absetzen (helleres Grau, klein).
const optionalStyle = { color: 'var(--text-3)', fontWeight: 400, textTransform: 'none', letterSpacing: 0 };
function labelWithOptional(text) {
  const marker = '(freiwillig)';
  if (typeof text === 'string' && text.includes(marker)) {
    const before = text.slice(0, text.indexOf(marker)).trimEnd();
    return <>{before} <span style={optionalStyle}>{marker}</span></>;
  }
  return text;
}

// Kleiner Info-Button mit Tooltip — Hover am Desktop, Tap auf Mobile.
function InfoTip({ text }) {
  const [open, setOpen] = uS(false);
  return (
    <span className="infotip">
      <button type="button" className="infotip-btn" aria-label="Mehr Infos"
              onClick={(e) => { e.preventDefault(); setOpen(o => !o); }}
              onMouseEnter={() => setOpen(true)}
              onMouseLeave={() => setOpen(false)}>
        <Icon name="info" size={14}/>
      </button>
      {open && <span className="infotip-bubble" role="tooltip">{text}</span>}
    </span>
  );
}

/* ──────────────── HOME ──────────────── */
function HomePage({ onNav }) {
  return (
    <>
      <div className="hero">
        <div className="hero-bg"></div>
        <div className="hero-photo"></div>
        <div className="hero-inner">
          <div className="hero-left">
            <div className="hero-eyebrow"><span className="line"></span>Doppel-Turnier<span className="line"></span></div>
            <h1 className="hero-title hero-title-display">
              <span className="t-line"><span className="ord">1.</span> <span className="word">Paartal</span></span>
              <span className="t-line t-with-edeka">
                <span className="word">Open</span>
                <HeroPoweredBadge/>
              </span>
            </h1>
          </div>
          <Countdown/>
          <PrizeStar/>
        </div>
      </div>

      <div className="meta-section">
        <div className="meta-inner">
          <div className="meta-cta">
            <button type="button" className="cta-banner" onClick={() => onNav('meldeliste')}>
              <Icon name="racquet" size={18}/>
              <span className="cta-banner-title">Hier geht's zur Anmeldung</span>
              <Icon name="arrow-right" size={16}/>
            </button>
          </div>
          <div className="meta-row">
            <div className="item"><span className="lbl">Konkurrenz</span><span className="val">Herren · alle</span></div>
            <div className="item"><span className="lbl">Spielmodus</span><span className="val">Doppel</span></div>
            <div className="item"><span className="lbl">Startgeld</span><span className="val">50 € / Paarung</span></div>
            <div className="item"><span className="lbl">Preisgeld</span><span className="val">1.000 € gesamt</span></div>
            <div className="item"><span className="lbl">Meldeschluss</span><span className="val">04.08.2026</span></div>
            <div className="item"><span className="lbl">Auslosung</span><span className="val">05.08.2026 · 18:00</span></div>
          </div>

          <hr className="meta-divider"/>

          <div className="intro-block">
            <div className="lbl">Beschreibung</div>
            <div className="intro-text">
              <p>
                Die 1. Paartal Open sind die Premiere unseres reinen Doppelturniers
                und finden in dieser Form zum ersten Mal statt. Gespielt wird im
                klassischen KO-System auf Sandplatz. Wer in der ersten Runde
                verliert, rutscht in die Trostrunde und kann sich auch dort noch
                ein Preisgeld erspielen.
              </p>
              <p>
                Eine offizielle LK-Ranglistenwertung gibt es nicht. Die Setzliste
                richtet sich aber nach den aktuellen Leistungsklassen der gemeldeten
                Spieler, sodass die Paarungen sportlich fair zugelost werden können.
              </p>
              <p>
                Alle Details wie Modus, Spielzeiten, Preisgeldstaffelung und
                Teilnahmebedingungen sind in der{' '}
                <a href="Turnierinfos/Turnierinfos.pdf?v=20260604"
                   target="_blank" rel="noopener noreferrer"
                   className="text-link">vollständigen Turnierausschreibung</a>{' '}
                zu finden.
              </p>
              <p>
                <strong>EDEKA Riasanow</strong> ist Hauptsponsor des
                Turniers und ermöglicht die Veranstaltung in dieser Form
                überhaupt erst. Unterstützt wird das Turnier außerdem von
                weiteren Partnern aus der Region (siehe unten). Für die
                Verpflegung an allen drei Tagen sorgt das Vereinsteam des
                TSV Baar-Ebenhausen.
              </p>
              <p>
                Insgesamt stehen 6 Sandplätze + 1 Hallenplatz zur Verfügung.
              </p>
            </div>
          </div>

          <hr className="meta-divider"/>

          <div className="atmo-block">
            <div className="atmo-strip" aria-hidden="true">
              <div className="atmo-tile atmo-grill"></div>
              <div className="atmo-tile atmo-bier"></div>
              <div className="atmo-tile atmo-kuchen"></div>
            </div>
            <div className="atmo-labels">
              <span>Grill</span>
              <span>Bewirtschaftung</span>
              <span>Kuchen</span>
            </div>
          </div>
        </div>
      </div>

      <SponsorSection />
    </>
  );
}

function SponsorSection() {
  // EDEKA Riasanow ist alleiniger Hauptsponsor (im Hero). Hier sammeln
  // sich die Co-Sponsoren / Partner. Reihenfolge bewusst gewaehlt:
  // VR Bank Bayern Mitte zuerst (groesster Finanzpartner — heisst seit
  // der 2018er Fusion offiziell so, frueher Hallertauer Volksbank),
  // dann die weiteren regionalen Unterstuetzer wie zugesagt.
  const partners = [
    { name: 'Volksbank Raiffeisenbank Bayern Mitte eG', logo: 'uploads/vr.png', href: 'https://www.vr-bayernmitte.de/', logoClass: 'sp-logo-vr' },
    { name: 'DVAG',                      tagline: 'Roland Pietsch',          logo: 'uploads/allfinanz.png',      href: 'https://www.allfinanz.ag/roland.pietsch/index.html', logoClass: 'sp-logo-allfinanz' },
    { name: 'Getränke Hörl',             tagline: 'Claudia Huber',           logo: 'uploads/hoerl_neu.png',      href: 'https://hoerl-getraenke.de/getraenkemarkt/baar-ebenhausen-muenchener-strasse-112.html', logoClass: 'sp-logo-hoerl' },
    { name: 'Fahrschule Fleischer',      logo: 'uploads/fleischer.png',      href: 'https://www.start-fahrschule.de/',                logoClass: 'sp-logo-fleischer' },
    { name: 'GMW Studio',                logo: 'uploads/GMW2.png',           href: 'https://www.gmw-studio.de/',                      logoClass: 'sp-logo-gmw' },
    { name: 'Schanzer Besaitungsservice', logo: 'uploads/schanzer.svg?v=20260603b', href: 'https://schanzer-besaitungsservice.de/',          logoClass: 'sp-logo-schanzer' },
    { name: 'Scaly Academy',             logo: 'uploads/scaly.png',          href: 'https://scaly.com/',                              logoClass: 'sp-logo-scaly' },
  ];
  return (
    <div className="sponsors-section">
      <div className="sponsors-inner">
        <div className="sponsors-head">
          <div>
            <div className="page-eyebrow">Partner</div>
            <h2 className="page-title" style={{fontSize: 32, marginBottom: 0}}>Unsere Partner</h2>
          </div>
        </div>

        <div className="partner-grid">
          {partners.map((p) => {
            const inner = (
              <>
                <div className={`partner-logo ${p.logoClass}`}>
                  <img src={p.logo} alt={p.name}/>
                </div>
                <div className="partner-name">
                  {p.name}{p.tagline && <span className="partner-tagline">{p.tagline}</span>}
                </div>
              </>
            );
            return p.href
              ? <a key={p.name} className="partner-card" href={p.href} target="_blank" rel="noopener noreferrer">{inner}</a>
              : <div key={p.name} className="partner-card">{inner}</div>;
          })}
        </div>
      </div>
    </div>
  );
}

/* ──────────────── ANMELDUNG ──────────────── */
// Anmeldung läuft als Modal über der Meldeliste — kein eigener Routing-
// Eintrag mehr. Per "Anmeldung zum Turnier"-Button auf der Meldelisten-
// Seite getriggert.
function AnmeldungModal({ onSubmit, onClose }) {
  uE(() => { lockBodyScroll(); return unlockBodyScroll; }, []);
  const emptyPlayer = () => ({ first:'', last:'', birthdate:'', club:'', email:'', phone:'', lk:'ohne LK' });
  const empty = { p1: emptyPlayer(), p2: emptyPlayer(), accept: false };
  const [f, setF] = uS(empty);
  const [errors, setErrors] = uS({});
  const [done, setDone] = uS(false);
  // Nachnamen aus dem Submit festhalten, damit der Verwendungszweck
  // im Erfolg-Screen mit den realen Namen vorgeschlagen werden kann
  // (Form wird im selben Schritt resettet, deshalb separater State).
  const [submittedNames, setSubmittedNames] = uS({ p1:'', p2:'' });
  const modalRef = React.useRef(null);

  // Nach einem fehlgeschlagenen Absende-Versuch sofort zum ersten roten
  // Feld scrollen (bzw. zur Sammel-Warnung oben), damit der Fehler nicht
  // uebersehen wird. errors wird bei jedem validate() neu gesetzt -> der
  // Effekt feuert auch bei wiederholten Versuchen.
  uE(() => {
    if (!Object.keys(errors).length || !modalRef.current) return;
    const root = modalRef.current;
    const target = root.querySelector('.input-error') || root.querySelector('.form-error-summary');
    if (!target) return;
    target.scrollIntoView({ behavior: 'smooth', block: 'center' });
    if (target.matches('.input-error') && typeof target.focus === 'function') {
      target.focus({ preventScroll: true });
    }
  }, [errors]);

  const updPlayer = (which, key) => (e) =>
    setF(prev => ({ ...prev, [which]: { ...prev[which], [key]: e.target.value } }));
  const updAccept = (e) => setF(prev => ({ ...prev, accept: e.target.checked }));

  const validate = () => {
    const e = {};
    const checkPlayer = (which) => {
      const p = f[which];
      // Pflichtfelder: Vorname, Nachname, Geburtsdatum, Verein.
      // E-Mail nur fuer Spieler 1 Pflicht (eine Bestaetigung reicht
      // pro Paarung), Telefon und LK sind freiwillig.
      const required = which === 'p1'
        ? ['first','last','birthdate','club','email']
        : ['first','last','birthdate','club'];
      required.forEach(k => {
        if (!String(p[k] ?? '').trim()) e[`${which}_${k}`] = 'Pflichtfeld';
      });
      if (p.email && !/^\S+@\S+\.\S+$/.test(p.email)) e[`${which}_email`] = 'Ungültige E-Mail';
      if (p.birthdate) {
        const d = new Date(p.birthdate);
        const min = new Date('1920-01-01');
        const max = new Date('2015-12-31');
        if (isNaN(d) || d < min || d > max) e[`${which}_birthdate`] = 'Ungültiges Datum';
      }
    };
    checkPlayer('p1');
    checkPlayer('p2');
    if (!f.accept) e.accept = 'Bitte zustimmen';
    setErrors(e);
    return Object.keys(e).length === 0;
  };

  const trimPlayer = (p) => ({
    first: p.first.trim(),
    last: p.last.trim(),
    birthdate: p.birthdate,
    club: p.club.trim(),
    email: p.email.trim(),
    phone: p.phone.trim(),
    lk: p.lk,
  });

  const submit = (e) => {
    e.preventDefault();
    if (!validate()) return;
    const reg = {
      id: 'r' + Date.now(),
      p1: trimPlayer(f.p1),
      p2: trimPlayer(f.p2),
      ts: todayDE(),
    };
    onSubmit(reg);
    setSubmittedNames({ p1: reg.p1.last, p2: reg.p2.last });
    setDone(true);
    setF(empty);
  };

  if (done) {
    const namesPart = [submittedNames.p1, submittedNames.p2]
      .filter(Boolean).join(' / ') || 'Nachname 1 / Nachname 2';
    const purpose = `Paartal Open 2026 ${namesPart}`;
    return (
      <div className="modal-overlay">
        <div className="modal modal-wide" onClick={e=>e.stopPropagation()} style={{textAlign:'center', padding:'48px 30px'}}>
          <button type="button" className="modal-close" onClick={onClose} aria-label="Schließen"><Icon name="x" size={18}/></button>
          <div style={{width: 72, height: 72, borderRadius:'50%', background:'var(--red-soft)', color:'var(--red)', display:'inline-grid', placeItems:'center', marginBottom: 18}}>
            <Icon name="check" size={32}/>
          </div>
          <h2 className="page-title" style={{fontSize: 28, marginTop: 0}}>Anmeldung eingegangen!</h2>
          <p className="page-sub" style={{margin:'0 auto 18px', maxWidth: 540}}>
            Die Daten sind eingegangen. An die hinterlegte E-Mail-Adresse wird
            in Kürze <b>zusätzlich</b> eine Bestätigungs-E-Mail mit den
            Zahlungsinfos für die Startgebühr von 50 € pro Paarung versendet.
            Erst nach Zahlungseingang wird die Paarung öffentlich freigeschaltet.
          </p>

          <div className="pay-box">
            <div className="pay-box-grid">
              <div className="lbl">Kontoinhaber</div>     <div className="val">TSV Baar-Ebenhausen e. V.</div>
              <div className="lbl">IBAN</div>             <div className="val">DE05 7215 0000 0000 3005 58</div>
              <div className="lbl">BIC</div>              <div className="val">BYLADEM1ING</div>
              <div className="lbl">Bank</div>             <div className="val">Sparkasse Ingolstadt Eichstätt</div>
              <div className="lbl">Betrag</div>           <div className="val">50,00 €</div>
              <div className="lbl">Verwendungszweck</div> <div className="val">{purpose}</div>
            </div>
          </div>

          <div className="pay-note">
            <p className="pay-note-head">Wichtig: nur eine Überweisung pro Paarung</p>
            <ul>
              <li>Bitte die vollen 50 € in einer Überweisung (nicht 2× 25 €).</li>
              <li>Gebt den Verwendungszweck genau wie oben an, damit wir die Zahlung eurer Paarung zuordnen können.</li>
            </ul>
          </div>

          <p className="spam-hint">
            <b>Bitte auch im Spam-/Junk-Ordner nachsehen!</b> Da wir eine neue
            Domain nutzen, kann die erste Mail dort landen. Markiere sie dann
            als <b>„Kein Spam"</b> und füge <b>info@paartal-open.de</b> zu
            deinen Kontakten hinzu. So kommen alle weiteren Infos sicher im
            Posteingang an.
          </p>
          <div style={{display:'flex', gap: 12, justifyContent:'center'}}>
            <button className="btn btn-ghost" onClick={onClose}>Schließen</button>
          </div>
        </div>
      </div>
    );
  }

  const renderPlayerCard = (which, label) => {
    const p = f[which];
    const errKey = (k) => `${which}_${k}`;
    const fld = (k, props={}) => {
      const hasErr = !!errors[errKey(k)];
      return (
        <div className="field" key={k}>
          <label className={'field-label' + (hasErr ? ' is-error' : '')}>{labelWithOptional(props.label)}</label>
          <input
            className={'input' + (hasErr ? ' input-error' : '')}
            type={props.type || 'text'}
            value={p[k]}
            onChange={updPlayer(which, k)}
            placeholder={props.placeholder}
            max={props.max}
            min={props.min}
            aria-invalid={hasErr || undefined}
          />
          {hasErr && <div className="field-error"><Icon name="info" size={13}/>{errors[errKey(k)]}</div>}
        </div>
      );
    };
    return (
      <div className="card card-pad" style={{marginBottom: 18}} key={which}>
        <div className="label" style={{color:'var(--red)', marginBottom: 12}}>{label}</div>
        <div className="field-grid-2">
          {fld('first', { label: 'Vorname *' })}
          {fld('last',  { label: 'Nachname *' })}
        </div>
        <div className="field-grid-2">
          {fld('birthdate', { label: 'Geburtsdatum *', type: 'date', min: '1920-01-01', max: '2015-12-31' })}
          {fld('club', { label: 'Verein *' })}
        </div>
        <div className="field-grid-2">
          <div className="field">
            <div className="field-label" style={{display:'flex', alignItems:'center', gap:6}}>
              <span>Aktuelle Leistungsklasse <span style={optionalStyle}>(freiwillig)</span></span>
              <InfoTip text="Die LK dient lediglich als Orientierung für die Setzliste. Zum Zeitpunkt der Auslosung wird die aktuelle LK von tennis.de verwendet."/>
            </div>
            <select className="select" value={p.lk} onChange={updPlayer(which, 'lk')}>
              {LK_OPTIONS.map(o => <option key={o} value={o}>{o}</option>)}
            </select>
          </div>
          {fld('phone', { label: 'Telefon (freiwillig)' })}
        </div>
        {fld('email', {
          label: which === 'p1' ? 'E-Mail (für Bestätigung) *' : 'E-Mail (freiwillig)',
          type: 'email',
        })}
      </div>
    );
  };

  const errCount = Object.keys(errors).length;

  return (
    <div className="modal-overlay">
      <div className="modal modal-wide" ref={modalRef} onClick={e=>e.stopPropagation()}>
        <button type="button" className="modal-close" onClick={onClose} aria-label="Schließen"><Icon name="x" size={18}/></button>
        <h3 style={{margin:'0 0 16px'}}>Anmeldung zum Turnier</h3>

        {errCount > 0 && (
          <div className="form-error-summary" role="alert">
            <Icon name="info" size={18}/>
            <span>
              Die Anmeldung wurde <b>nicht</b> abgeschickt — {errCount === 1
                ? 'ein Pflichtfeld fehlt oder ist ungültig'
                : `${errCount} Pflichtangaben fehlen oder sind ungültig`}.
              Bitte prüfe die <b>rot markierten Felder</b>.
            </span>
          </div>
        )}

        <div className="note-card" style={{marginBottom: 18}}>
          <Icon name="trophy" size={14}/>
          <span><b>Keine LK-Wertung.</b><br/>Die Matches zählen nicht für die DTB-Leistungsklasse, sondern nur fürs Turnier.</span>
        </div>

        <form onSubmit={submit}>
          {renderPlayerCard('p1', 'Spieler 1')}
          {renderPlayerCard('p2', 'Spieler 2')}

          <div className="card card-pad" style={{background:'var(--cream-2)', marginBottom: 18, border: errors.accept ? '1px solid var(--loss)' : undefined, boxShadow: errors.accept ? '0 0 0 3px rgba(177,58,58,0.16)' : undefined}}>
            <label style={{display:'flex', alignItems:'flex-start', gap: 10, cursor:'pointer'}}>
              <input type="checkbox" checked={f.accept} onChange={updAccept} style={{marginTop: 3}}/>
              <span style={{fontSize: 13, color:'var(--text-2)', lineHeight: 1.5}}>
                Wir akzeptieren die Turnierregeln und verpflichten uns zur Zahlung
                der Startgebühr von <b style={{color:'var(--text-1)', whiteSpace:'nowrap'}}>50 € / Paarung</b>.
                Beide Spieler erhalten nach Anmeldung eine Bestätigungs-E-Mail mit den
                Zahlungsinformationen. Erst nach Zahlungseingang wird die Paarung
                öffentlich freigeschaltet. Die Daten werden ausschließlich zur
                Turnierorganisation verwendet.
              </span>
            </label>
            {errors.accept && <div className="field-error" style={{marginLeft: 24}}><Icon name="info" size={13}/>{errors.accept}</div>}
          </div>

          <div style={{display:'flex', justifyContent:'space-between', alignItems:'center', gap: 16, flexWrap:'wrap'}}>
            <button type="button" className="btn btn-ghost" onClick={onClose}>Abbrechen</button>
            <button type="submit" className="btn btn-primary">
              <Icon name="check" size={14}/> Verbindlich anmelden
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

/* ──────────────── MELDELISTE ──────────────── */
function MeldelistePage({ registrations, seeds, isAdmin, onSubmitReg, onAddReg, onUpdateReg, onDeleteReg, onApproveReg }) {
  const [editing, setEditing] = uS(null); // null | 'new' | reg-object
  const [confirmDel, setConfirmDel] = uS(null);
  const [showAnmeldung, setShowAnmeldung] = uS(false);
  // Body-Scroll sperren, solange das Lösch-Bestätigungs-Modal sichtbar
  // ist. RegEditorModal kümmert sich selber, weil's eine eigene
  // Komponente ist — der ConfirmDel ist hier inline.
  uE(() => {
    if (!confirmDel) return;
    lockBodyScroll();
    return unlockBodyScroll;
  }, [confirmDel]);

  // Hauptfeld = erste 32 freigegebene nach Anmeldedatum, Nachrücker = 33+.
  // Pending = noch nicht freigegebene Anmeldungen (warten auf 50€).
  // mainFieldRegs/reserveRegs filtern bereits auf approved.
  const main = mainFieldRegs(registrations);
  const reserves = reserveRegs(registrations);
  const pending = pendingRegs(registrations);
  const seedsMap = seeds || {};
  const sortedMain = sortedBySeed(main);

  const renderRow = (r, i, opts = {}) => {
    const s = seedsMap[r.id];
    // data-cell wird vom Mobile-CSS-Grid genutzt, um die td's klar in
    // Spieler-1- und Spieler-2-Bloecke zu gruppieren — sonst sind
    // Verein/LK auf Mobile nicht eindeutig zuordnenbar.
    return (
      <tr key={r.id} className={s ? 'seeded' : ''}>
        {/* Checkbox-Spalte: nur im Druck sichtbar — leere Quadrat-
            Glyphe zum Abhaken angemeldeter Teilnehmer am Turniertag. */}
        <td className="print-check" data-cell="check" aria-hidden="true">☐</td>
        <td className="num-col" data-cell="num">{i+1}</td>
        <td className="player" data-cell="p1">{r.p1.first} {r.p1.last}</td>
        <td className="meta"   data-cell="c1">{r.p1.club}</td>
        <td className="meta mono" data-cell="lk1">{r.p1.lk || '—'}</td>
        <td className="meta mono" data-cell="yr1">{birthYear(r.p1) || '—'}</td>
        <td className="player col-divider" data-cell="p2">{r.p2.first} {r.p2.last}</td>
        <td className="meta"   data-cell="c2">{r.p2.club}</td>
        <td className="meta mono" data-cell="lk2">{r.p2.lk || '—'}</td>
        <td className="meta mono" data-cell="yr2">{birthYear(r.p2) || '—'}</td>
        {isAdmin && (
          <td className="meta" data-cell="actions" style={{textAlign:'right'}}>
            <button className="link-btn" onClick={() => setEditing(r)}>Bearbeiten</button>
            <span style={{margin:'0 6px', color:'var(--text-4)'}}>·</span>
            <button className="link-btn link-danger" onClick={() => setConfirmDel(r)}>{opts.deleteLabel || 'Löschen'}</button>
          </td>
        )}
      </tr>
    );
  };

  const tableHead = (
    <thead>
      <tr>
        <th className="print-check" aria-hidden="true">✓</th>
        <th>#</th>
        <th>Spieler 1</th>
        <th>Verein</th>
        <th>LK</th>
        <th>Jg.</th>
        <th className="col-divider">Spieler 2</th>
        <th>Verein</th>
        <th>LK</th>
        <th>Jg.</th>
        {isAdmin && <th style={{width: 110, textAlign:'right'}}>Aktion</th>}
      </tr>
    </thead>
  );

  return (
    <div className="page">
      {/* Header-Zeile: Title-Block links, Print-Splitbutton rechts oben
          (wie im Tableau). Print-Button bleibt rechts oben auch im
          Page-Layout aussen am Header — nicht direkt neben dem CTA. */}
      <div className="meldeliste-header">
        <div className="meldeliste-title">
          <div className="page-eyebrow">Meldeliste</div>
          <h1 className="page-title">Angemeldete Paarungen</h1>
          {isAdmin && (
            <p className="page-sub">
              {main.length} von 32 Paarungen gemeldet
              {reserves.length > 0 && <> · {reserves.length} Nachrücker</>}
            </p>
          )}
        </div>
        {isAdmin && (
          /* Admin-Print-Splitbutton — A4 fuer Klemmbrett am Turniertisch,
             A3 zum Aushaengen. */
          <div className="btn-split no-print" role="group" aria-label="Meldeliste drucken">
            <button className="btn-split-half" onClick={() => window.printAs('a4')}
                    title="A4 Hochformat — Klemmbrett">
              <Icon name="print" size={14}/> A4
            </button>
            <button className="btn-split-half" onClick={() => window.printAs('a3')}
                    title="A3 Hochformat — zum Aushaengen">
              <Icon name="print" size={14}/> A3
            </button>
          </div>
        )}
      </div>
      <div style={{display:'flex', gap: 12, flexWrap:'wrap', alignItems:'center', marginTop: 18}}>
        {/* Admin: cremefarbener "Manuell anmelden"-CTA. Public: roter
            "Anmeldung zum Turnier"-CTA. */}
        {isAdmin ? (
          <button type="button" className="cta-banner cta-banner-light" onClick={() => setEditing('new')}>
            <Icon name="plus" size={18}/>
            <span className="cta-banner-title">Manuell anmelden (Admin)</span>
          </button>
        ) : (
          <button type="button" className="cta-banner" onClick={() => setShowAnmeldung(true)}>
            <Icon name="plus" size={18}/>
            <span className="cta-banner-title">Anmeldung zum Turnier</span>
          </button>
        )}
      </div>

      {isAdmin && pending.length > 0 && (
        <div className="card card-pad" style={{marginTop: 22, padding: 18, borderColor:'var(--gold)', borderWidth: 2, borderStyle:'solid', background:'rgba(190,160,90,0.08)'}}>
          <div style={{display:'flex', alignItems:'center', gap: 10, marginBottom: 14}}>
            <Icon name="lock" size={14}/>
            <h3 style={{margin: 0, fontFamily:'var(--font-display)', fontSize: 20}}>
              Wartet auf Freigabe ({pending.length})
            </h3>
          </div>
          <p className="page-sub" style={{marginTop: 0, marginBottom: 14, fontSize: 13}}>
            Diese Paarungen sind eingegangen, aber noch nicht öffentlich. Nach
            Eingang der 50 € Startgebühr hier freigeben — danach erscheinen
            sie in der Meldeliste und werden ggf. ins Bracket gelost.
          </p>
          <div style={{overflowX:'auto'}}>
            <table className="list-table">
              <thead>
                <tr>
                  <th>Paar 1</th>
                  <th>Verein</th>
                  <th>LK</th>
                  <th>Paar 2</th>
                  <th>Verein</th>
                  <th>LK</th>
                  <th>Eingang</th>
                  <th style={{width: 220, textAlign:'right'}}>Aktion</th>
                </tr>
              </thead>
              <tbody>
                {pending.map(r => (
                  <tr key={r.id}>
                    <td className="player">{r.p1.first} {r.p1.last}</td>
                    <td className="meta">{r.p1.club}</td>
                    <td className="meta mono">{r.p1.lk || '—'}</td>
                    <td className="player">{r.p2.first} {r.p2.last}</td>
                    <td className="meta">{r.p2.club}</td>
                    <td className="meta mono">{r.p2.lk || '—'}</td>
                    <td className="meta mono">{r.ts || '—'}</td>
                    <td className="meta" style={{textAlign:'right'}}>
                      <button className="btn btn-primary" style={{padding:'6px 12px', fontSize: 12}} onClick={() => onApproveReg(r.id)}>
                        <Icon name="check" size={12}/> Freigeben
                      </button>
                      <span style={{margin:'0 6px', color:'var(--text-4)'}}>·</span>
                      <button className="link-btn link-danger" onClick={() => setConfirmDel(r)}>Ablehnen</button>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      )}

      {sortedMain.length > 0 ? (
        <div style={{marginTop: 26, overflowX:'auto'}}>
          <table className="list-table">
            {tableHead}
            <tbody>
              {sortedMain.map((r, i) => renderRow(r, i))}
            </tbody>
          </table>
        </div>
      ) : (
        <div className="card card-pad" style={{padding:'48px 30px', textAlign:'center', marginTop: 24}}>
          <div style={{width: 72, height: 72, borderRadius:'50%', background:'var(--cream-2)', color:'var(--text-3)', display:'inline-grid', placeItems:'center', marginBottom: 18}}>
            <Icon name="racquet" size={28}/>
          </div>
          {isAdmin ? (
            <>
              <h2 className="page-title" style={{fontSize: 28, marginTop: 0}}>Noch keine Anmeldungen</h2>
              <p className="page-sub" style={{margin:'0 auto', maxWidth: 560}}>
                Sobald sich Paarungen anmelden, erscheinen sie hier. Über den Button oben kannst du auch manuell eine Paarung anlegen.
              </p>
            </>
          ) : (
            <>
              <h2 className="page-title" style={{fontSize: 28, marginTop: 0}}>Seid die erste Paarung!</h2>
              <p className="page-sub" style={{margin:'0 auto', maxWidth: 560}}>
                Die Anmeldung für die 1. Paartal Open ist eröffnet. Meldet euch als Doppel an und sichert euch euren Platz im Hauptfeld. Die Meldeliste füllt sich hier in Kürze.
              </p>
            </>
          )}
        </div>
      )}

      {reserves.length > 0 && (
        <div style={{marginTop: 36}}>
          <h2 style={{fontFamily:'var(--font-display)', fontSize:24, margin:'0 0 6px'}}>Nachrücker</h2>
          <p className="page-sub" style={{marginTop:0, marginBottom:14}}>
            Sortiert nach Anmeldedatum. Wenn eine Hauptfeld-Paarung gelöscht wird, rückt der oberste Nachrücker automatisch nach.
          </p>
          <div style={{overflowX:'auto'}}>
            <table className="list-table">
              {tableHead}
              <tbody>
                {reserves.map((r, i) => renderRow(r, i, { deleteLabel: 'Entfernen' }))}
              </tbody>
            </table>
          </div>
        </div>
      )}

      {showAnmeldung && (
        <AnmeldungModal
          onSubmit={onSubmitReg}
          onClose={() => setShowAnmeldung(false)}
        />
      )}

      {editing && (
        <RegEditorModal
          reg={editing === 'new' ? null : editing}
          onClose={() => setEditing(null)}
          onSave={(reg) => {
            if (editing === 'new') onAddReg(reg); else onUpdateReg(reg);
            setEditing(null);
          }}
        />
      )}

      {confirmDel && (() => {
        const isMain = main.some(r => r.id === confirmDel.id);
        const promoter = isMain && reserves.length > 0 ? reserves[0] : null;
        return (
          <div className="modal-overlay">
            <div className="modal" onClick={e=>e.stopPropagation()} style={{maxWidth: 460}}>
              <button type="button" className="modal-close" onClick={() => setConfirmDel(null)} aria-label="Schließen"><Icon name="x" size={18}/></button>
              <h3>Anmeldung löschen?</h3>
              <div className="sub">
                {confirmDel.p1.first} {confirmDel.p1.last} / {confirmDel.p2.first} {confirmDel.p2.last}
                <br/>
                {promoter
                  ? <>Hauptfeld-Paarung — Nachrücker <b>{promoter.p1.last} / {promoter.p2.last}</b> rückt automatisch nach. Bereits gespielte Folge-Runden werden für dieses Match zurückgesetzt.</>
                  : isMain
                    ? <>Diese Paarung wird auch aus dem Tableau entfernt. Es gibt keinen Nachrücker — der Platz bleibt leer.</>
                    : <>Nachrücker — wird einfach aus der Liste entfernt, kein Einfluss auf das Tableau.</>}
              </div>
              <div style={{display:'flex', justifyContent:'space-between', marginTop: 12, gap: 10}}>
                <button className="btn btn-ghost" onClick={() => setConfirmDel(null)}>Abbrechen</button>
                <button className="btn btn-primary" style={{background:'var(--loss)', borderColor:'var(--loss)'}}
                        onClick={() => { onDeleteReg(confirmDel.id); setConfirmDel(null); }}>
                  <Icon name="check" size={14}/> Löschen
                </button>
              </div>
            </div>
          </div>
        );
      })()}
    </div>
  );
}

// Admin-Editor für Anmeldungen (anlegen + bearbeiten).
// Pflichtfelder bewusst minimal — Vorname/Nachname/Verein reichen, der Rest
// ist optional. Im öffentlichen Anmelde-Formular bleibt alles Pflicht.
// Setzliste wird nicht mehr im Editor gewählt — sie ergibt sich automatisch
// aus den LKs der Paarungen (siehe seedsFor in data.js).
function RegEditorModal({ reg, onClose, onSave }) {
  const blank = () => ({ first:'', last:'', birthdate:'', club:'', email:'', phone:'', lk:'ohne LK' });
  const initial = reg
    ? { p1: { ...blank(), ...reg.p1 }, p2: { ...blank(), ...reg.p2 } }
    : { p1: blank(), p2: blank() };
  const [f, setF] = uS(initial);
  const [err, setErr] = uS('');
  uE(() => { lockBodyScroll(); return unlockBodyScroll; }, []);

  const updP = (which, key) => (e) =>
    setF(s => ({ ...s, [which]: { ...s[which], [key]: e.target.value } }));

  const save = () => {
    // Pflicht: Vorname, Nachname, Geburtsdatum, Verein.
    // E-Mail nur fuer Spieler 1 Pflicht (eine Bestaetigung reicht pro
    // Paarung), Telefon und LK sind freiwillig — gleiche Regel wie im
    // oeffentlichen Anmelde-Formular.
    for (const w of ['p1','p2']) {
      const required = w === 'p1'
        ? ['first','last','birthdate','club','email']
        : ['first','last','birthdate','club'];
      for (const k of required) {
        if (!String(f[w][k] ?? '').trim()) {
          setErr('Bitte Pflichtfelder ausfüllen (Vor-/Nachname, Geburtsdatum, Verein, sowie E-Mail bei Spieler 1).');
          return;
        }
      }
      if (f[w].email && !/^\S+@\S+\.\S+$/.test(f[w].email)) {
        setErr('E-Mail-Adresse ungültig.');
        return;
      }
    }
    setErr('');
    const trim = (p) => ({
      first: p.first.trim(), last: p.last.trim(),
      birthdate: p.birthdate || '',
      club: p.club.trim(),
      email: (p.email || '').trim(),
      phone: (p.phone || '').trim(),
      lk: p.lk || 'ohne LK',
    });
    const out = {
      id: reg ? reg.id : 'r' + Date.now(),
      p1: trim(f.p1),
      p2: trim(f.p2),
      ts: reg ? reg.ts : todayDE(),
    };
    onSave(out);
  };

  const playerCard = (which, label) => {
    const p = f[which];
    return (
      <div className="card card-pad" style={{marginBottom: 14, padding: 14}}>
        <div className="label" style={{color:'var(--red)', marginBottom: 10}}>{label}</div>
        <div className="field-grid-2">
          <div className="field">
            <label className="field-label">Vorname *</label>
            <input className="input" value={p.first} onChange={updP(which,'first')}/>
          </div>
          <div className="field">
            <label className="field-label">Nachname *</label>
            <input className="input" value={p.last} onChange={updP(which,'last')}/>
          </div>
        </div>
        <div className="field-grid-2">
          <div className="field">
            <label className="field-label">Verein *</label>
            <input className="input" value={p.club} onChange={updP(which,'club')}/>
          </div>
          <div className="field">
            <label className="field-label">Leistungsklasse</label>
            <select className="select" value={p.lk} onChange={updP(which,'lk')}>
              {LK_OPTIONS.map(o => <option key={o} value={o}>{o}</option>)}
            </select>
          </div>
        </div>
        <div className="field-grid-2">
          <div className="field">
            <label className="field-label">Geburtsdatum *</label>
            <input className="input" type="date" value={p.birthdate} onChange={updP(which,'birthdate')}
                   min="1920-01-01" max="2015-12-31"/>
          </div>
          <div className="field">
            <label className="field-label">Telefon (freiwillig)</label>
            <input className="input" value={p.phone} onChange={updP(which,'phone')}/>
          </div>
        </div>
        <div className="field">
          <label className="field-label">{which === 'p1' ? 'E-Mail *' : 'E-Mail (freiwillig)'}</label>
          <input className="input" type="email" value={p.email} onChange={updP(which,'email')}/>
        </div>
      </div>
    );
  };

  return (
    <div className="modal-overlay">
      <div className="modal modal-wide" onClick={e=>e.stopPropagation()}>
        <button type="button" className="modal-close" onClick={onClose} aria-label="Schließen"><Icon name="x" size={18}/></button>
        <h3>{reg ? 'Anmeldung bearbeiten' : 'Manuell anmelden (Admin)'}</h3>
        <div className="sub">Pflichtfelder mit *. LK und Telefon sind freiwillig. Setzliste wird automatisch aus den LKs berechnet.</div>

        {playerCard('p1', 'Spieler 1')}
        {playerCard('p2', 'Spieler 2')}

        {err && <div style={{color:'var(--loss)', fontSize: 12, marginBottom: 10}}>{err}</div>}

        <div style={{display:'flex', justifyContent:'space-between', gap: 10}}>
          <button className="btn btn-ghost" onClick={onClose}>Abbrechen</button>
          <button className="btn btn-primary" onClick={save}>
            <Icon name="check" size={14}/> Speichern
          </button>
        </div>
      </div>
    </div>
  );
}

/* ──────────────── INFO ──────────────── */
function InfoPage() {
  // Leaflet-Karte mit eigenem rotem Pin. iframe-Embed von OSM zeigt einen
  // grünen Default-Marker, daher rendern wir die Karte selbst.
  React.useEffect(() => {
    if (typeof L === 'undefined') return;
    const lat = 48.67199, lon = 11.47598;
    const map = L.map('map', { scrollWheelZoom: false }).setView([lat, lon], 16);
    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
      attribution: '© OpenStreetMap',
      maxZoom: 19,
    }).addTo(map);
    const icon = L.divIcon({
      className: 'red-pin',
      html: '<svg viewBox="0 0 24 24" width="32" height="44" xmlns="http://www.w3.org/2000/svg"><path fill="#c8202b" stroke="#fff" stroke-width="1.5" d="M12 1.5a8 8 0 0 0-8 8c0 5.5 8 13 8 13s8-7.5 8-13a8 8 0 0 0-8-8Z"/><circle fill="#fff" cx="12" cy="9.5" r="3"/></svg>',
      iconSize: [32, 44],
      iconAnchor: [16, 44],
      popupAnchor: [0, -40],
    });
    L.marker([lat, lon], { icon })
      .addTo(map)
      .bindPopup('<b>Tennisanlage TSV Baar-Ebenhausen</b><br/>Am Sportplatz 1');
    return () => map.remove();
  }, []);

  return (
    <div className="page">
      <div className="page-eyebrow">Info & Anfahrt</div>
      <h1 className="page-title">Tennisanlage Baar-Ebenhausen</h1>
      <p className="page-sub" style={{maxWidth: 'none'}}>
        6 Sandplätze + 1 Hallenplatz (Rebound Ace) · Parkplatz direkt an der Anlage.
      </p>

      <div className="info-grid">
        <div>
          <div className="card card-pad" style={{marginBottom: 18}}>
            <div className="label" style={{color:'var(--red)', marginBottom: 14}}>Veranstaltungsort</div>
            <div style={{display:'flex', gap: 14, alignItems:'flex-start'}}>
              <Icon name="pin" size={20}/>
              <div>
                <div style={{fontFamily:'var(--font-display)', fontSize: 20, fontWeight: 600, marginBottom: 4}}>Tennisanlage TSV Baar-Ebenhausen</div>
                <div style={{color:'var(--text-2)', fontSize: 14, lineHeight: 1.55}}>
                  Am Sportplatz 1<br/>85107 Baar-Ebenhausen
                </div>
              </div>
            </div>
          </div>

          <div className="card card-pad">
            <div className="label" style={{color:'var(--red)', marginBottom: 14}}>Kontakt</div>
            <div style={{display:'flex', flexDirection:'column', gap: 12}}>
              <a href="mailto:info@paartal-open.de" style={{display:'flex', gap: 12, alignItems:'center', textDecoration:'none', color:'var(--text-1)'}}>
                <Icon name="mail" size={18}/>
                <span style={{fontSize: 14}}>info@paartal-open.de</span>
              </a>
            </div>
          </div>
        </div>

        <div className="map-wrap">
          <div id="map" style={{width:'100%', height:'100%', minHeight: 320}}></div>
        </div>
      </div>

      <div style={{textAlign:'right', marginTop: 8}}>
        <a href="https://www.openstreetmap.org/?mlat=48.67199&mlon=11.47598#map=17/48.67199/11.47598"
           target="_blank" rel="noopener noreferrer"
           style={{fontSize: 11, color:'var(--text-3)', textDecoration:'none'}}>
          Größere Karte anzeigen ↗
        </a>
      </div>

      <div className="tennis-photo-strip" aria-hidden="true">
        <div className="tennis-photo tp-1"></div>
        <div className="tennis-photo tp-2"></div>
        <div className="tennis-photo tp-3"></div>
        <div className="tennis-photo tp-4"></div>
        <div className="tennis-photo tp-5"></div>
        <div className="tennis-photo tp-6"></div>
        <div className="tennis-photo tp-7"></div>
        <div className="tennis-photo tp-8"></div>
        <div className="tennis-photo tp-9"></div>
        <div className="tennis-photo tp-10"></div>
        <div className="tennis-photo tp-11"></div>
        <div className="tennis-photo tp-12"></div>
      </div>
    </div>
  );
}

/* ──────────────── IMPRESSUM & DATENSCHUTZ (kombiniert) ──────────────── */
// Eine gemeinsame Rechtliches-Seite — Impressum oben, Datenschutz
// darunter. Daten aus dem TSV-Hauptauftritt (tsv-baar-ebenhausen.de)
// uebernommen: 1. Vorsitzender, Vorstands-Mail, Registernummer.
function LegalPage() {
  return (
    <div className="page legal-page">
      <div className="page-eyebrow">Rechtliches</div>
      <h1 className="page-title">Impressum & Datenschutz</h1>
      <p className="page-sub">
        Angaben gemäß § 5 TMG sowie Informationen zur Datenverarbeitung
        nach DSGVO.
      </p>

      <h2 className="legal-section-title">Impressum</h2>

      <h3>Veranstalter</h3>
      <p>
        TSV Baar-Ebenhausen e.V.<br/>
        Tennisabteilung<br/>
        Am Sportplatz 1<br/>
        85107 Baar-Ebenhausen
      </p>

      <h3>Vertretungsberechtigt</h3>
      <p>
        1. Vorsitzender: Karlheinz Binder<br/>
        E-Mail: <a href="mailto:vorstand@tsv-baar-ebenhausen.de">vorstand@tsv-baar-ebenhausen.de</a>
      </p>

      <h3>Kontakt Turnierleitung</h3>
      <p>
        E-Mail: <a href="mailto:info@paartal-open.de">info@paartal-open.de</a>
      </p>

      <h3>Registereintrag</h3>
      <p>
        Eintragung im Vereinsregister<br/>
        Registergericht: Amtsgericht Ingolstadt<br/>
        Registernummer: VR 20594
      </p>

      <h3>Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV</h3>
      <p>
        Karlheinz Binder<br/>
        c/o TSV Baar-Ebenhausen e.V.<br/>
        Am Sportplatz 1, 85107 Baar-Ebenhausen
      </p>

      <h3>Haftungsausschluss</h3>
      <p>
        Die Inhalte dieser Seite werden mit größtmöglicher Sorgfalt erstellt.
        Für die Richtigkeit, Vollständigkeit und Aktualität der Inhalte können
        wir jedoch keine Gewähr übernehmen. Für Inhalte externer Links sind
        ausschließlich deren Betreiber verantwortlich.
      </p>

      <h3>Urheberrecht</h3>
      <p>
        Die durch den Veranstalter erstellten Inhalte und Werke unterliegen
        dem deutschen Urheberrecht. Vervielfältigung, Bearbeitung, Verbreitung
        und jede Art der Verwertung außerhalb der Grenzen des Urheberrechts
        bedürfen der schriftlichen Zustimmung.
      </p>

      <h2 className="legal-section-title">Datenschutzerklärung</h2>

      <h3>Verantwortlicher</h3>
      <p>
        TSV Baar-Ebenhausen e.V., Tennisabteilung<br/>
        Am Sportplatz 1, 85107 Baar-Ebenhausen<br/>
        E-Mail: <a href="mailto:info@paartal-open.de">info@paartal-open.de</a>
      </p>

      <h3>Welche Daten erheben wir?</h3>
      <p>Bei der Anmeldung zum 1. Paartal Open erheben wir pro Spieler:</p>
      <ul>
        <li>Vor- und Nachname</li>
        <li>Geburtsdatum</li>
        <li>Vereinszugehörigkeit</li>
        <li>Leistungsklasse (LK, optional)</li>
        <li>E-Mail-Adresse</li>
        <li>Telefonnummer (optional)</li>
      </ul>

      <h3>Wofür verwenden wir die Daten?</h3>
      <p>
        Ausschließlich zur Organisation und Durchführung des Turniers:
        Auslosung, Zeitplan-Kommunikation, Ergebnisverwaltung und
        Anmeldebestätigung. Eine Weitergabe an Dritte findet nicht statt.
      </p>

      <h3>Veröffentlichte Daten</h3>
      <p>
        Auf der Meldeliste und im Tableau werden Vor- und Nachname,
        Vereinszugehörigkeit, Geburtsjahr und Leistungsklasse beider
        Spieler einer Paarung öffentlich angezeigt. E-Mail-Adresse,
        Telefonnummer und vollständiges Geburtsdatum werden <b>nicht</b>{' '}
        öffentlich angezeigt — sie sind ausschließlich für die
        Turnierleitung einsehbar.
      </p>

      <h3>Speicherung &amp; Hosting</h3>
      <p>
        Anmeldungen und Tableau-Stand werden in einer
        Postgres-Datenbank des europäischen Cloud-Anbieters{' '}
        <b>Supabase</b> (Region <span className="mono">eu-central-1</span>,
        Frankfurt am Main) gespeichert. Die Website selbst wird über{' '}
        <b>Vercel</b> und parallel <b>Railway</b> ausgeliefert. Beide
        Anbieter erhalten technisch bedingt deine IP-Adresse als
        Bestandteil jeder HTTP-Anfrage; die Verarbeitung erfolgt
        ausschließlich zum Zweck der Auslieferung.
      </p>

      <h3>Cookies &amp; lokale Speicherung</h3>
      <p>
        Diese Website setzt keine eigenen Tracking-Cookies. Nach einem
        Admin-Login der Turnierleitung speichert Supabase einen
        Auth-Token im <span className="mono">localStorage</span> des
        Browsers, der beim Logout wieder entfernt wird. Für normale
        Besucher der Seite passiert das nicht.
      </p>

      <h3>Externe Dienste</h3>
      <ul>
        <li><b>Google Fonts</b> — Schriftarten werden zur Laufzeit von
          fonts.googleapis.com geladen. Dabei wird deine IP-Adresse an
          Google übertragen.</li>
        <li><b>OpenStreetMap</b> — Die Karte auf der Info & Anfahrt-Seite
          wird via Leaflet von openstreetmap.org eingebunden.</li>
        <li><b>unpkg.com</b> — JavaScript-Libraries (React, Babel,
          Supabase-Client, Leaflet) werden über das CDN ausgeliefert.</li>
        <li><b>Instagram</b> — Der Verweis auf das TSV-Tennis-Profil ist
          ein normaler Link; erst beim Klick verlässt du diese Seite.</li>
      </ul>

      <h3>Deine Rechte</h3>
      <p>
        Du hast jederzeit das Recht auf Auskunft, Berichtigung, Löschung
        und Einschränkung der Verarbeitung deiner Daten sowie auf
        Datenübertragbarkeit. Wende dich dafür an die oben genannte
        E-Mail-Adresse.
      </p>

      <h3>Beschwerderecht</h3>
      <p>
        Du kannst dich bei der zuständigen Datenschutz-Aufsichtsbehörde
        beschweren — in Bayern beim Bayerischen Landesamt für
        Datenschutzaufsicht (BayLDA), Promenade 18, 91522 Ansbach,
        E-Mail:{' '}
        <a href="mailto:poststelle@lda.bayern.de">poststelle@lda.bayern.de</a>.
      </p>
    </div>
  );
}

Object.assign(window, { HomePage, AnmeldungModal, MeldelistePage, InfoPage, LegalPage });
