403Webshell
Server IP : 202.61.199.114  /  Your IP : 216.73.217.139
Web Server : nginx/1.22.1
System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64
User : web20 ( 1018)
PHP Version : 8.4.23
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : ON
Directory :  /var/www/seaside.pacim.de/web/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/seaside.pacim.de/web/checkin.php
<?php
/**
 * checkin.php – Mobile Check-in & Zahlung erfassen zu einer Buchung (Phase 2, funktional).
 *
 * Aufruf: /checkin.php?b=<booking_id>&h=<hash>  (Hash = pacim_sign('checkin:'.id), ohne Login).
 * AJAX-Aktionen (POST, gleicher Hash):
 *   action=pay      type(1|2|3), coupon(0|1|2)  -> Zahlung erfassen/ändern (Betrag serverseitig)
 *   action=checkin                              -> Check-in (nur wenn bezahlt)
 * Rabatte: coupon 1 = 5 € Bestandskundenrabatt, 2 = 10 % Frühbucherrabatt (auf Parkplatz, prod_id<=2).
 */
require_once( __DIR__ . '/includes/config.inc.php' );
require_once( __DIR__ . '/includes/day-arrivals.php' );
require_once( __DIR__ . '/includes/mobileaccess.php' );
require_once( __DIR__ . '/includes/booking-notes.php' );
require_once( __DIR__ . '/includes/plate-info.php' );
@ini_set( 'display_errors', '0' );
error_reporting( 0 );
while ( ob_get_level() > 0 ) ob_end_clean();

$bid    = isset( $_GET['b'] ) ? (int) $_GET['b'] : (int) ( $_POST['b'] ?? 0 );
$h      = trim( (string) ( $_GET['h'] ?? ( $_POST['h'] ?? '' ) ) );
$tok    = trim( (string) ( $_GET['t'] ?? ( $_POST['t'] ?? '' ) ) );   // Mobile-Zugang-Token (alternativ zu Hash)
$embed  = ! empty( $_GET['embed'] );   // im Overlay/iframe der Tagesliste eingebettet
$action = (string) ( $_POST['action'] ?? '' );

function ci_json( $a ) { while ( ob_get_level() > 0 ) ob_end_clean(); header( 'Content-Type: application/json; charset=utf-8' ); echo json_encode( $a ); exit(); }
function ci_fail( $m ) { ci_json( array( 'ok' => false, 'error' => $m ) ); }

$db = new MysqliDb( MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DB );

// Zugriff: entweder Mobile-Zugang-Token (mit Rechten/Geltungsbereich) ODER Legacy-Hash (volle Rechte).
$access = null; $canAct = true;
if ( $tok !== '' ) {
    $access = pacim_ma_load( $db, $tok );
    $authOk = ( $access && $bid > 0 && pacim_ma_in_scope( $db, $access, $bid ) );
    $canAct = ( $access && (int) $access['ma_perm_action'] === 1 );
    // PIN-geschützter Zugang: Freischalt-Cookie der Tagesliste ODER gültige Terminal-Sitzung erforderlich.
    if ( $access && (string) $access['ma_pin'] !== '' ) {
        $ck = 'mau_' . $tok;
        $okPin = ( isset( $_COOKIE[ $ck ] ) && hash_equals( pacim_ma_unlock_token( $tok ), (string) $_COOKIE[ $ck ] ) )
              || ( function_exists( 'pacim_terminal_grants' ) && pacim_terminal_grants( $db, $tok ) );
        if ( ! $okPin ) $authOk = false;
    }
} else {
    $authOk = ( $bid > 0 && function_exists( 'pacim_sign_verify' ) && pacim_sign_verify( 'checkin:' . $bid, $h ) );
}
$readOnly = ( $tok !== '' && ! $canAct );
// Modus des Zugangs: Abreisen (Check-out) statt Anreisen (Check-in). Legacy-Hash = immer Anreisen.
$isDep = ( $access && function_exists( 'pacim_ma_mode' ) && pacim_ma_mode( $access ) === 'abreise' );
if ( ! $authOk ) {
    if ( $action !== '' ) ci_fail( 'Ungültiger oder abgelaufener Link.' );
    ci_error_page();
}

/* ----------------------------------------- Daten laden */
function ci_load( $db, $bid ) {
    $r = $db->rawQueryOne(
        "SELECT b.booking_id, b.booking_no, b.booking_event, b.booking_type, b.booking_checkin, b.booking_checkout,
                b.booking_begin, b.booking_end, b.booking_valet, license_plate(b.booking_serial) AS plate,
                b.booking_hint, b.booking_hint_reply,
                DATE_FORMAT(b.booking_hint_reply_at, '%d.%m.%Y · %H:%i') AS hint_reply_at, b.booking_hint_reply_by,
                DATE_FORMAT(b.booking_cruise_checkin, '%H:%i') AS boarding,
                DATE_FORMAT(b.booking_checkin, '%H:%i') AS checkin_time,
                DATE_FORMAT(b.booking_checkout, '%H:%i') AS checkout_time,
                DATEDIFF(b.booking_end, b.booking_begin) AS trip_days,
                d.invoice_id, d.invoice_no, d.invoice_payment, d.invoice_status, d.invoice_coupon,
                c.customer_firstname, c.customer_lastname, c.customer_name, c.customer_source, c.customer_gender,
                c.customer_street, c.customer_zipcode, c.customer_city, c.customer_country,
                c.customer_phone, c.customer_email
           FROM pacim_booking b
           JOIN pacim_invoice d ON d.invoice_id = b.booking_invoice
           LEFT JOIN pacim_customer c ON c.customer_id = b.booking_customer
          WHERE b.booking_id = ?", array( $bid ) );
    return $r ?: null;
}
function ci_positions( $db, $iid ) {
    return (array) $db->rawQuery( "SELECT product_id, invoice_pos_price, invoice_pos_quantity, invoice_pos_discount_type FROM pacim_invoice_pos WHERE invoice_id = ?", array( $iid ) );
}
/** Brutto-Betrag für einen Coupon (0/1/2) – Logik wie invoice.class::setPayment (Parkplatz = prod_id<=2). */
function ci_amount( $pos, $coupon ) {
    $sum = 0.0;
    foreach ( $pos as $p ) {
        $net = (float) $p['invoice_pos_price']; $qty = (float) $p['invoice_pos_quantity']; $pid = (int) $p['product_id'];
        if ( $pid <= 2 && $coupon === 2 )      $line = $net * 0.9 * $qty * 1.19;
        elseif ( $pid <= 2 && $coupon === 1 )  $line = ( $net * $qty * 1.19 ) - 5;
        else                                   $line = $net * $qty * 1.19;
        $sum += $line;
    }
    return round( max( 0, $sum ), 2 );
}
function ci_paid_value( $db, $iid ) {
    $r = $db->rawQueryOne( "SELECT COALESCE(SUM(payment_value),0) v FROM pacim_payments WHERE payment_invoice = ?", array( $iid ) );
    return $r ? (float) $r['v'] : 0.0;
}
function ci_eur( $v ) { return number_format( (float) $v, 2, ',', '.' ) . ' €'; }
function ci_payname( $t ) { return array( 1 => 'Überweisung', 2 => 'EC-Karte', 3 => 'Bar' )[ (int) $t ] ?? '—'; }
/** Rechnungs-Posten (Titel + Brutto) für die POS-Ansicht. removable = Extra (kein Parkplatz 1/2). */
function ci_pos_items( $db, $iid ) {
    $out = array();
    foreach ( (array) $db->rawQuery(
        "SELECT ip.invoice_pos_id AS id, ip.product_id, ip.invoice_pos_price AS net, ip.invoice_pos_quantity AS qty,
                COALESCE(NULLIF(TRIM(ip.invoice_pos_title),''), p.product_title, CONCAT('Position ', ip.product_id)) AS title
           FROM pacim_invoice_pos ip LEFT JOIN pacim_products p ON p.product_id = ip.product_id
          WHERE ip.invoice_id = ? ORDER BY ip.invoice_pos_id", array( $iid ) ) as $pp ) {
        $out[] = array(
            'id'        => (int) $pp['id'],
            'title'     => (string) $pp['title'],
            'qty'       => (float) $pp['qty'],
            'gross'     => round( (float) $pp['net'] * (float) $pp['qty'] * 1.19, 2 ),
            'removable' => ( (int) $pp['product_id'] > 2 ),   // Parkplatz (1/2) bleibt
        );
    }
    return $out;
}
/** Aktive Zusatzprodukte (sichtbar, außer Parkplatz 1/2) – zum Hinzufügen. Preis brutto (Jahrespreis falls vorhanden). */
function ci_add_products( $db, $year ) {
    $out = array();
    foreach ( (array) $db->rawQuery(
        "SELECT p.product_id AS id, p.product_title AS title,
                COALESCE((SELECT pp.price FROM pacim_product_prices pp WHERE pp.product_id = p.product_id AND pp.year = ? LIMIT 1), p.product_price) AS net
           FROM pacim_products p WHERE p.product_visible = 1 AND p.product_id NOT IN (1,2) ORDER BY p.product_title",
        array( (int) $year ) ) as $p ) {
        $out[] = array( 'id' => (int) $p['id'], 'title' => (string) $p['title'], 'gross' => round( (float) $p['net'] * 1.19, 2 ) );
    }
    return $out;
}

$r = ci_load( $db, $bid );
if ( ! $r ) { if ( $action !== '' ) ci_fail( 'Buchung nicht gefunden.' ); ci_error_page( 404, 'Buchung nicht gefunden.' ); }
$iid    = (int) $r['invoice_id'];
$storno = ( (int) $r['invoice_status'] === 3 );
$isAida = ( strcasecmp( trim( (string) $r['customer_source'] ), 'AIDA' ) === 0 );  // AIDA = über myAIDA bezahlt
$pos    = ci_positions( $db, $iid );
// Einzelposten (POS-Ansicht) + hinzufügbare Produkte (aktiv, außer Parkplatz 1/2).
$bkYear      = (int) date( 'Y', strtotime( (string) $r['booking_begin'] ) ) ?: (int) date( 'Y' );
$posItems    = ci_pos_items( $db, $iid );
$addProducts = ( $canAct && ! $isAida && ! $storno ) ? ci_add_products( $db, $bkYear ) : array();

/* ---- Notiz-Kontext (Autor/Zugang/Schreibrecht). Lesen immer erlaubt; Schreiben nur mit Recht. */
$bnAccessId = ( $tok !== '' && $access ) ? (int) $access['ma_id'] : null;
$bnUser     = ( $tok !== '' && $access )
              ? ( trim( (string) $access['ma_label'] ) !== '' ? $access['ma_label'] : 'Mobil-Zugang' )
              : ( trim( (string) ( $_SESSION['user_name'] ?? '' ) ) !== '' ? $_SESSION['user_name'] : 'Check-in' );
$bnCanWrite = ( $tok !== '' ) ? ( $access ? pacim_ma_can_notes( $access ) : false ) : true;
$bnCanAll   = ( $tok === '' );   // Legacy-Hash/Admin = volle Rechte auf alle Notizen
function ci_note_can_edit( $note, $canWrite, $canAll, $accessId ) {
    if ( ! $canWrite ) return false;
    if ( $canAll ) return true;
    return ( $accessId !== null && (int) $note['note_access'] === (int) $accessId );
}
// Kurzer, einzeiliger Auszug einer Notiz für den Aktivitäts-/Sitzungsverlauf (optional Hervorheben-Marker).
function ci_note_snip( $text, $highlight = 0 ) {
    $t = trim( preg_replace( '/\s+/', ' ', (string) $text ) );
    if ( mb_strlen( $t ) > 120 ) $t = mb_substr( $t, 0, 120 ) . '…';
    return ( $highlight ? '★ ' : '' ) . $t;
}

/* ============================================ AJAX: Interne Notizen (Lesen vor Read-Only-Guard) */
if ( $action === 'notes' ) {
    $out = array();
    foreach ( pacim_bn_list( $db, $bid, true ) as $n ) {   // nur in der Web-App sichtbare Notizen
        $canE = ci_note_can_edit( $n, $bnCanWrite, $bnCanAll, $bnAccessId );
        $out[] = array(
            'id' => (int) $n['note_id'], 'text' => (string) $n['note_text'], 'user' => (string) $n['note_user'],
            'highlight' => ! empty( $n['note_highlight'] ),
            'created' => pacim_bn_fmt_dt( $n['note_created'] ),
            'updated' => $n['note_updated'] ? pacim_bn_fmt_dt( $n['note_updated'] ) : '',
            'updatedBy' => (string) $n['note_updated_by'], 'canEdit' => $canE, 'canDel' => $canE,
        );
    }
    ci_json( array( 'ok' => true, 'notes' => $out, 'canCreate' => $bnCanWrite, 'marquee' => pacim_bn_highlighted( $db, $bid ) ) );
}
if ( $action === 'note_add' ) {
    if ( ! $bnCanWrite ) ci_fail( 'Keine Berechtigung für interne Notizen.' );
    if ( $storno ) ci_fail( 'Buchung ist storniert.' );
    $hl = ! empty( $_POST['highlight'] ) ? 1 : 0;
    $noteTxt = (string) ( $_POST['text'] ?? '' );
    $id = pacim_bn_add( $db, $bid, $noteTxt, $bnUser, $bnAccessId, 1, $hl );   // Web-App-Notiz = web sichtbar
    if ( ! $id ) ci_fail( 'Notiz konnte nicht gespeichert werden (leer oder Limit erreicht).' );
    if ( function_exists( 'pacim_aa_log_tok' ) ) pacim_aa_log_tok( $db, $tok, $bid, (int) $r['invoice_id'], 'note_add', ci_note_snip( $noteTxt, $hl ), date( 'Y-m-d', strtotime( (string) $r['booking_begin'] ) ) );
    ci_json( array( 'ok' => true, 'id' => $id ) );
}
if ( $action === 'note_edit' || $action === 'note_del' ) {
    if ( ! $bnCanWrite ) ci_fail( 'Keine Berechtigung für interne Notizen.' );
    $nid = (int) ( $_POST['id'] ?? 0 );
    $n = pacim_bn_get( $db, $nid );
    if ( ! $n || (int) $n['note_booking'] !== $bid || $n['note_deleted_at'] ) ci_fail( 'Notiz nicht gefunden.' );
    if ( ! ci_note_can_edit( $n, $bnCanWrite, $bnCanAll, $bnAccessId ) ) ci_fail( 'Diese Notiz darf nicht geändert werden.' );
    if ( $action === 'note_del' ) {
        pacim_bn_delete( $db, $nid, $bnUser );
        if ( function_exists( 'pacim_aa_log_tok' ) ) pacim_aa_log_tok( $db, $tok, $bid, (int) $r['invoice_id'], 'note_del', ci_note_snip( (string) $n['note_text'] ), date( 'Y-m-d', strtotime( (string) $r['booking_begin'] ) ) );
        ci_json( array( 'ok' => true ) );
    }
    $hl = ! empty( $_POST['highlight'] ) ? 1 : 0;   // Web-Sichtbarkeit bleibt unverändert (null)
    $noteTxt = (string) ( $_POST['text'] ?? '' );
    if ( ! pacim_bn_update( $db, $nid, $noteTxt, $bnUser, null, $hl ) ) ci_fail( 'Speichern fehlgeschlagen.' );
    if ( function_exists( 'pacim_aa_log_tok' ) ) pacim_aa_log_tok( $db, $tok, $bid, (int) $r['invoice_id'], 'note_edit', ci_note_snip( $noteTxt, $hl ), date( 'Y-m-d', strtotime( (string) $r['booking_begin'] ) ) );
    ci_json( array( 'ok' => true ) );
}

/* ============================================ AJAX: Kennzeichen-Auskunft (lokal, vor Read-Only-Guard) */
if ( $action === 'plateinfo' ) {
    $pl = (string) ( $_POST['plate'] ?? $r['plate'] );
    ci_json( array( 'ok' => true ) + pacim_plate_info( $pl ) );
}

// Nur-Ansicht-Zugang: keinerlei Schreibaktionen (Zahlung/Check-in/Kennzeichen).
if ( $action !== '' && $readOnly ) ci_fail( 'Dieser Zugang erlaubt nur die Ansicht.' );

/* ============================================ AJAX: Zahlung erfassen / ändern */
if ( $action === 'pay' ) {
    if ( $storno ) ci_fail( 'Buchung ist storniert.' );
    if ( $isAida ) ci_fail( 'AIDA-Buchung – bereits über myAIDA bezahlt.' );
    $type   = (int) ( $_POST['type'] ?? 0 );
    $coupon = (int) ( $_POST['coupon'] ?? 0 );
    if ( ! in_array( $type, array( 1, 2, 3 ), true ) ) ci_fail( 'Bitte Zahlungsart wählen.' );
    if ( ! in_array( $coupon, array( 0, 1, 2 ), true ) ) $coupon = 0;
    $amount = ci_amount( $pos, $coupon );

    // Atomar schreiben: bei einem Fehler wird NICHTS gespeichert (keine Rechnungsnummer, keine Zahlung).
    $db->startTransaction();
    try {
        $db->where( 'payment_invoice', $iid ); $db->delete( 'pacim_payments' );   // bei Änderung alten Eintrag ersetzen
        $okIns = $db->insert( 'pacim_payments', array(
            'payment_invoice' => $iid, 'payment_type' => $type, 'payment_value' => $amount,
            'payment_notice'  => '', 'payment_user' => trim( (string) ( $_SESSION['user_name'] ?? 'Check-in' ) ),
            'payment_date'    => date( 'Y-m-d' ),
        ) );
        if ( ! $okIns ) throw new Exception( 'payment-insert' );
        $upd = array(
            'invoice_coupon'  => $coupon ?: null,
            'invoice_payment' => $type,
            'invoice_paydate' => date( 'Y-m-d H:i:s' ),
        );
        // Rechnungs-NUMMER wird NICHT mehr automatisch vergeben – erst über „Rechnung(en) erstellen"
        // (Tab Rechnungen, bzw. einzeln im 3-Punkte-Menü). Nur das Rechnungsdatum = Zahlungstag setzen,
        // damit der Vorgang in der Tagesabrechnung des richtigen Tages erscheint.
        if ( trim( (string) $r['invoice_no'] ) === '' ) {
            $upd['invoice_date'] = date( 'Y-m-d' );
        }
        $db->where( 'invoice_id', $iid );
        if ( ! $db->update( 'pacim_invoice', $upd ) ) throw new Exception( 'invoice-update' );
        $db->commit();
    } catch ( Exception $ex ) {
        $db->rollback();
        ci_fail( 'Zahlung konnte nicht gespeichert werden. Es wurde keine Rechnung erstellt.' );
    }
    try { if ( class_exists( 'mailrules' ) ) mailrules::evaluateEvent( 'payment_received', array( 'invoice_id' => $iid ), 'payment_received' ); } catch ( Exception $e ) {}
    if ( function_exists( 'pacim_aa_log_tok' ) ) pacim_aa_log_tok( $db, $tok, $bid, $iid, 'payment', ci_eur( $amount ) . ' · ' . ci_payname( $type ) . ' · ' . trim( (string) $r['booking_no'] ), date( 'Y-m-d', strtotime( (string) $r['booking_begin'] ) ) );

    ci_json( array( 'ok' => true, 'paid' => true, 'amount' => $amount, 'amount_fmt' => ci_eur( $amount ),
                    'type' => $type, 'type_name' => ci_payname( $type ), 'coupon' => $coupon ) );
}

/* ============================================ AJAX: Rechnungs-Posten hinzufügen / entfernen */
if ( $action === 'addpos' || $action === 'delpos' ) {
    if ( $storno ) ci_fail( 'Buchung ist storniert.' );
    if ( $isAida ) ci_fail( 'AIDA-Buchung – Posten nicht änderbar.' );
    if ( ! empty( $r['invoice_payment'] ) ) ci_fail( 'Bereits bezahlt – Zahlung erst zurücknehmen.' );
    if ( $action === 'addpos' ) {
        $pid  = (int) ( $_POST['product_id'] ?? 0 );
        if ( $pid <= 2 ) ci_fail( 'Dieses Produkt kann nicht hinzugefügt werden.' );
        $prod = $db->rawQueryOne(
            "SELECT p.product_id, p.product_title, p.product_visible,
                    COALESCE((SELECT pp.price FROM pacim_product_prices pp WHERE pp.product_id = p.product_id AND pp.year = ? LIMIT 1), p.product_price) AS net
               FROM pacim_products p WHERE p.product_id = ? LIMIT 1",
            array( $bkYear, $pid ) );
        if ( ! $prod || (int) $prod['product_visible'] !== 1 ) ci_fail( 'Produkt nicht verfügbar.' );
        $db->insert( 'pacim_invoice_pos', array(
            'invoice_id' => $iid, 'product_id' => $pid, 'booking_id' => $bid,
            'invoice_pos_price' => (float) $prod['net'], 'invoice_pos_quantity' => 1,
            'invoice_pos_title' => (string) $prod['product_title'],
        ) );
        if ( function_exists( 'pacim_aa_log_tok' ) ) pacim_aa_log_tok( $db, $tok, $bid, $iid, 'addpos', (string) $prod['product_title'], date( 'Y-m-d', strtotime( (string) $r['booking_begin'] ) ) );
    } else { // delpos – nur Extra-Positionen (Parkplatz 1/2 bleibt)
        $posId = (int) ( $_POST['pos_id'] ?? 0 );
        $row   = $db->rawQueryOne( "SELECT product_id, invoice_pos_title FROM pacim_invoice_pos WHERE invoice_pos_id = ? AND invoice_id = ?", array( $posId, $iid ) );
        if ( ! $row ) ci_fail( 'Position nicht gefunden.' );
        if ( (int) $row['product_id'] <= 2 ) ci_fail( 'Parkplatz kann nicht entfernt werden.' );
        $db->where( 'invoice_pos_id', $posId ); $db->where( 'invoice_id', $iid ); $db->delete( 'pacim_invoice_pos' );
        if ( function_exists( 'pacim_aa_log_tok' ) ) pacim_aa_log_tok( $db, $tok, $bid, $iid, 'delpos', (string) $row['invoice_pos_title'], date( 'Y-m-d', strtotime( (string) $r['booking_begin'] ) ) );
    }
    // Neue Positionen + Beträge zurückgeben; invoice_price = Brutto-Summe (ohne Rabatt) konsistent halten.
    $pos2 = ci_positions( $db, $iid );
    $am   = array( 0 => ci_amount( $pos2, 0 ), 1 => ci_amount( $pos2, 1 ), 2 => ci_amount( $pos2, 2 ) );
    $db->where( 'invoice_id', $iid ); $db->update( 'pacim_invoice', array( 'invoice_price' => $am[0] ) );
    ci_json( array( 'ok' => true, 'positions' => ci_pos_items( $db, $iid ), 'amounts' => $am ) );
}

/* ============================================ AJAX: Kennzeichen ändern */
if ( $action === 'plate' ) {
    if ( $storno ) ci_fail( 'Buchung ist storniert.' );
    $val = strtoupper( trim( (string) ( $_POST['plate'] ?? '' ) ) );
    if ( $val === '' ) ci_fail( 'Bitte ein Kennzeichen eingeben.' );
    if ( mb_strlen( $val ) > 20 || ! preg_match( '/^[A-ZÄÖÜ0-9 .\-]+$/u', $val ) ) ci_fail( 'Ungültiges Kennzeichen.' );
    $db->where( 'booking_id', $bid ); $db->update( 'pacim_booking', array( 'booking_serial' => $val ) );
    $f = $db->rawQueryOne( "SELECT license_plate(booking_serial) AS p FROM pacim_booking WHERE booking_id = ?", array( $bid ) );
    $plNew = ( $f && trim( (string) $f['p'] ) !== '' ) ? $f['p'] : $val;
    if ( function_exists( 'pacim_aa_log_tok' ) ) pacim_aa_log_tok( $db, $tok, $bid, (int) $r['invoice_id'], 'plate', 'Kennzeichen: ' . $plNew, date( 'Y-m-d', strtotime( (string) $r['booking_begin'] ) ) );
    ci_json( array( 'ok' => true, 'plate' => $plNew ) );
}

/* ============================================ AJAX: Check-in */
if ( $action === 'checkin' ) {
    if ( $storno ) ci_fail( 'Buchung ist storniert.' );
    if ( ! $isAida && (int) $r['invoice_payment'] <= 0 ) ci_fail( 'Bitte zuerst die Zahlung erfassen.' );
    if ( ! empty( $r['booking_checkin'] ) ) ci_json( array( 'ok' => true, 'already' => true ) );
    $db->where( 'booking_id', $bid ); $db->update( 'pacim_booking', array( 'booking_checkin' => date( 'Y-m-d H:i:s' ) ) );
    // Check-in abgeschlossen => einmalige Vorgangsnummer vergeben (storniert wird oben bereits geblockt).
    if ( class_exists( 'vorgang' ) ) vorgang::assign( $db, $bid );
    if ( function_exists( 'pacim_aa_log_tok' ) ) pacim_aa_log_tok( $db, $tok, $bid, (int) $r['invoice_id'], 'checkin', '', date( 'Y-m-d', strtotime( (string) $r['booking_begin'] ) ) );
    ci_json( array( 'ok' => true, 'checked' => true, 'time' => date( 'H:i' ) ) );
}

/* ============================================ AJAX: Check-in zurücknehmen (Auschecken) */
if ( $action === 'uncheckin' ) {
    if ( $storno ) ci_fail( 'Buchung ist storniert.' );
    $db->where( 'booking_id', $bid ); $db->update( 'pacim_booking', array( 'booking_checkin' => null ) );
    if ( function_exists( 'pacim_aa_log_tok' ) ) pacim_aa_log_tok( $db, $tok, $bid, (int) $r['invoice_id'], 'uncheckin', '', date( 'Y-m-d', strtotime( (string) $r['booking_begin'] ) ) );
    ci_json( array( 'ok' => true, 'unchecked' => true ) );
}

/* ============================================ AJAX: Check-out (Abreise erfassen) */
if ( $action === 'checkout' ) {
    if ( $storno ) ci_fail( 'Buchung ist storniert.' );
    if ( ! empty( $r['booking_checkout'] ) ) ci_json( array( 'ok' => true, 'already' => true ) );
    $db->where( 'booking_id', $bid ); $db->update( 'pacim_booking', array( 'booking_checkout' => date( 'Y-m-d H:i:s' ) ) );
    if ( function_exists( 'pacim_aa_log_tok' ) ) pacim_aa_log_tok( $db, $tok, $bid, (int) $r['invoice_id'], 'checkout', '', date( 'Y-m-d', strtotime( (string) $r['booking_end'] ) ) );
    ci_json( array( 'ok' => true, 'checked' => true, 'time' => date( 'H:i' ) ) );
}

/* ============================================ AJAX: Check-out zurücknehmen */
if ( $action === 'uncheckout' ) {
    if ( $storno ) ci_fail( 'Buchung ist storniert.' );
    $db->where( 'booking_id', $bid ); $db->update( 'pacim_booking', array( 'booking_checkout' => null ) );
    if ( function_exists( 'pacim_aa_log_tok' ) ) pacim_aa_log_tok( $db, $tok, $bid, (int) $r['invoice_id'], 'uncheckout', '', date( 'Y-m-d', strtotime( (string) $r['booking_end'] ) ) );
    ci_json( array( 'ok' => true, 'unchecked' => true ) );
}

// Aktueller Zustand (für den Live-Abgleich, wenn die App-Shell aus dem Cache kam und veraltet sein könnte).
if ( $action === 'state' ) {
    $ckin  = trim( (string) $r['checkin_time'] );  if ( $ckin  === '00:00' ) $ckin  = '';
    $ckout = trim( (string) $r['checkout_time'] ); if ( $ckout === '00:00' ) $ckout = '';
    $bd    = trim( (string) $r['boarding'] );      if ( $bd    === '00:00' ) $bd    = '';
    ci_json( array(
        'ok'      => true,
        'arrived' => $isDep ? ! empty( $r['booking_checkout'] ) : ! empty( $r['booking_checkin'] ),
        'paid'    => ( $isAida || in_array( (int) $r['invoice_payment'], array( 1, 2, 3 ), true ) ),
        'storno'  => $storno,
        'times'   => array( 'boarding' => ( $isDep ? '' : $bd ), 'checkin' => $ckin, 'checkout' => $ckout ),
    ) );
}

/* ============================================ Seite rendern */
// Geöffneten Datensatz in den Terminal-Sitzungs-Verlauf schreiben (nur Terminal-Sitzungen, echte Seitenaufrufe).
if ( $tok !== '' && ! $embed && function_exists( 'pacim_ts_log_tok' ) ) {
    $__od = trim( (string) $r['booking_no'] );
    $__pl = trim( (string) $r['plate'] ); if ( $__pl !== '' && $__pl !== '—' ) $__od = trim( $__od . ( $__od !== '' ? ' · ' : '' ) . $__pl );
    pacim_ts_log_tok( $db, $tok, 'open', $bid, (int) $r['invoice_id'], $__od );
}
$e = function ( $s ) { return htmlspecialchars( (string) $s, ENT_QUOTES, 'UTF-8' ); };
// Detailansicht: „Nachname, Vorname" (nur EIN Vorname, erster Token).
$fnRaw = trim( (string) $r['customer_firstname'] );
$fn    = $fnRaw !== '' ? preg_split( '/\s+/', $fnRaw )[0] : '';
$ln    = trim( (string) $r['customer_lastname'] );
$fullname = ( $ln !== '' && $fn !== '' ) ? ( $ln . ', ' . $fn ) : trim( $ln . ' ' . $fn );
if ( $fullname === '' ) $fullname = trim( (string) $r['customer_name'] );

/* ---- Kontaktdaten (Kontakt-Ansicht) – leere/"0"-Felder werden ausgelassen. ---- */
$cPhoneRaw = trim( (string) $r['customer_phone'] );
$cEmail    = trim( (string) $r['customer_email'] );
if ( $cPhoneRaw === '0' ) $cPhoneRaw = '';
if ( $cEmail === '0' )    $cEmail = '';
$cStreet  = trim( (string) $r['customer_street'] );
$cZip     = trim( (string) $r['customer_zipcode'] );
$cCity    = trim( (string) $r['customer_city'] );
$cCountry = trim( (string) $r['customer_country'] );
$cCityLine = trim( $cZip . ' ' . $cCity );
$hasAddress = ( $cStreet !== '' || $cCityLine !== '' || $cCountry !== '' );
// tel:-Link – nur Ziffern und ein evtl. führendes „+" behalten.
$cPhoneTel = preg_replace( '/[^0-9+]/', '', $cPhoneRaw ); if ( strpos( $cPhoneTel, '+' ) > 0 ) $cPhoneTel = str_replace( '+', '', $cPhoneTel );
// AIDA-Zusatzangaben: Buchungsnummer = 8 zusammenhängende Ziffern der Rechnungsnr.; Kabine = letzter
// Zahlenabschnitt der booking_no nach dem letzten Bindestrich.
$aidaBookNo = ''; $aidaCabin = '';
if ( $isAida ) {
    if ( preg_match( '/\d{8}/', (string) $r['invoice_no'], $mm ) ) $aidaBookNo = $mm[0];
    $segs = explode( '-', (string) $r['booking_no'] ); $last = trim( (string) end( $segs ) );
    if ( $last !== '' && ctype_digit( $last ) ) $aidaCabin = $last;
}
$plate   = trim( (string) $r['plate'] ) !== '' ? $r['plate'] : '—';
$ship    = trim( (string) $r['booking_event'] ) !== '' ? $r['booking_event'] : '—';
$valet   = ( (int) $r['booking_valet'] === 1 );
$days    = (int) $r['trip_days'];
// Stellplatz: Halle/Außen [- VALET] + Tage (muted)
$stell   = ( (int) $r['booking_type'] === 1 ? 'Halle' : 'Außen' ) . ( $valet ? ' - VALET' : '' );
$bg = strtotime( (string) $r['booking_begin'] ); $en = strtotime( (string) $r['booking_end'] );
$von     = $bg ? date( 'd.m.', $bg ) : '—';
$bis     = $en ? date( 'd.m.y', $en ) : '—';
$boarding = trim( (string) $r['boarding'] ); if ( $boarding === '00:00' ) $boarding = '';
// „arrived"/„ciTime" je nach Modus: Anreisen = Check-in, Abreisen = Check-out. Bei Abreisen gibt es
// keine geplante Abreisezeit (Boarding bleibt für Abreisen leer).
if ( $isDep ) {
    $ciTime  = trim( (string) $r['checkout_time'] ); if ( $ciTime === '00:00' ) $ciTime = '';
    $arrived = ! empty( $r['booking_checkout'] );
    $boarding = '';
} else {
    $ciTime  = trim( (string) $r['checkin_time'] ); if ( $ciTime === '00:00' ) $ciTime = '';
    $arrived = ! empty( $r['booking_checkin'] );
}
// Zeit-Kachel: alle vorhandenen Zeiten (Boarding / Check-in / Check-out) – durch Antippen umschaltbar.
$ckinT  = trim( (string) $r['checkin_time'] );  if ( $ckinT  === '00:00' ) $ckinT  = '';
$ckoutT = trim( (string) $r['checkout_time'] ); if ( $ckoutT === '00:00' ) $ckoutT = '';
$paid    = $isAida || in_array( (int) $r['invoice_payment'], array( 1, 2, 3 ), true );
$coupon  = (int) $r['invoice_coupon'];
$amounts = array( 0 => ci_amount( $pos, 0 ), 1 => ci_amount( $pos, 1 ), 2 => ci_amount( $pos, 2 ) );
$dateKey = substr( (string) $r['booking_begin'], 0, 10 );
$backUrl = $tok !== '' ? SITE_URL . '/day-list.php?t=' . $tok
                       : SITE_URL . '/day-list.php?date=' . $dateKey . '&h=' . pacim_sign( 'daylist:' . $dateKey );

// Fahrzeugbilder-Rechte: Token = Zugangsrechte, Legacy-Hash = volle Rechte.
$imgPerms     = ( $tok !== '' ) ? ( $access ? pacim_ma_img_perms( $access ) : array() ) : pacim_ma_img_all();
$canViewImg   = in_array( 'view',   $imgPerms, true );
$canUploadImg = in_array( 'upload', $imgPerms, true );
$canNoteImg   = in_array( 'note_create', $imgPerms, true );

// Notizen: Lesen darf jeder berechtigte Zugang; Erfassen/Bearbeiten nur mit Recht (siehe $bnCanWrite oben).
$canNotes = $bnCanWrite;

// Anzahlen für die Footer-Tab-Titel (x Notiz/Notizen, x Foto/Fotos).
$noteN  = (int) pacim_bn_count( $db, $bid, true );
$photoN = ( $canViewImg || $canUploadImg ) ? (int) pacim_bi_count_booking( $bid ) : 0;

$cfg = array(
    'b' => $bid, 'h' => $h, 't' => $tok, 'embed' => $embed, 'paid' => $paid, 'arrived' => $arrived, 'storno' => $storno, 'isAida' => $isAida, 'isDep' => $isDep,
    'canAct' => $canAct, 'coupon' => $coupon, 'amounts' => $amounts, 'payType' => (int) $r['invoice_payment'], 'back' => $backUrl,
    'boarding' => $boarding, 'ciTime' => $ciTime,
    'times' => array( 'boarding' => ( $isDep ? '' : $boarding ), 'checkin' => $ckinT, 'checkout' => $ckoutT ),
    'imgApi' => SITE_URL . '/booking-images.php', 'imgView' => $canViewImg, 'imgUpload' => $canUploadImg, 'imgNote' => $canNoteImg,
    'canNotes' => $canNotes, 'hasImg' => ( $canViewImg || $canUploadImg ),
    'plateHasInfo' => ( pacim_plate_lookup( $plate ) !== null ),
    'notesCount' => $noteN,
    'noteMarquee' => pacim_bn_highlighted( $db, $bid ),
    'photosCount' => $photoN,
    'plate' => $plate, 'bookingNo' => (string) $r['booking_no'],
    'posItems' => $posItems, 'addProducts' => $addProducts, 'canEditPos' => ( $canAct && ! $isAida && ! $storno ),
);

header( 'Content-Type: text/html; charset=utf-8' );
echo '<!DOCTYPE html><html lang="de"><head><meta charset="utf-8">'
   . '<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, viewport-fit=cover">'
   . '<meta name="robots" content="noindex,nofollow">'
   . '<meta name="apple-mobile-web-app-capable" content="yes"><meta name="mobile-web-app-capable" content="yes">'
   . '<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"><meta name="theme-color" content="#0d2c44">'
   // Web-App (PWA): Manifest/iOS-Icon nur bei direktem Token-Aufruf (nicht im eingebetteten Overlay, nicht im CRM).
   . ( ( $tok !== '' && ! $embed ) ?
         '<link rel="manifest" href="' . SITE_URL . '/manifest.php?t=' . rawurlencode( $tok ) . '">'
       . '<link rel="apple-touch-icon" href="' . SITE_URL . '/assets/webapp/icon-180.png">'
       . '<meta name="apple-mobile-web-app-title" content="Seaside Parking">' : '' )
   . '<title>Check-in · ' . $e( $plate ) . '</title>'
   . '<style>@font-face{font-family:"EuroPlate";src:url("' . SITE_URL . '/assets/fonts/EuroPlate.ttf") format("truetype");font-display:swap;}'
   . ci_css() . '</style></head><body' . ( $embed ? '' : ' class="ci-sa"' ) . '>';
?>
<div class="card">
  <div class="hd">
<a class="back" id="ci-back" href="<?= $e( $backUrl ) ?>" title="Zurück zur Liste" aria-label="Zurück zur Liste"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg></a>
    <div class="k"><?= $readOnly ? 'Übersicht' : ( $isDep ? 'Check-out' : ( $isAida ? 'Check-in' : 'Check-in &amp; Zahlung' ) ) ?></div>
    <div class="t"><?= $e( $fullname ?: '—' ) ?></div>
    <div class="no"><?= $e( $r['booking_no'] ) ?><?= ( ! $isAida && ! $readOnly && $r['invoice_no'] ) ? ' · Rg. ' . $e( $r['invoice_no'] ) : '' ?></div>
    <span class="hstat" id="ci-status-badge"></span>
  </div>
  <div class="slider" id="ci-slider"><div class="track" id="ci-track">

  <!-- ============================ Tab 1: Buchung ============================ -->
  <section class="pane active" id="pane-buchung" data-pane="buchung">
  <?php $mqText = implode( '   •••   ', (array) $cfg['noteMarquee'] ); ?>
  <!-- Hervorgehobene Notiz(en) als Endlos-Lauftext im Datenblatt -->
  <div class="ci-marquee<?= $mqText === '' ? ' d-none' : '' ?>" id="ci-marquee">
    <svg class="ci-mq-ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M3 11v2a1 1 0 0 0 1 1h2l4 4V6L6 10H4a1 1 0 0 0-1 1z"/><path d="M15 9a3 3 0 0 1 0 6M17.5 6.5a6 6 0 0 1 0 11"/></svg>
    <div class="ci-mq-win"><div class="ci-mq-track" id="ci-mq-track"><span class="ci-mq-seg"><?= $e( $mqText ) ?></span><span class="ci-mq-seg" aria-hidden="true"><?= $e( $mqText ) ?></span></div></div>
  </div>
  <div class="bd">
    <?php $plateRO = ( $storno || $readOnly ) ? ' data-ro="1"' : ''; ?>
    <!-- Kennzeichen-Kachel -->
    <div class="kachel k-plate">
      <div class="k-l">Kennzeichen</div>
      <div class="plate-wrap">
        <div class="plate fe" id="ci-plate" spellcheck="false"<?= $plateRO ?>><?= $e( $plate ) ?></div>
        <button type="button" class="plate-save d-none" id="ci-plate-save" title="Speichern" aria-label="Speichern"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg></button>
        <button type="button" class="plate-info<?= $cfg['plateHasInfo'] ? '' : ' d-none' ?>" id="ci-plate-info" title="Kennzeichen-Herkunft" aria-label="Kennzeichen-Info"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9.5"/><path d="M12 11v5"/><circle cx="12" cy="7.7" r="0.7" fill="currentColor" stroke="none"/></svg></button>
      </div>
    </div>
    <!-- Info-Kacheln -->
    <div class="k-grid">
      <div class="kachel"><div class="k-l">Dauer</div><div class="k-v"><?= (int) $days ?> Tage</div><div class="k-sub"><?= $e( $von ) ?> – <?= $e( $bis ) ?></div></div>
      <div class="kachel"><div class="k-l">Event</div><div class="k-v"><?= $e( $ship ) ?></div></div>
      <div class="kachel"><div class="k-l">Platz</div><div class="k-v"><?= $e( $stell ) ?></div></div>
      <?php
        // Verfügbare Zeiten in fester Reihenfolge; Standard-Anzeige = höchste (Check-out > Check-in > Boarding).
        $ciTimes = array();
        if ( ! $isDep && $boarding !== '' ) $ciTimes[] = array( 'l' => 'Boarding',  'v' => $boarding );
        if ( $ckinT  !== '' )               $ciTimes[] = array( 'l' => 'Check-in',  'v' => $ckinT );
        if ( $ckoutT !== '' )               $ciTimes[] = array( 'l' => 'Check-out', 'v' => $ckoutT );
        $showTime = count( $ciTimes ) > 0;
        $tIdx     = $showTime ? count( $ciTimes ) - 1 : 0;
        $multiT   = count( $ciTimes ) > 1;
        $emptyLbl = $isDep ? 'Check-out' : 'Boarding';   // Label, wenn (noch) keine Zeit vorhanden -> „–"
      ?>
      <div class="kachel<?= $multiT ? ' k-tap' : '' ?>" id="ci-timerow">
        <div class="k-l" id="ci-timelabel"><span id="ci-timelbl-tx"><?= $e( $showTime ? $ciTimes[ $tIdx ]['l'] : $emptyLbl ) ?></span><svg class="k-cyc<?= $multiT ? '' : ' d-none' ?>" id="ci-timecyc" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 4v6h-6"/><path d="M1 20v-6h6"/><path d="M3.5 9a9 9 0 0 1 14.9-3.4L23 10M1 14l4.6 4.4A9 9 0 0 0 20.5 15"/></svg></div>
        <div class="k-v" id="ci-timeval"><?= $showTime ? $e( $ciTimes[ $tIdx ]['v'] ) . ' Uhr' : '–' ?></div>
      </div>
    </div>
  </div>

  <?php if ( $storno ) { ?>
    <div class="sec"><div class="note warn">Diese Buchung ist storniert – keine Aktion möglich.</div></div>
  <?php } elseif ( $readOnly ) { ?>
    <div class="sec"><div class="note info">Nur Ansicht – Check-in und Zahlung sind für diesen Zugang nicht freigegeben.</div></div>
  <?php } ?>

  <!-- Navigations-Karten: öffnen die jeweilige Unterseite -->
  <div class="navcards">
    <button type="button" class="navcard navcard-rg" data-go="rechnung">
      <span class="nc-ic ic-green"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2h9l3 3v17l-3-2-3 2-3-2-3 2V2z"/><path d="M9 7h6M9 11h6M9 15h4"/></svg></span>
      <span class="nc-tx"><span class="nc-t">Rechnung</span><span class="nc-s <?= $isAida ? 'paid' : 'open' ?>" id="ci-rg-cardst"><?= $isAida ? 'bezahlt' : 'offen' ?></span></span>
      <span class="nc-amt <?= $isAida ? 'paid' : 'open' ?>" id="ci-nc-rg"><?= $e( ci_eur( $amounts[0] ) ) ?></span>
    </button>
    <div class="navtabs">
      <button type="button" class="navtab" data-go="notizen">
        <span class="nc-ic ic-amber"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16v12l-4 4H4z"/><path d="M16 20v-4h4M8 9h8M8 13h5"/></svg></span>
        <span class="navtab-t">Notizen</span>
      </button>
      <?php if ( $canViewImg || $canUploadImg ) { ?>
      <button type="button" class="navtab" data-go="fotos">
        <span class="nc-ic ic-teal"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg></span>
        <span class="navtab-t">Fotos</span>
      </button>
      <?php } ?>
      <button type="button" class="navtab" data-go="kontakt">
        <span class="nc-ic ic-blue"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round"><path d="M20 8v6M23 11h-6"/><circle cx="9" cy="8" r="4"/><path d="M3 21v-1a6 6 0 0 1 6-6h0a6 6 0 0 1 6 6v1"/></svg></span>
        <span class="navtab-t">Kontakt</span>
      </button>
    </div>
  </div>

  <div class="status" id="ci-status"></div>
  </section>

  <!-- ============================ Unterseite: Rechnung ============================ -->
  <section class="pane" id="pane-rechnung" data-pane="rechnung">
    <div class="subhd"><button type="button" class="subback" data-go="buchung"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg> Übersicht</button><span class="subhd-t">Rechnung</span></div>
    <div class="bd">
      <!-- Rechnungsbetrag: riesig, rot = offen / grün = bezahlt -->
      <div class="rg-box">
        <div class="rg-amount <?= $isAida ? 'paid' : 'open' ?>" id="ci-rg-amount"><?= $e( ci_eur( $amounts[0] ) ) ?></div>
        <div class="rg-status <?= $isAida ? 'paid' : 'open' ?>" id="ci-rg-status"><?= $isAida ? 'bezahlt · myAIDA' : 'offen' ?></div>
      </div>
      <!-- Posten (POS-Stil) – per JS gerendert aus CFG.posItems -->
      <div class="rg-pos" id="ci-pos-list"></div>
      <?php if ( $canAct && ! $isAida && ! $storno ) { ?>
      <div class="rg-addwrap">
        <button type="button" class="rg-addbtn" id="ci-pos-addbtn"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg> Posten hinzufügen</button>
        <div class="rg-picker d-none" id="ci-pos-picker"></div>
        <div class="status" id="ci-pos-msg"></div>
      </div>
      <?php } ?>
      <?php if ( ! $isAida && ! $storno && ! $readOnly ) { ?>
        <!-- Zahlungs-Status (Erfassen NUR über den großen Aktions-Button unten) -->
        <div class="sec d-none" id="ci-paid-sec">
          <div class="note ok"><strong>Bezahlt:</strong> <span id="ci-paid-info"></span> <button type="button" class="lnk" id="ci-edit-btn">ändern</button></div>
        </div>
      <?php } elseif ( $storno ) { ?>
        <div class="sec"><div class="note warn">Buchung storniert.</div></div>
      <?php } elseif ( $readOnly ) { ?>
        <div class="sec"><div class="note info">Zahlung ist für diesen Zugang nicht freigegeben.</div></div>
      <?php } ?>
      <?php if ( ! $isAida && $r['invoice_no'] ) { ?><div class="sec"><div class="note info">Rechnungs-Nr.: <?= $e( $r['invoice_no'] ) ?></div></div><?php } ?>
    </div>
  </section>

  <!-- ============================ Tab 2: Notizen ============================ -->
  <section class="pane" id="pane-notizen" data-pane="notizen">
    <div class="subhd"><button type="button" class="subback" data-go="buchung"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg> Übersicht</button><span class="subhd-t">Notizen</span></div>
    <?php $hint = trim( (string) $r['booking_hint'] ); $hintReply = trim( (string) $r['booking_hint_reply'] ); ?>
    <?php if ( $hint !== '' ) { ?>
    <div class="nt-hint">
      <div class="nt-hint-h"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"/><path d="M12 8h.01M11 12h1v4h1"/></svg> Kundenhinweis zur Buchung</div>
      <div class="nt-hint-b"><?= nl2br( $e( $hint ) ) ?></div>
      <?php if ( $hintReply !== '' ) { ?>
      <details class="nt-hint-reply">
        <summary class="nt-hint-rh">Antwort gesendet<?= $r['hint_reply_at'] ? ' am ' . $e( $r['hint_reply_at'] ) . ' Uhr' : '' ?><?= trim( (string) $r['booking_hint_reply_by'] ) !== '' ? ' · ' . $e( $r['booking_hint_reply_by'] ) : '' ?></summary>
        <div class="nt-hint-b"><?= nl2br( $e( $hintReply ) ) ?></div>
      </details>
      <?php } ?>
    </div>
    <?php } ?>

    <div class="nt-block">
      <div class="sec-h nt-title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16v12l-4 4H4z"/><path d="M16 20v-4h4M8 9h8M8 13h5"/></svg> Interne Notizen <span class="ph-count" id="ci-nt-count"></span></div>
      <?php if ( $canNotes && ! $storno ) { ?>
      <div class="nt-add">
        <textarea id="ci-nt-text" rows="3" maxlength="4000" placeholder="Interne Notiz zur Buchung …"></textarea>
        <label class="nt-hl-opt"><input type="checkbox" id="ci-nt-hl"> <span>Hervorheben – läuft im Datenblatt als Lauftext durch</span></label>
        <div class="modal-err d-none" id="ci-nt-err"></div>
        <button type="button" class="btn btn-checkin" id="ci-nt-save">Notiz speichern</button>
      </div>
      <?php } ?>
      <div class="nt-list" id="ci-nt-list"></div>
      <div class="nt-empty d-none" id="ci-nt-empty">Noch keine internen Notizen.</div>
      <?php if ( ! $canNotes ) { ?><div class="nt-foot">Dieser Zugang darf interne Notizen nur ansehen.</div><?php } ?>
    </div>
  </section>

  <!-- ============================ Tab 3: Fotos ============================ -->
  <?php if ( $canViewImg || $canUploadImg ) { ?>
  <section class="pane" id="pane-fotos" data-pane="fotos">
  <div class="subhd"><button type="button" class="subback" data-go="buchung"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg> Übersicht</button><span class="subhd-t">Fotos</span></div>
  <div class="sec ph-sec" id="ci-photos">
    <div class="ph-head">
      <span class="sec-h ph-title"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg> Fahrzeugfotos <span class="ph-count" id="ci-ph-count"></span></span>
      <?php if ( $canUploadImg ) { ?><button type="button" class="ph-add" id="ci-ph-add"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M5 12h14"/></svg> Fotos</button><?php } ?>
    </div>
    <div class="ph-grid" id="ci-ph-grid"></div>
    <div class="ph-empty" id="ci-ph-empty"><?= $canUploadImg ? 'Noch keine Fahrzeugfotos – jetzt hinzufügen.' : 'Noch keine Fahrzeugfotos vorhanden.' ?></div>
  </div>
  </section>
  <?php } ?>

  <!-- ============================ Unterseite: Kontakt ============================ -->
  <section class="pane" id="pane-kontakt" data-pane="kontakt">
    <div class="subhd"><button type="button" class="subback" data-go="buchung"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg> Übersicht</button><span class="subhd-t">Kontakt</span></div>
    <div class="bd">
      <div class="kt-card">
        <div class="kt-name"><?= $e( $fullname ?: '—' ) ?></div>
        <?php if ( $hasAddress ) { ?>
        <div class="kt-row"><span class="kt-lbl">Anschrift</span><span class="kt-val">
          <?php if ( $cStreet !== '' )   echo $e( $cStreet ) . '<br>'; ?>
          <?php if ( $cCityLine !== '' ) echo $e( $cCityLine ) . '<br>'; ?>
          <?php if ( $cCountry !== '' )  echo $e( $cCountry ); ?>
        </span></div>
        <?php } ?>
        <?php if ( $cPhoneRaw !== '' ) { ?>
        <div class="kt-row"><span class="kt-lbl">Telefon</span><span class="kt-val"><?= $e( $cPhoneRaw ) ?></span></div>
        <?php } ?>
        <?php if ( $cEmail !== '' ) { ?>
        <div class="kt-row"><span class="kt-lbl">E-Mail</span><span class="kt-val"><a href="mailto:<?= $e( $cEmail ) ?>"><?= $e( $cEmail ) ?></a></span></div>
        <?php } ?>
        <?php if ( $isAida && $aidaBookNo !== '' ) { ?>
        <div class="kt-row"><span class="kt-lbl">Buchungsnummer</span><span class="kt-val"><?= $e( $aidaBookNo ) ?></span></div>
        <?php } ?>
        <?php if ( $isAida && $aidaCabin !== '' ) { ?>
        <div class="kt-row"><span class="kt-lbl">Kabine</span><span class="kt-val"><?= $e( $aidaCabin ) ?></span></div>
        <?php } ?>
      </div>
      <?php if ( $cPhoneTel !== '' ) { ?>
      <a class="kt-call" href="tel:<?= $e( $cPhoneTel ) ?>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.8 19.8 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.8 19.8 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72c.13.96.36 1.9.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.9.34 1.85.57 2.81.7A2 2 0 0 1 22 16.92z"/></svg> Kunde anrufen</a>
      <?php } ?>
      <?php if ( ! $hasAddress && $cPhoneRaw === '' && $cEmail === '' && ! ( $isAida && ( $aidaBookNo !== '' || $aidaCabin !== '' ) ) ) { ?>
      <div class="sec"><div class="note info">Keine Kontaktdaten hinterlegt.</div></div>
      <?php } ?>
    </div>
  </section>

  </div></div><!-- /track /slider -->
</div>

<!-- Footer-Tab-Navigation (fixiert) -->
<nav class="tabbar" id="ci-tabbar">
  <button type="button" class="tab active" data-tab="buchung">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="3" width="16" height="18" rx="2"/><path d="M8 7h8M8 11h8M8 15h5"/></svg>
    <span>Buchung</span></button>
  <button type="button" class="tab" data-tab="notizen">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h16v12l-4 4H4z"/><path d="M16 20v-4h4M8 9h8M8 13h5"/></svg>
    <span id="ci-tb-notes"><?= $noteN ?> Notiz<?= $noteN === 1 ? '' : 'en' ?></span></button>
  <?php if ( $canViewImg || $canUploadImg ) { ?>
  <button type="button" class="tab" data-tab="fotos">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
    <span id="ci-tb-photos"><?= $photoN ?> Foto<?= $photoN === 1 ? '' : 's' ?></span></button>
  <?php } ?>
</nav>

<?php if ( ! $storno && ! $readOnly ) { ?>
<!-- Fixierter Haupt-Aktions-Button (ersetzt die Fuß-Tableiste) -->
<div class="actionbar" id="ci-actionbar"><button type="button" class="btn" id="ci-action-btn">Aktion</button></div>
<?php } ?>

<?php if ( ! $storno && ! $readOnly && ! $isAida ) { ?>
<!-- Zahlungs-Modal -->
<div class="overlay d-none" id="ci-pay-modal">
  <div class="modal">
    <div class="modal-h"><span>Zahlung erfassen</span><button type="button" class="modal-x" id="ci-pay-close" aria-label="Schließen">&times;</button></div>
    <div class="modal-b">
      <label class="fld-l">Rabattaktion</label>
      <select id="ci-coupon" class="sel">
        <option value="0">Kein Rabatt</option>
        <option value="1">5 € Bestandskundenrabatt</option>
        <option value="2">10 % Frühbucherrabatt</option>
      </select>
      <div class="amount">Zu zahlen: <strong id="ci-amount">—</strong></div>
      <label class="fld-l">Zahlungsart</label>
      <div class="pay-types" id="ci-types">
        <button type="button" class="pt" data-type="2">EC-Karte</button>
        <button type="button" class="pt" data-type="3">Bar</button>
      </div>
      <div class="modal-err d-none" id="ci-pay-err"></div>
      <button type="button" class="btn btn-checkin" id="ci-pay-btn">Zahlung speichern</button>
    </div>
  </div>
</div>
<?php } ?>

<?php if ( $canUploadImg ) { ?>
<!-- Foto-Upload-Modal -->
<div class="overlay d-none" id="ci-up-modal">
  <div class="modal ph-upmodal">
    <div class="modal-h"><span>Fahrzeugfotos hinzufügen</span><button type="button" class="modal-x" id="ci-up-close" aria-label="Schließen">&times;</button></div>
    <div class="modal-b">
      <div class="ph-pick">
        <label class="ph-pickbtn"><input type="file" id="ci-up-cam" accept="image/*" capture="environment" hidden>
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
          <span>Kamera</span></label>
        <label class="ph-pickbtn"><input type="file" id="ci-up-gal" accept="image/*,.heic,.heif" multiple hidden>
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>
          <span>Galerie</span></label>
      </div>
      <div class="ph-tiles" id="ci-up-tiles"></div>
      <div class="modal-err d-none" id="ci-up-err"></div>
      <button type="button" class="btn btn-checkin" id="ci-up-go" disabled>Hochladen</button>
    </div>
  </div>
</div>
<?php } ?>

<?php if ( $canViewImg ) { ?>
<!-- Lightbox -->
<div class="overlay lb d-none" id="ci-lb">
  <div class="lb-bar">
    <span class="lb-pos" id="ci-lb-pos"></span>
    <span class="lb-actions">
      <button type="button" class="lb-btn active" id="ci-lb-mtoggle" title="Markierungen ein/aus"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 21s-7-6.3-7-11a7 7 0 0 1 14 0c0 4.7-7 11-7 11z"/><circle cx="12" cy="10" r="2.5"/></svg></button>
      <button type="button" class="lb-btn" id="ci-lb-zoom" title="Zoom"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.3-4.3M11 8.5v5M8.5 11h5"/></svg></button>
      <button type="button" class="lb-btn" id="ci-lb-dl" style="display:none" title="Original"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12M7 10l5 5 5-5M5 21h14"/></svg></button>
      <button type="button" class="lb-btn" id="ci-lb-del" style="display:none" title="Löschen"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18M8 6V4h8v2M6 6l1 14h10l1-14"/></svg></button>
      <button type="button" class="lb-btn" id="ci-lb-x" title="Schließen">&times;</button>
    </span>
  </div>
  <div class="lb-stage" id="ci-lb-stage"><img id="ci-lb-img" alt="Foto" draggable="false"></div>
  <button type="button" class="lb-nav lb-prev" id="ci-lb-prev" aria-label="Zurück">&#8249;</button>
  <button type="button" class="lb-nav lb-next" id="ci-lb-next" aria-label="Weiter">&#8250;</button>
  <div class="lb-info" id="ci-lb-info"></div>
</div>
<?php } ?>

<!-- Kennzeichen-Herkunft (lokale DB, DE/AT/CH) -->
<div class="overlay d-none" id="ci-plate-modal">
  <div class="modal pl-modal">
    <div class="modal-h"><span>Kennzeichen-Herkunft</span><button type="button" class="modal-x" id="ci-pl-close" aria-label="Schließen">&times;</button></div>
    <div class="modal-b" id="ci-pl-body"></div>
  </div>
</div>

<!-- Check-in Erfolg / Countdown -->
<div class="overlay d-none" id="ci-done">
  <div class="done-box">
    <div class="check" id="ci-done-ic"><svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg></div>
    <div class="done-t" id="ci-done-t">Check-in erfasst</div>
    <div class="done-x">Zurück zur Liste in <span id="ci-cd">3</span> s …</div>
    <button type="button" class="btn-cancel" id="ci-cancel">Abbrechen – hier bleiben</button>
    <div class="done-skip">irgendwo tippen, um sofort zur Liste zu wechseln</div>
  </div>
</div>

<script>
var CFG = <?= json_encode( $cfg ) ?>;
(function(){
  // Browser-Zoom unterbinden (iOS Safari ignoriert user-scalable=no)
  ['gesturestart','gesturechange','gestureend'].forEach(function(ev){ document.addEventListener(ev, function(e){ e.preventDefault(); }, {passive:false}); });
  document.addEventListener('touchmove', function(e){ if(e.touches && e.touches.length>1) e.preventDefault(); }, {passive:false});
  // Vollbild beim ersten Tippen (nur eigenständig; eingebettet übernimmt das die Tagesliste).
  if(!CFG.embed){ document.addEventListener('pointerdown', function f(ev){ if(ev.target&&ev.target.closest&&ev.target.closest('a,button,input,textarea,select,label')) return; document.removeEventListener('pointerdown',f); var e=document.documentElement,r=e.requestFullscreen||e.webkitRequestFullscreen; if(r){ try{ r.call(e); }catch(_){} } }, {passive:true}); }

  var API = location.pathname; // gleiche Datei
  var $ = function(id){ return document.getElementById(id); };
  var coupon = $('ci-coupon'), amountEl = $('ci-amount'), status = $('ci-status');
  var selType = CFG.payType || 0;  // keine Zahlungsart vorausgewählt

  // Eingebettet (Overlay der Tagesliste): Statusänderungen an die Elternseite melden.
  function ciState(){ return CFG.storno?'x':(CFG.arrived?'i':(CFG.paid?'p':'o')); }
  function parentMsg(o){ if(CFG.embed && window.parent && window.parent!==window){ o.pacimCi=1; o.b=CFG.b; try{ window.parent.postMessage(o,'*'); }catch(e){} } }
  // Zeit-Kachel: zeigt Boarding / Check-in / Check-out. Standard = höchste vorhandene Zeit
  // (Check-out > Check-in > Boarding). Sind mehrere Zeiten da, schaltet ein Tippen durch.
  var timeIdx=null;
  function ciTimeList(){ var t=CFG.times||{}, list=[];
    if(!CFG.isDep && t.boarding) list.push({l:'Boarding', v:t.boarding});
    if(t.checkin)  list.push({l:'Check-in', v:t.checkin});
    if(t.checkout) list.push({l:'Check-out',v:t.checkout});
    return list; }
  function renderTimeRow(){
    var row=$('ci-timerow'), lab=$('ci-timelbl-tx'), val=$('ci-timeval'), cyc=$('ci-timecyc'); if(!row||!lab||!val) return;
    var list=ciTimeList();
    row.classList.remove('d-none');
    if(!list.length){ lab.textContent = CFG.isDep ? 'Check-out' : 'Boarding'; val.textContent='–'; row.classList.remove('k-tap'); if(cyc) cyc.classList.add('d-none'); return; }
    if(timeIdx===null || timeIdx>=list.length) timeIdx=list.length-1;   // Standard: höchste Zeit
    lab.textContent=list[timeIdx].l; val.textContent=list[timeIdx].v+' Uhr';
    var multi=list.length>1;
    row.classList.toggle('k-tap', multi);
    if(cyc) cyc.classList.toggle('d-none', !multi);
  }
  (function(){ var row=$('ci-timerow'); if(!row) return;
    row.addEventListener('click', function(){ var list=ciTimeList(); if(list.length<2) return;
      timeIdx=((timeIdx===null?list.length-1:timeIdx)+1)%list.length; renderTimeRow(); }); })();

  function eur(v){ return v.toFixed(2).replace('.',',') + ' €'; }
  function setStatus(t,k){ status.textContent = t||''; status.className = 'status' + (k?(' '+k):''); }

  function refreshAmount(){ if(amountEl) amountEl.textContent = eur(CFG.amounts[coupon ? coupon.value : 0]); }
  function markType(){ var ts=document.querySelectorAll('#ci-types .pt'); for(var i=0;i<ts.length;i++) ts[i].classList.toggle('on', parseInt(ts[i].dataset.type,10)===selType); }

  function statusBadge(){
    var sb=$('ci-status-badge'); if(!sb) return;
    var cls,txt;
    if(CFG.storno){ cls='s-red'; txt='Storniert'; }
    else if(CFG.isDep){ if(CFG.arrived){ cls='s-green'; txt='Abgereist'; } else { cls='s-blue'; txt='Abreise offen'; } }
    else if(CFG.arrived){ cls='s-green'; txt='Angereist'; }
    else if(CFG.paid){ cls='s-blue'; txt='Check-in offen'; }
    else { cls='s-orange'; txt = CFG.canAct ? (eur(CFG.amounts[CFG.coupon||0])+' offen') : 'Zahlung offen'; }
    sb.className='hstat '+cls; sb.textContent=txt;
  }
  var PAYNAMES={1:'Überweisung',2:'EC-Karte',3:'Bar'};

  function renderState(){
    statusBadge();
    if(CFG.storno) return;
    var paidSec=$('ci-paid-sec'), btn=$('ci-action-btn');
    if(paidSec){
      if(CFG.paid && !CFG.isAida){
        paidSec.classList.remove('d-none');
        $('ci-paid-info').textContent = eur(CFG.amounts[CFG.coupon||0]) + ' · ' + (PAYNAMES[CFG.payType]||'');
      } else paidSec.classList.add('d-none');
    }
    if(btn){
      btn.disabled=false;
      if(CFG.isDep){
        // Abreisen: Check-out erfassen / zurücknehmen (keine Zahlungs-Logik).
        if(CFG.arrived){ btn.textContent='Check-out zurücknehmen'; btn.className='btn btn-danger'; btn.dataset.act='uncheckout'; }
        else { btn.textContent='Check-out erfassen'; btn.className='btn btn-checkin'; btn.dataset.act='checkout'; }
      }
      else if(CFG.arrived){ btn.textContent='Fahrzeug auschecken'; btn.className='btn btn-danger'; btn.dataset.act='uncheckin'; }
      else if(CFG.paid){ btn.textContent='Check-in durchführen'; btn.className='btn btn-checkin'; btn.dataset.act='checkin'; }
      else { btn.textContent='Zahlung erfassen'; btn.className='btn btn-warn'; btn.dataset.act='pay'; }
    }
    // Navigations-Karten + Rechnung/Zahlungen-Unterseiten synchron halten
    var amt=eur(CFG.amounts[CFG.coupon||0]);
    var isPaid = !!(CFG.paid || CFG.isAida);
    function paintPaid(el){ if(!el) return; el.classList.toggle('paid', isPaid); el.classList.toggle('open', !isPaid); }
    var ncrg=$('ci-nc-rg'); if(ncrg){ ncrg.textContent=amt; paintPaid(ncrg); }
    var rgCs=$('ci-rg-cardst'); if(rgCs){ rgCs.textContent = isPaid ? 'bezahlt' : 'offen'; paintPaid(rgCs); }
    var rgAmt=$('ci-rg-amount'); if(rgAmt){ rgAmt.textContent=amt; paintPaid(rgAmt); }
    var rgSt=$('ci-rg-status'); if(rgSt){ rgSt.textContent = CFG.isAida ? 'bezahlt · myAIDA' : (CFG.paid ? ('bezahlt · '+(PAYNAMES[CFG.payType]||'')) : 'offen'); paintPaid(rgSt); }
  }

  if(coupon){ coupon.value = CFG.coupon || 0; coupon.addEventListener('change', refreshAmount); refreshAmount(); }
  var types=document.querySelectorAll('#ci-types .pt');
  for(var i=0;i<types.length;i++){ types[i].addEventListener('click', function(){ selType=parseInt(this.dataset.type,10); markType(); payClearErr(); }); }
  markType();

  function post(data, cb){
    var fd=new URLSearchParams(); fd.set('b',CFG.b); fd.set('h',CFG.h); if(CFG.t) fd.set('t',CFG.t);
    for(var k in data) fd.set(k,data[k]);
    fetch(API,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded;charset=UTF-8'},body:fd.toString()})
      .then(function(r){return r.json();}).then(cb).catch(function(){ setStatus('Serverfehler.','err'); });
  }

  // ---- Zahlungs-Modal
  var payModal=$('ci-pay-modal'), payErr=$('ci-pay-err');
  function payError(m){ if(payErr){ payErr.textContent=m||'Fehler.'; payErr.classList.remove('d-none'); } else setStatus(m,'err'); }
  function payClearErr(){ if(payErr){ payErr.textContent=''; payErr.classList.add('d-none'); } }
  function openPay(){ if(!payModal) return; selType=CFG.payType||0; if(coupon){coupon.value=CFG.coupon||0; refreshAmount();} markType(); payClearErr(); payModal.classList.remove('d-none'); }
  function closePay(){ if(payModal) payModal.classList.add('d-none'); }
  var payClose=$('ci-pay-close'); if(payClose) payClose.addEventListener('click', closePay);
  if(payModal) payModal.addEventListener('click', function(e){ if(e.target===payModal) closePay(); });

  var payBtn=$('ci-pay-btn');
  if(payBtn) payBtn.addEventListener('click', function(){
    payClearErr();
    if(!selType){ payError('Bitte Zahlungsart wählen.'); return; }
    payBtn.disabled=true;
    post({action:'pay', type:selType, coupon:(coupon?coupon.value:0)}, function(res){
      payBtn.disabled=false;
      if(!res||!res.ok){ payError((res&&res.error)||'Fehler.'); return; }   // Fehler bleibt im Modal, keine Rechnung erstellt
      CFG.paid=true; CFG.coupon=res.coupon; CFG.payType=res.type;
      closePay(); renderState(); parentMsg({state:ciState()});
      setStatus('✓ Zahlung erfasst ('+res.amount_fmt+' · '+res.type_name+')','ok');
    });
  });

  var editBtn=$('ci-edit-btn');
  if(editBtn) editBtn.addEventListener('click', function(){ openPay(); });

  var IN_ICON='<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg>';
  var OUT_ICON='<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M14 3h5a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-5"/><path d="M11 16l4-4-4-4"/><path d="M15 12H3"/></svg>';
  function startCountdown(mode){
    var out=(mode==='out');
    var ov=$('ci-done'); if(!ov){ if(CFG.embed) parentMsg({close:1}); else location.href=CFG.back; return; }
    ov.classList.toggle('out', out);
    var ic=$('ci-done-ic'); if(ic) ic.innerHTML = out?OUT_ICON:IN_ICON;
    var tt=$('ci-done-t'); if(tt) tt.textContent = CFG.isDep ? (out?'Check-out zurückgenommen':'Abreise erfasst') : (out?'Fahrzeug ausgecheckt':'Check-in erfasst');
    ov.classList.remove('d-none');
    var n=3, cd=$('ci-cd'), done=false; cd.textContent=n;
    var t=setInterval(function(){ n--; cd.textContent=n; if(n<=0) finish(); },1000);
    function finish(){ if(done) return; done=true; clearInterval(t); if(CFG.embed){ parentMsg({close:1}); } else { location.href=CFG.back; } }
    function stay(){ if(done) return; done=true; clearInterval(t); ov.classList.add('d-none'); }
    var cBtn=$('ci-cancel'); if(cBtn) cBtn.onclick=function(e){ e.stopPropagation(); stay(); };  // Abbrechen -> hier bleiben
    ov.onclick=function(e){ if(e.target.closest('#ci-cancel')) return; finish(); };               // woanders -> sofort weiter
  }

  var actBtn=$('ci-action-btn');
  if(actBtn) actBtn.addEventListener('click', function(){
    var act=actBtn.dataset.act;
    if(act==='pay'){ openPay(); return; }
    if(act==='uncheckin'){
      actBtn.disabled=true; setStatus('Check-in wird zurückgenommen …');
      post({action:'uncheckin'}, function(res){
        if(!res||!res.ok){ actBtn.disabled=false; setStatus((res&&res.error)||'Fehler.','err'); return; }
        CFG.arrived=false; CFG.ciTime=''; if(CFG.times) CFG.times.checkin=''; timeIdx=null; renderState(); renderTimeRow(); parentMsg({state:ciState()}); setStatus('Check-in zurückgenommen.','ok');
        startCountdown('out');
      });
      return;
    }
    if(act==='checkin'){
      actBtn.disabled=true; setStatus('Check-in …');
      post({action:'checkin'}, function(res){
        if(!res||!res.ok){ actBtn.disabled=false; setStatus((res&&res.error)||'Fehler.','err'); return; }
        CFG.arrived=true; CFG.ciTime=res.time||CFG.ciTime; if(CFG.times) CFG.times.checkin=res.time||CFG.ciTime; timeIdx=null; renderState(); renderTimeRow(); parentMsg({state:'i'}); startCountdown('in');
      });
      return;
    }
    if(act==='checkout'){
      actBtn.disabled=true; setStatus('Check-out …');
      post({action:'checkout'}, function(res){
        if(!res||!res.ok){ actBtn.disabled=false; setStatus((res&&res.error)||'Fehler.','err'); return; }
        CFG.arrived=true; CFG.ciTime=res.time||CFG.ciTime; if(CFG.times) CFG.times.checkout=res.time||CFG.ciTime; timeIdx=null; renderState(); renderTimeRow(); parentMsg({state:'i'}); startCountdown('in');
      });
      return;
    }
    if(act==='uncheckout'){
      actBtn.disabled=true; setStatus('Check-out wird zurückgenommen …');
      post({action:'uncheckout'}, function(res){
        if(!res||!res.ok){ actBtn.disabled=false; setStatus((res&&res.error)||'Fehler.','err'); return; }
        CFG.arrived=false; CFG.ciTime=''; if(CFG.times) CFG.times.checkout=''; timeIdx=null; renderState(); renderTimeRow(); parentMsg({state:ciState()}); setStatus('Check-out zurückgenommen.','ok');
        startCountdown('out');
      });
      return;
    }
  });

  // Kennzeichen ändern – direkt im Kennzeichen-Feld (Klick rein aktiviert, Speichern-Button rechts)
  var plate=$('ci-plate'), plSave=$('ci-plate-save'), plOrig = plate ? plate.textContent : '';
  if(plate && plSave && !plate.getAttribute('data-ro')){
    function startEdit(){
      if(plate.isContentEditable) return;
      plOrig = plate.textContent;
      plate.contentEditable='true'; plate.classList.add('editing'); plSave.classList.remove('d-none');
      plate.focus();
      try{ var rng=document.createRange(); rng.selectNodeContents(plate); rng.collapse(false); var sel=getSelection(); sel.removeAllRanges(); sel.addRange(rng); }catch(e){}
    }
    function stopEdit(){ plate.contentEditable='false'; plate.classList.remove('editing'); plSave.classList.add('d-none'); }
    plate.addEventListener('click', startEdit);
    plate.addEventListener('keydown', function(ev){
      if(ev.key==='Enter'){ ev.preventDefault(); plSave.click(); }
      else if(ev.key==='Escape'){ plate.textContent=plOrig; stopEdit(); }
    });
    // Klick außerhalb: ohne Änderung Bearbeiten beenden; mit ungespeicherter Änderung nachfragen.
    plate.addEventListener('blur', function(){
      if(!plate.isContentEditable) return;
      var cur=(plate.textContent||'').replace(/\s+/g,' ').trim();
      var org=(plOrig||'').replace(/\s+/g,' ').trim();
      if(cur===org || cur===''){ plate.textContent=plOrig; stopEdit(); return; }
      if(confirm('Kennzeichen „'+cur+'" speichern?\n\nOK = Speichern · Abbrechen = Verwerfen')){ plSave.click(); }
      else { plate.textContent=plOrig; stopEdit(); }
    });
    plSave.addEventListener('mousedown', function(ev){ ev.preventDefault(); }); // Fokus im Feld halten (kein blur beim Speichern)
    plSave.addEventListener('click', function(ev){
      ev.stopPropagation();
      var v=(plate.textContent||'').replace(/\s+/g,' ').trim();
      if(!v){ setStatus('Bitte ein Kennzeichen eingeben.','err'); return; }
      setStatus('Kennzeichen wird gespeichert …');
      post({action:'plate', plate:v}, function(res){
        if(!res||!res.ok){ setStatus((res&&res.error)||'Fehler.','err'); plate.textContent=plOrig; return; }
        plate.textContent=res.plate; plOrig=res.plate; stopEdit(); parentMsg({rows:1});
        setStatus('✓ Kennzeichen aktualisiert','ok');
      });
    });
  }

  // Zurück-Button: über pointerup steuern (iOS/Standalone verschluckt oft den ERSTEN click -> Tap wirkte tot).
  // Der Kompatibilitäts-click wird per Zeitfenster unterdrückt (zustandslos, kann nicht desynchronisieren).
  // __wab markiert den Button, damit der globale Loader-click-Trigger ihn NICHT zusätzlich auslöst.
  var backLink=$('ci-back');
  if(backLink){
    backLink.__wab=1;
    var ciBackLt=-1;
    function ciBackAct(){
      // Nicht im Hauptslide (Rechnung/Notizen/Fotos/Kontakt) -> erst zurück zur Übersicht.
      var ap=document.querySelector('.pane.active');
      if(ap && ap.getAttribute('data-pane')!=='buchung'){ if(window.ciGoto) window.ciGoto('buchung'); return; }
      // Auf dem Hauptslide: im Overlay schließen (sofort, Liste liegt schon darunter),
      // sonst mit Lade-Overlay dem href zur Anreiseliste folgen.
      if(CFG.embed){ parentMsg({close:1}); return; }
      if(window.waLoad) window.waLoad(backLink.href);
      location.assign(backLink.href);
    }
    backLink.addEventListener('pointerup', function(e){ if(e.button&&e.button!==0) return; ciBackLt=e.timeStamp; e.preventDefault(); ciBackAct(); });
    backLink.addEventListener('click', function(e){ e.preventDefault(); if(ciBackLt>=0 && (e.timeStamp-ciBackLt)<800) return; ciBackAct(); });
  }

  // ---- Rechnungs-Posten (POS) rendern + hinzufügen/entfernen ----
  function posEsc(s){ return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]; }); }
  var posList=$('ci-pos-list'), posPicker=$('ci-pos-picker'), posAddBtn=$('ci-pos-addbtn'), posMsg=$('ci-pos-msg');
  function renderPositions(){
    if(!posList) return;
    var items=CFG.posItems||[], h='';
    for(var i=0;i<items.length;i++){ var it=items[i];
      h+='<div class="rp"><span class="rp-n">'+posEsc(it.title)+(it.qty>1?' <span class="rp-q">× '+it.qty+'</span>':'')+'</span>'
        +'<span class="rp-r"><span class="rp-p">'+eur(it.gross)+'</span>'
        +((CFG.canEditPos && it.removable)?'<button type="button" class="rp-del" data-pos="'+it.id+'" aria-label="Entfernen">&times;</button>':'')
        +'</span></div>';
    }
    h+='<div class="rp rp-total"><span class="rp-n">Gesamt</span><span class="rp-p">'+eur(CFG.amounts[0])+'</span></div>';
    posList.innerHTML=h;
  }
  function renderPicker(){ if(!posPicker) return; var ps=CFG.addProducts||[], h='';
    for(var i=0;i<ps.length;i++){ h+='<button type="button" class="rg-pick" data-add="'+ps[i].id+'"><span class="rg-pick-t">'+posEsc(ps[i].title)+'</span><span class="rg-pick-p">'+eur(ps[i].gross)+'</span></button>'; }
    posPicker.innerHTML=h;
  }
  function posResult(res){ if(!res||!res.ok){ if(posMsg){ posMsg.textContent=(res&&res.error)||'Fehler.'; posMsg.className='status err'; } return false; }
    CFG.posItems=res.positions; CFG.amounts=res.amounts; renderPositions(); renderState(); refreshAmount(); return true; }
  if(posAddBtn){ renderPicker(); posAddBtn.addEventListener('click', function(){ if(posPicker) posPicker.classList.toggle('d-none'); }); }
  if(posPicker) posPicker.addEventListener('click', function(e){ var b=e.target.closest('[data-add]'); if(!b) return; b.disabled=true;
    post({action:'addpos', product_id:b.getAttribute('data-add')}, function(res){ b.disabled=false; if(posResult(res)){ posPicker.classList.add('d-none'); if(posMsg){ posMsg.textContent='✓ Posten hinzugefügt'; posMsg.className='status ok'; } } }); });
  if(posList) posList.addEventListener('click', function(e){ var d=e.target.closest('.rp-del'); if(!d) return; d.disabled=true;
    post({action:'delpos', pos_id:d.getAttribute('data-pos')}, function(res){ if(!posResult(res)) d.disabled=false; }); });
  renderPositions();

  renderState();
  renderTimeRow();

  // Live-Zustandsabgleich: Die App-Shell kann aus dem Service-Worker-Cache stammen und veraltet sein –
  // z. B. wenn das Fahrzeug inzwischen (auch automatisch im Hintergrund) ein-/ausgecheckt oder bezahlt wurde.
  // Ein POST geht am Cache vorbei und liefert den ECHTEN Serverzustand; Button + Zeiten werden korrigiert.
  post({action:'state'}, function(res){
    if(!res || !res.ok) return;
    if(typeof res.arrived==='boolean') CFG.arrived=res.arrived;
    if(typeof res.paid==='boolean')    CFG.paid=res.paid;
    if(typeof res.storno==='boolean')  CFG.storno=res.storno;
    if(res.times){ CFG.times=res.times; }
    timeIdx=null;
    renderState(); renderTimeRow();
    parentMsg({state:ciState()});
  });
})();

/* ============================================================ Fahrzeugfotos */
(function(){
  if(!CFG.imgView && !CFG.imgUpload) return;
  var $=function(id){return document.getElementById(id);};
  var grid=$('ci-ph-grid'), countEl=$('ci-ph-count'), emptyEl=$('ci-ph-empty');
  var API=CFG.imgApi, images=[];
  function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];});}
  function authParams(fd){ fd.append('b',CFG.b); if(CFG.t) fd.append('t',CFG.t); if(CFG.h) fd.append('h',CFG.h); return fd; }
  function listUrl(){ var q='action=list&b='+CFG.b; if(CFG.t)q+='&t='+encodeURIComponent(CFG.t); if(CFG.h)q+='&h='+encodeURIComponent(CFG.h); return API+'?'+q; }

  // ---------- Galerie
  function loadGallery(){
    if(!CFG.imgView){ if(emptyEl) emptyEl.style.display=''; return; }
    fetch(listUrl(),{headers:{'X-Requested-With':'XMLHttpRequest'}}).then(function(r){return r.json();}).then(function(res){
      if(!res||!res.success){ return; }
      images=res.images||[]; renderGrid();
    }).catch(function(){});
  }
  var NOTE_SVG='<svg viewBox="0 0 24 24" fill="none" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>';
  function renderGrid(){
    if(countEl){ countEl.textContent=images.length; countEl.classList.toggle('zero', images.length===0); }
    var tbP=document.getElementById('ci-tb-photos'); if(tbP) tbP.textContent = images.length + (images.length===1?' Foto':' Fotos');
    if(emptyEl) emptyEl.style.display = images.length? 'none':'';
    if(window.ciSliderResize) window.ciSliderResize();
    if(!grid) return;
    var h='';
    for(var i=0;i<images.length;i++){ var im=images[i];
      var mb=(im.marker_count>0)?'<span class="ph-mbadge"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2a7 7 0 0 0-7 7c0 5.2 7 13 7 13s7-7.8 7-13a7 7 0 0 0-7-7zm0 9.5A2.5 2.5 0 1 1 12 6a2.5 2.5 0 0 1 0 5.5z"/></svg>'+im.marker_count+'</span>':'';
      h+='<div class="ph-thumb" data-i="'+i+'"><img src="'+esc(im.thumb_url)+'" loading="lazy" alt="">'+mb+((im.note&&im.note_visible!==false)?'<span class="ph-ndot">'+NOTE_SVG+'</span>':'')+'</div>';
    }
    grid.innerHTML=h;
  }
  if(grid) grid.addEventListener('click', function(e){ var t=e.target.closest('.ph-thumb'); if(t) openLb(parseInt(t.dataset.i,10)); });

  // ---------- Lightbox
  var lb=$('ci-lb'), lbImg=$('ci-lb-img'), lbInfo=$('ci-lb-info'), lbPos=$('ci-lb-pos'),
      lbDel=$('ci-lb-del'), lbDl=$('ci-lb-dl'), lbStage=$('ci-lb-stage');
  var idx=0, scale=1, tx=0, ty=0;
  // Ladespinner über der Bühne (sichtbar beim Durchsliden bis das Bild geladen ist).
  var lbSpin=document.createElement('div'); lbSpin.className='ci-lb-spin'; if(lbStage) lbStage.appendChild(lbSpin);
  function showSpin(){ if(lbSpin) lbSpin.classList.add('on'); }
  function hideSpin(){ if(lbSpin) lbSpin.classList.remove('on'); }
  function applyT(){ lbImg.style.transform='translate('+tx+'px,'+ty+'px) scale('+scale+')'; if(typeof mSync==='function') mSync(); }
  function resetT(){ scale=1; tx=0; ty=0; applyT(); }
  function showInfo(im){
    var meta=[]; meta.push(im.created_at_formatted); if(im.created_by_name) meta.push('von '+esc(im.created_by_name));
    if(CFG.plate) meta.push(esc(CFG.plate)); if(CFG.bookingNo) meta.push(esc(CFG.bookingNo));
    if(im.file_size_formatted) meta.push(im.file_size_formatted+(im.width?' · '+im.width+'×'+im.height:''));
    var note = (im.note&&im.note_visible!==false)? esc(im.note) : '';
    var html='';
    if(note) html+='<div class="lb-note">'+note+'</div>'; else html+='<div class="lb-note" style="opacity:.6;font-weight:400;font-style:italic">Keine Notiz</div>';
    html+='<div class="lb-meta">'+meta.join(' · ')+'</div>';
    if(im.can_edit) html+='<button type="button" class="lb-edit" id="ci-lb-noteedit">Notiz bearbeiten</button>';
    lbInfo.innerHTML=html;
    var ne=$('ci-lb-noteedit'); if(ne) ne.addEventListener('click', function(){ editNote(im); });
    if(lbDel) lbDel.style.display=im.can_delete?'':'none';
    if(lbDl) lbDl.style.display=(im.can_download&&im.original_url)?'':'none';
  }
  function openLb(i){ if(!lb||i<0||i>=images.length) return; idx=i; var im=images[i];
    showSpin(); lbImg.src=im.image_url; resetT(); lbPos.textContent=(i+1)+' / '+images.length; showInfo(im);
    lb.classList.remove('d-none'); document.documentElement.style.overflow='hidden';
    if(typeof mLoad==='function') mLoad();
  }
  function closeLb(){ if(lb){ lb.classList.add('d-none'); lbImg.src=''; document.documentElement.style.overflow='';
    if(typeof mHide==='function') mHide(); mMarkers=[]; if(mLayer) mLayer.innerHTML=''; } }
  function nav(d){ var n=idx+d; if(n<0||n>=images.length) return; openLb(n); }
  if($('ci-lb-x')) $('ci-lb-x').addEventListener('click', closeLb);
  if($('ci-lb-prev')) $('ci-lb-prev').addEventListener('click', function(){ nav(-1); });
  if($('ci-lb-next')) $('ci-lb-next').addEventListener('click', function(){ nav(1); });
  if($('ci-lb-zoom')) $('ci-lb-zoom').addEventListener('click', function(){ scale = scale>1?1:2.5; if(scale===1){tx=0;ty=0;} applyT(); });
  if(lbDl) lbDl.addEventListener('click', function(){ var im=images[idx]; if(im&&im.original_url) window.open(im.original_url,'_blank'); });
  if(lbDel) lbDel.addEventListener('click', function(){ delImage(images[idx]); });

  // Touch: Pinch-Zoom + Pan + Wisch-Navigation + Doppeltipp + Lange-drücken (Marker setzen).
  var pts={}, startDist=0, startScale=1, startTx=0, startTy=0, panX=0, panY=0, lastTap=0, lpTimer=null;
  function dist(a,b){ var dx=a.x-b.x, dy=a.y-b.y; return Math.sqrt(dx*dx+dy*dy); }
  function lpCancel(){ if(lpTimer){ clearTimeout(lpTimer); lpTimer=null; } }
  if(lbStage){
    lbStage.addEventListener('pointerdown', function(e){ pts[e.pointerId]={x:e.clientX,y:e.clientY}; var k=Object.keys(pts);
      if(k.length===1){ panX=e.clientX; panY=e.clientY; startTx=tx; startTy=ty;
        var now=Date.now(); if(now-lastTap<300){ scale=scale>1?1:2.5; if(scale===1){tx=0;ty=0;} applyT(); } lastTap=now;
        // Gedrückt halten -> Marker an dieser Stelle (nur mit Recht, nicht auf einem Marker)
        lpCancel(); if(CFG.imgNote && !(e.target&&e.target.closest&&e.target.closest('.ci-mdot'))){ var lx=e.clientX, ly=e.clientY; lpTimer=setTimeout(function(){ lpTimer=null; addMarkerLP(lx,ly); }, 500); }
      } else if(k.length===2){ lpCancel(); startDist=dist(pts[k[0]],pts[k[1]]); startScale=scale; }
    });
    lbStage.addEventListener('pointermove', function(e){ if(!pts[e.pointerId]) return; pts[e.pointerId]={x:e.clientX,y:e.clientY}; var k=Object.keys(pts);
      if(lpTimer && (Math.abs(e.clientX-panX)>10 || Math.abs(e.clientY-panY)>10)) lpCancel();
      if(k.length===2){ var d=dist(pts[k[0]],pts[k[1]]); if(startDist>0){ scale=Math.min(4,Math.max(1,startScale*d/startDist)); if(scale===1){tx=0;ty=0;} applyT(); } }
      else if(k.length===1 && scale>1){ tx=startTx+(e.clientX-panX); ty=startTy+(e.clientY-panY); applyT(); }
    });
    function up(e){ lpCancel(); var wasPan=(Object.keys(pts).length===1 && scale<=1);
      var sx=pts[e.pointerId]?pts[e.pointerId].x:panX; delete pts[e.pointerId];
      if(wasPan){ var dx=sx-panX; if(Math.abs(dx)>50) nav(dx<0?1:-1); }
      if(Object.keys(pts).length===0 && scale<=1){ tx=0;ty=0;applyT(); }
    }
    lbStage.addEventListener('pointerup', up); lbStage.addEventListener('pointercancel', up);
  }
  // Marker per Lange-drücken setzen: Punkt erfassen, 3× als Zoomstufe speichern, Notiz abfragen.
  function addMarkerLP(cx,cy){
    var im=images[idx]; if(!im) return; var r=lbImg.getBoundingClientRect();
    if(cx<r.left||cx>r.right||cy<r.top||cy>r.bottom) return;
    var rx=(cx-r.left)/r.width, ry=(cy-r.top)/r.height;
    if(navigator.vibrate) try{ navigator.vibrate(30); }catch(_){}
    // Zoom geräteunabhängig: 1,5× der aktuellen mobilen Anzeige (relativ zur Originalauflösung gespeichert).
    var mZoom=(lbImg.offsetWidth&&lbImg.naturalWidth)?(lbImg.offsetWidth*1.5/lbImg.naturalWidth):1.5;
    var v=prompt('Markierung setzen – Notiz (optional):',''); var note=(v===null)?'':v.trim();
    var fd=authParams(new FormData()); fd.append('id',im.id); fd.append('rx',rx.toFixed(5)); fd.append('ry',ry.toFixed(5)); fd.append('zoom',mZoom.toFixed(3)); if(note) fd.append('note',note);
    fetch(API+'?action=marker_add',{method:'POST',body:fd}).then(function(r2){return r2.json();}).then(function(res){
      if(!res||!res.success){ alert((res&&res.error)||'Marker konnte nicht gesetzt werden.'); return; }
      if(!mVisible){ mVisible=true; if(mTog) mTog.classList.add('active'); }
      mMarkers.push(res.marker); mRender();
    }).catch(function(){ alert('Fehler beim Setzen des Markers.'); });
  }

  function editNote(im){ if(!im.can_edit) return; var v=prompt('Notiz zum Bild:', im.note||''); if(v===null) return;
    var fd=authParams(new FormData()); fd.append('id',im.id); fd.append('note',v);
    fetch(API+'?action=note',{method:'POST',body:fd}).then(function(r){return r.json();}).then(function(res){
      if(res&&res.success){ im.note=res.note; showInfo(im); renderGrid(); } else alert((res&&res.error)||'Fehler.');
    }).catch(function(){ alert('Fehler.'); });
  }
  function delImage(im){ if(!im||!im.can_delete) return; if(!confirm('Dieses Foto löschen?')) return;
    var fd=authParams(new FormData()); fd.append('id',im.id);
    fetch(API+'?action=delete',{method:'POST',body:fd}).then(function(r){return r.json();}).then(function(res){
      if(res&&res.success){ closeLb(); loadGallery(); } else alert((res&&res.error)||'Fehler.');
    }).catch(function(){ alert('Fehler.'); });
  }

  // ---------- Upload
  if(CFG.imgUpload){
    var addBtn=$('ci-ph-add'), upModal=$('ci-up-modal'), tiles=$('ci-up-tiles'),
        goBtn=$('ci-up-go'), upErr=$('ci-up-err'), cam=$('ci-up-cam'), gal=$('ci-up-gal');
    var queue=[];   // {file, url, note, el, bar, st, status}
    function openUp(){ queue=[]; tiles.innerHTML=''; goBtn.disabled=true; upErr.classList.add('d-none'); upModal.classList.remove('d-none'); }
    function closeUp(){ upModal.classList.add('d-none'); queue.forEach(function(q){ if(q.url) URL.revokeObjectURL(q.url); }); queue=[]; tiles.innerHTML=''; }
    if(addBtn) addBtn.addEventListener('click', openUp);
    if($('ci-up-close')) $('ci-up-close').addEventListener('click', closeUp);
    if(upModal) upModal.addEventListener('click', function(e){ if(e.target===upModal) closeUp(); });

    function addFiles(fl){
      var MAX=10;
      for(var i=0;i<fl.length;i++){ if(queue.length>=MAX){ upErr.textContent='Maximal '+MAX+' Bilder auf einmal.'; upErr.classList.remove('d-none'); break; }
        var f=fl[i]; if(!/^image\//.test(f.type) && !/\.(heic|heif)$/i.test(f.name||'')) continue;   // HEIC: Typ oft leer -> per Endung zulassen
        addTile(f);
      }
      goBtn.disabled = queue.length===0;
    }
    function addTile(f){
      var url=URL.createObjectURL(f);
      var el=document.createElement('div'); el.className='ph-tile';
      el.innerHTML='<div class="ph-tibox"><img src="'+url+'" alt=""><button type="button" class="ph-rm">&times;</button><div class="ph-st d-none"></div><div class="ph-bar"></div></div>'+
        (CFG.imgNote?'<input type="text" class="ph-tnote" maxlength="500" placeholder="Notiz (optional)">':'');
      tiles.appendChild(el);
      var item={file:f,url:url,note:'',el:el,bar:el.querySelector('.ph-bar'),st:el.querySelector('.ph-st'),status:'wait'};
      var noteInp=el.querySelector('.ph-tnote'); if(noteInp) noteInp.addEventListener('input',function(){ item.note=this.value; });
      el.querySelector('.ph-rm').addEventListener('click',function(){ if(item.status==='up') return; var ix=queue.indexOf(item); if(ix>=0)queue.splice(ix,1); URL.revokeObjectURL(item.url); el.remove(); goBtn.disabled=queue.length===0; });
      queue.push(item);
    }
    if(cam) cam.addEventListener('change',function(){ addFiles(this.files); this.value=''; });
    if(gal) gal.addEventListener('change',function(){ addFiles(this.files); this.value=''; });

    function setSt(item,txt,show){ item.st.textContent=txt||''; item.st.classList.toggle('d-none', !show); }
    function uploadItem(item, done){
      // Original in voller Qualität hochladen – keine Canvas-Umwandlung/Skalierung mehr.
      item.status='up'; setSt(item,'Wird hochgeladen…',true); item.bar.style.width='5%';
      var fd=authParams(new FormData()); fd.append('image',item.file,item.file.name||'photo.jpg'); if(item.note) fd.append('note',item.note);
      var xhr=new XMLHttpRequest(); xhr.open('POST',API+'?action=upload');
      xhr.upload.onprogress=function(e){ if(e.lengthComputable){ item.bar.style.width=Math.max(5,Math.round(e.loaded/e.total*95))+'%'; } };
      xhr.onload=function(){ var res; try{res=JSON.parse(xhr.responseText);}catch(_){res=null;}
        if(res&&res.success){ item.status='done'; item.el.classList.add('done'); item.bar.style.width='100%'; setSt(item,'✓ Fertig',true); done(true); }
        else { item.status='err'; item.el.classList.add('err'); setSt(item,(res&&res.error)||'Fehler',true); done(false); } };
      xhr.onerror=function(){ item.status='err'; item.el.classList.add('err'); setSt(item,'Netzwerkfehler',true); done(false); };
      xhr.send(fd);
    }
    if(goBtn) goBtn.addEventListener('click', function(){
      var pend=queue.filter(function(q){return q.status==='wait';}); if(!pend.length) return;
      goBtn.disabled=true; upErr.classList.add('d-none');
      var i=0, okCount=0;
      (function next(){ if(i>=pend.length){ // fertig
          loadGallery();
          if(okCount===pend.length){ closeUp(); }
          else { upErr.textContent='Einige Bilder konnten nicht hochgeladen werden.'; upErr.classList.remove('d-none'); goBtn.disabled=false; goBtn.textContent='Erneut versuchen'; }
          return; }
        uploadItem(pend[i], function(ok){ if(ok)okCount++; i++; next(); });
      })();
    });
  }

  // ---------- Befund-Marker (mobil: ansehen, ein-/ausblenden; Erstellen nur im CRM mit Lupe) ----------
  var mLayer=null, mMarkers=[], mPrev=null, mVisible=true, mTog=$('ci-lb-mtoggle');
  function mAuthQ(){ var q=''; if(CFG.t) q+='&t='+encodeURIComponent(CFG.t); if(CFG.h) q+='&h='+encodeURIComponent(CFG.h)+'&b='+CFG.b; return q; }
  function mEnsure(){ if(!lbStage) return;
    if(!mLayer){ mLayer=document.createElement('div'); mLayer.className='ci-mlayer'; lbStage.appendChild(mLayer);
      mLayer.addEventListener('click', function(e){ var d=e.target.closest('.ci-mdot'); if(d) mShow(parseInt(d.dataset.i,10), d); }); }
    if(!mPrev){ mPrev=document.createElement('div'); mPrev.className='ci-mprev'; lbStage.appendChild(mPrev); }
  }
  function mHide(){ if(mPrev) mPrev.classList.remove('on'); }
  function mSync(){ if(!mLayer) return; mLayer.style.transform=lbImg.style.transform||''; mLayer.style.setProperty('--mInv', scale?(1/scale):1); }
  function mLoad(){ var im=images[idx]; mMarkers=[]; if(!im||!CFG.imgView){ mRender(); return; }
    fetch(API+'?action=markers&id='+im.id+mAuthQ(),{headers:{'X-Requested-With':'XMLHttpRequest'}}).then(function(r){return r.json();}).then(function(res){ if(res&&res.success){ mMarkers=res.markers||[]; mRender(); } }).catch(function(){});
  }
  function mRender(){ mEnsure(); if(!mLayer) return; mLayer.innerHTML='';
    if(!mVisible){ mLayer.style.display='none'; return; } mLayer.style.display='';
    if(!mMarkers.length) return; var ow=lbImg.offsetWidth, oh=lbImg.offsetHeight; if(ow<2) return;
    mLayer.style.left=lbImg.offsetLeft+'px'; mLayer.style.top=lbImg.offsetTop+'px'; mLayer.style.width=ow+'px'; mLayer.style.height=oh+'px';
    for(var i=0;i<mMarkers.length;i++){ var m=mMarkers[i]; var d=document.createElement('button'); d.type='button'; d.className='ci-mdot'; d.textContent=(i+1); d.style.left=(m.rx*ow)+'px'; d.style.top=(m.ry*oh)+'px'; d.dataset.i=i; mLayer.appendChild(d); }
    mSync();
  }
  function mShow(i, dot){ var m=mMarkers[i], im=images[idx]; if(!m||!im) return; mEnsure();
    var sr=lbStage.getBoundingClientRect(), bw=210, bh=140, Z=m.zoom;
    var nw=lbImg.naturalWidth||lbImg.offsetWidth, nh=lbImg.naturalHeight||lbImg.offsetHeight, bgW=nw*Z, bgH=nh*Z;   // geräteunabhängig (Originalauflösung)
    var zcur=lbImg.offsetWidth?(bgW/lbImg.offsetWidth):Z;
    var note=(m.note&&m.note_visible!==false)?esc(m.note):'';
    var h='<div class="ci-mprev-img" style="background-image:url(\''+im.image_url+'\');background-size:'+bgW+'px '+bgH+'px;background-position:'+(bw/2-m.rx*bgW)+'px '+(bh/2-m.ry*bgH)+'px"></div>';
    h+='<div class="ci-mprev-b"><div class="ci-mprev-z">Zoom '+(Math.round(zcur*10)/10).toString().replace('.',',')+'×</div>';
    h+='<div class="ci-mprev-note">'+(note||'<span class="ci-mprev-empty">Keine Notiz</span>')+'</div>';
    h+='<div class="ci-mprev-meta">'+esc(m.created_at_formatted)+(m.created_by_name?' · '+esc(m.created_by_name):'')+'</div>';
    h+='<div class="ci-mprev-act">'+(m.can_delete?'<button type="button" class="ci-mprev-del">Löschen</button>':'')+'<button type="button" class="ci-mprev-x">&times;</button></div></div>';
    mPrev.innerHTML=h; mPrev.style.width=bw+'px';
    var dr=dot?dot.getBoundingClientRect():null; var dx=dr?(dr.left+dr.width/2-sr.left):sr.width/2, dy=dr?(dr.top+dr.height/2-sr.top):sr.height/2;
    var px=dx+18, py=dy-10; if(px+bw>sr.width) px=dx-bw-18; if(px<0)px=4; if(py+240>sr.height) py=sr.height-240; if(py<0)py=4;
    mPrev.style.left=px+'px'; mPrev.style.top=py+'px'; mPrev.classList.add('on');
    var db=mPrev.querySelector('.ci-mprev-del'); if(db) db.onclick=function(){ mDel(m); };
    mPrev.querySelector('.ci-mprev-x').onclick=mHide;
  }
  function mDel(m){ if(!m.can_delete) return; if(!confirm('Diesen Marker löschen?')) return;
    var fd=authParams(new FormData()); fd.append('mid',m.id);
    fetch(API+'?action=marker_delete',{method:'POST',body:fd}).then(function(r){return r.json();}).then(function(res){ if(res&&res.success){ var ix=mMarkers.indexOf(m); if(ix>=0)mMarkers.splice(ix,1); mHide(); mRender(); } else alert((res&&res.error)||'Fehler.'); }).catch(function(){ alert('Fehler.'); });
  }
  if(lbImg){ lbImg.addEventListener('load', mRender); lbImg.addEventListener('load', hideSpin); lbImg.addEventListener('error', hideSpin); }
  if(mTog) mTog.addEventListener('click', function(){ mVisible=!mVisible; mTog.classList.toggle('active',mVisible); if(!mVisible) mHide(); mRender(); });

  loadGallery();
})();

/* ============================================================ Footer-Tabs + interne Notizen */
(function(){
  var $=function(id){return document.getElementById(id);};
  var tabbar=$('ci-tabbar'); if(!tabbar) return;
  var panes={ buchung:$('pane-buchung'), rechnung:$('pane-rechnung'), notizen:$('pane-notizen'), fotos:$('pane-fotos'), kontakt:$('pane-kontakt') };
  function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];});}

  // ---- Tab-Slider (horizontal swipen, Touch + Maus). Footer-Tabs bleiben synchron.
  var slider=$('ci-slider'), track=$('ci-track');
  var order=['buchung','rechnung','notizen','fotos','kontakt'].filter(function(n){return !!panes[n];});
  var idx=0, notesLoaded=false;
  function paneH(name){ return panes[name]?panes[name].offsetHeight:0; }
  function syncTabs(name){
    var tabs=tabbar.querySelectorAll('.tab'), i;
    for(i=0;i<tabs.length;i++) tabs[i].classList.toggle('active', tabs[i].getAttribute('data-tab')===name);
    for(var k in panes){ if(panes[k]) panes[k].classList.toggle('active', k===name); }
  }
  function setIndex(i, scrollTop){
    i=Math.max(0,Math.min(order.length-1,i)); idx=i; var name=order[i];
    if(track) track.style.transform='translateX(-'+(i*100)+'%)';
    syncTabs(name);
    if(scrollTop && panes[name]) panes[name].scrollTop=0;
    if(name==='notizen' && !notesLoaded){ notesLoaded=true; loadNotes(); }
  }
  function activate(name){ var i=order.indexOf(name); if(i>=0) setIndex(i,true); }
  // App-Shell: Panes scrollen selbst, keine Höhenanpassung nötig (Hook bleibt für Aufrufer ohne Effekt).
  function resizeActive(){}
  window.ciSliderResize=resizeActive;
  window.ciGoto=activate;   // Navigation für Karten/Zurück-Buttons (data-go)
  tabbar.addEventListener('click', function(e){ var b=e.target.closest('.tab'); if(b) activate(b.getAttribute('data-tab')); });
  // Karten (Übersicht) + „Übersicht"-Zurück in den Unterseiten
  document.addEventListener('click', function(e){ var g=e.target.closest('[data-go]'); if(g){ e.preventDefault(); activate(g.getAttribute('data-go')); } });

  // Drag/Swipe via Pointer Events (touch-action:pan-y -> vertikales Scrollen bleibt nativ).
  if(slider && track && order.length>1){
    var sx=0, sy=0, dragging=false, decided=false, didSwipe=false, pid=null;
    slider.addEventListener('pointerdown', function(e){
      if(e.pointerType==='mouse' && e.button!==0) return;
      sx=e.clientX; sy=e.clientY; dragging=true; decided=false; didSwipe=false; pid=e.pointerId;
    });
    slider.addEventListener('pointermove', function(e){
      if(!dragging) return;
      var dx=e.clientX-sx, dy=e.clientY-sy;
      if(!decided){
        if(Math.abs(dx)<7 && Math.abs(dy)<7) return;
        if(Math.abs(dx)<=Math.abs(dy)){ dragging=false; return; }  // vertikal -> nativ scrollen
        decided=true; didSwipe=true;
        track.classList.add('dragging');
        try{ slider.setPointerCapture(pid); }catch(_){}
      }
      var w=slider.offsetWidth||1, pct=dx/w*100, base=-idx*100;
      if((idx===0 && dx>0) || (idx===order.length-1 && dx<0)) pct*=0.35;  // Rand-Widerstand
      track.style.transform='translateX('+(base+pct)+'%)';
      if(e.cancelable) e.preventDefault();
    });
    function endDrag(e){
      if(!dragging) return; dragging=false;
      if(!decided){ return; }
      track.classList.remove('dragging');
      var dx=e.clientX-sx, th=(slider.offsetWidth||1)*0.18, ni=idx;
      if(dx<=-th) ni=idx+1; else if(dx>=th) ni=idx-1;
      setIndex(ni,false);
    }
    slider.addEventListener('pointerup', endDrag);
    slider.addEventListener('pointercancel', function(){ if(dragging&&decided){ track.classList.remove('dragging'); dragging=false; setIndex(idx,false); } else dragging=false; });
    // Klick direkt nach einem Swipe unterdrücken (damit ein Wisch über Button/Thumb nicht auslöst).
    slider.addEventListener('click', function(e){ if(didSwipe){ didSwipe=false; e.stopPropagation(); e.preventDefault(); } }, true);
  }

  // Initiale Badges aus CFG (vor dem ersten Öffnen)
  setNotesBadge(CFG.notesCount||0);
  var pc0=CFG.photosCount||0; var tbP=$('ci-tb-photos'); if(tbP) tbP.textContent = pc0 + (pc0===1?' Foto':' Fotos');

  // ---- Notizen-API
  var API=location.pathname, curNotes=[];
  function npost(params, cb){
    params.b=CFG.b; if(CFG.t)params.t=CFG.t; if(CFG.h)params.h=CFG.h;
    var body=Object.keys(params).map(function(k){return encodeURIComponent(k)+'='+encodeURIComponent(params[k]);}).join('&');
    fetch(API,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded','X-Requested-With':'XMLHttpRequest'},body:body})
      .then(function(r){return r.json();}).then(cb).catch(function(){ cb({ok:false,error:'Netzwerkfehler'}); });
  }
  function setNotesBadge(n){ var tb=$('ci-tb-notes'); if(tb) tb.textContent = n + (n===1?' Notiz':' Notizen'); var c=$('ci-nt-count'); if(c){ c.textContent=n; c.classList.toggle('zero', n===0); } }
  // Hervorgehobene Notizen als Endlos-Lauftext im Datenblatt (Buchung-Tab).
  var mqEl=$('ci-marquee'), mqTrack=$('ci-mq-track');
  function setMarquee(texts){
    if(!mqEl||!mqTrack) return;
    var t=(texts||[]).join('   •••   ');
    if(!t){ mqEl.classList.add('d-none'); mqTrack.innerHTML=''; return; }
    mqEl.classList.remove('d-none');
    mqTrack.innerHTML='<span class="ci-mq-seg">'+esc(t)+'</span><span class="ci-mq-seg" aria-hidden="true">'+esc(t)+'</span>';
    mqTrack.style.animationDuration=Math.max(8, Math.round(t.length*0.28))+'s';
  }
  // Anfangs-Lauftext (server-seitig gerendert) -> Dauer passend setzen.
  if(mqEl && !mqEl.classList.contains('d-none') && mqTrack){ var seg0=mqTrack.querySelector('.ci-mq-seg'); if(seg0) setMarquee([seg0.textContent]); }
  function loadNotes(){ npost({action:'notes'}, function(res){ if(res&&res.ok){ renderNotes(res.notes||[]); setMarquee(res.marquee||[]); } }); }
  function renderNotes(notes){
    curNotes=notes; setNotesBadge(notes.length);
    var list=$('ci-nt-list'), empty=$('ci-nt-empty');
    if(empty) empty.classList.toggle('d-none', notes.length>0);
    if(!list) return;
    var h='';
    for(var i=0;i<notes.length;i++){ var n=notes[i];
      h+='<div class="nt-item'+(n.highlight?' nt-hl':'')+'" data-id="'+n.id+'">'
        +(n.highlight?'<span class="nt-hlbadge">★ Hervorgehoben</span>':'')
        +'<div class="nt-item-text">'+esc(n.text).replace(/\n/g,'<br>')+'</div>'
        +'<div class="nt-item-meta">'+esc(n.user)+' · '+esc(n.created)+(n.updated?' · bearbeitet '+esc(n.updated):'')+'</div>'
        +((n.canEdit||n.canDel)?'<div class="nt-item-act">'
            +(n.canEdit?'<button type="button" class="nt-a nt-edit">Bearbeiten</button>':'')
            +(n.canDel?'<button type="button" class="nt-a nt-del">Löschen</button>':'')
          +'</div>':'')
        +'</div>';
    }
    list.innerHTML=h;
    resizeActive();
  }

  // Neue Notiz
  var saveBtn=$('ci-nt-save'), txt=$('ci-nt-text'), err=$('ci-nt-err'), hlAdd=$('ci-nt-hl');
  function showErr(m){ if(err){ err.textContent=m; err.classList.remove('d-none'); } }
  function hideErr(){ if(err) err.classList.add('d-none'); }
  if(saveBtn){ saveBtn.addEventListener('click', function(){
    var v=(txt.value||'').trim(); hideErr();
    if(!v){ showErr('Bitte einen Text eingeben.'); return; }
    saveBtn.disabled=true;
    npost({action:'note_add', text:v, highlight:(hlAdd&&hlAdd.checked)?1:0}, function(res){ saveBtn.disabled=false;
      if(res&&res.ok){ txt.value=''; if(hlAdd) hlAdd.checked=false; loadNotes(); } else showErr((res&&res.error)||'Fehler beim Speichern.'); });
  }); }

  // Bearbeiten / Löschen (Inline)
  var list=$('ci-nt-list');
  if(list){ list.addEventListener('click', function(e){
    var item=e.target.closest('.nt-item'); if(!item) return; var id=item.getAttribute('data-id');
    if(e.target.closest('.nt-del')){ if(!confirm('Diese Notiz löschen?')) return;
      npost({action:'note_del', id:id}, function(res){ if(res&&res.ok) loadNotes(); else alert((res&&res.error)||'Fehler.'); }); return; }
    if(e.target.closest('.nt-edit')){ startEdit(item, id); return; }
    if(e.target.closest('.nt-ecancel')){ renderNotes(curNotes); return; }
    if(e.target.closest('.nt-esave')){ var ta=item.querySelector('.nt-eta'); var v=(ta.value||'').trim();
      if(!v){ alert('Bitte einen Text eingeben.'); return; }
      var eh=item.querySelector('.nt-ehl');
      npost({action:'note_edit', id:id, text:v, highlight:(eh&&eh.checked)?1:0}, function(res){ if(res&&res.ok) loadNotes(); else alert((res&&res.error)||'Fehler.'); }); return; }
  }); }
  function startEdit(item, id){
    var n=null; for(var i=0;i<curNotes.length;i++) if(String(curNotes[i].id)===String(id)) n=curNotes[i];
    if(!n) return;
    item.innerHTML='<textarea class="nt-eta" rows="3" maxlength="4000"></textarea>'
      +'<label class="nt-hl-opt"><input type="checkbox" class="nt-ehl"'+(n.highlight?' checked':'')+'> <span>Hervorheben – Lauftext im Datenblatt</span></label>'
      +'<div class="nt-eactions"><button type="button" class="nt-a nt-ecancel">Abbrechen</button><button type="button" class="nt-a nt-esave">Speichern</button></div>';
    var ta=item.querySelector('.nt-eta'); ta.value=n.text; ta.focus(); resizeActive();
  }
})();

/* ============================================================ Kennzeichen-Herkunft (Modal + lokale DB) */
(function(){
  var $=function(id){return document.getElementById(id);};
  var btn=$('ci-plate-info'), modal=$('ci-plate-modal'), body=$('ci-pl-body'), plate=$('ci-plate');
  if(!btn || !modal) return;
  function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];});}
  function open(){ modal.classList.remove('d-none'); }
  function close(){ modal.classList.add('d-none'); }
  function row(l,v){ return '<div class="pl-row"><span>'+esc(l)+'</span><b>'+esc(v)+'</b></div>'; }
  function render(d){
    if(!d || !d.found){ body.innerHTML='<div class="pl-empty">Zu diesem Kennzeichen liegen keine Daten vor.</div>'; return; }
    var title = d.ort || d.region;
    var h='<div class="pl-head"><span class="pl-code">'+esc(d.code)+'</span>'
        +'<div class="pl-htxt"><div class="pl-region">'+esc(title)+'</div>'
        +'<div class="pl-sub">'+esc(d.state)+' · '+esc(d.countryName)+'</div></div></div>';
    h+='<div class="pl-rows">';
    if(d.ort && d.region.indexOf(d.ort)===0) h+=row('Stadt', d.ort);          // kreisfreie Stadt ("Wuppertal, Stadt")
    else if(d.ort)                           h+=row('Ort', d.ort)+row('Landkreis', d.region);
    else                                     h+=row(d.regionLabel, d.region);
    h+=row(d.stateLabel, d.state)+row('Land', d.countryName)+'</div>';
    if(d.alts && d.alts.length){
      h+='<div class="pl-alt"><span>auch:</span> '+d.alts.map(function(a){ return esc(a.region)+' ('+esc(a.country)+')'; }).join(', ')+'</div>';
    }
    if(d.map) h+='<div class="pl-mapwrap">'+d.map+'</div>';
    body.innerHTML=h;
  }
  btn.addEventListener('click', function(e){
    e.preventDefault(); e.stopPropagation();
    body.innerHTML='<div class="pl-empty">Lädt …</div>'; open();
    var cur=plate?plate.textContent.trim():'';
    var p='action=plateinfo&b='+CFG.b+'&plate='+encodeURIComponent(cur);
    if(CFG.t)p+='&t='+encodeURIComponent(CFG.t); if(CFG.h)p+='&h='+encodeURIComponent(CFG.h);
    fetch(location.pathname,{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded','X-Requested-With':'XMLHttpRequest'},body:p})
      .then(function(r){return r.json();}).then(render).catch(function(){ body.innerHTML='<div class="pl-empty">Fehler beim Laden.</div>'; });
  });
  if($('ci-pl-close')) $('ci-pl-close').addEventListener('click', close);
  modal.addEventListener('click', function(e){ if(e.target===modal) close(); });
  document.addEventListener('keydown', function(e){ if(e.key==='Escape' && !modal.classList.contains('d-none')) close(); });
})();
</script>
<?php /* „Zum Startbildschirm hinzufügen"-Anleitung nur bei direktem Token-Aufruf (nicht eingebettet, nicht im CRM). */
if ( $tok !== '' && ! $embed ) echo pacim_webapp_install_html( 'Seaside Parking', SITE_URL . '/assets/webapp/icon-192.png' );
/* Service Worker (Offline-Navigation) + Offline-Banner – nur eigenständig, nicht im eingebetteten iframe
   (dort zeigt die Eltern-Ansicht das Banner bereits). */
if ( ! $embed && $tok !== '' && function_exists( 'pacim_webapp_offline_html' ) ) echo pacim_webapp_offline_html();
/* Team-Chat-Overlay – nur eigenständig (nicht im CRM-iframe). $bid aktiviert „Diese Buchung senden";
   lift hebt den Button über die Footer-Tabbar. */
if ( ! $embed && function_exists( 'pacim_chat_widget_html' ) ) echo pacim_chat_widget_html( $tok, '', (int) $bid, 76 );
// Lade-Overlay (Feedback beim Zurück-Navigieren zur Anreiseliste) – nur eigenständig, nicht im Overlay-iframe.
if ( ! $embed && function_exists( 'pacim_webapp_loader_html' ) ) echo pacim_webapp_loader_html(); ?>
</body></html>
<?php
exit();

/* ----------------------------------------- Helfer (HTML/Fehler/CSS) */
function ci_error_page( $code = 403, $msg = 'Dieser Link ist ungültig oder abgelaufen.' ) {
    http_response_code( $code );
    header( 'Content-Type: text/html; charset=utf-8' );
    echo '<!DOCTYPE html><html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">'
       . '<title>' . $code . '</title><style>' . ci_css() . '</style></head><body>'
       . '<div class="err"><h1>' . $code . '</h1><p>' . htmlspecialchars( $msg, ENT_QUOTES, 'UTF-8' ) . '</p></div></body></html>';
    exit();
}
function ci_css() {
    return '
    :root{--navy:#0d2c44;--navy2:#11436b;--blue:#1b6ca8;--green:#2fb344;--red:#d63939;--gold:#f3b53f;--ink:#16384f;--muted:#5b7488;--line:#e6eef4;}
    *{box-sizing:border-box;margin:0;padding:0;}
    html{-webkit-text-size-adjust:100%;text-size-adjust:100%;height:100%;}
    /* App-Shell: volle Viewporthöhe, kein Body-Scroll – gescrollt wird der Inhaltsbereich (Pane). */
    /* position:fixed/inset:0 statt height:100dvh – auf iOS füllt das zuverlässig den echten Viewport
       (100dvh resolved dort teils kürzer → Tableiste schwebte über dem unteren Rand). */
    body{font-family:-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;background:#fff;color:var(--ink);position:fixed;inset:0;display:flex;flex-direction:column;align-items:center;padding:0;overflow:hidden;touch-action:manipulation;}
    /* iOS Standalone: Karte nicht unter Dynamic Island / Statusleiste (nur direkt geöffnet, nicht im Overlay). */
    /* Randlos: Safe-Area oben übernimmt der Karten-Header (.hd) selbst; seitlich nur Notch-Schutz (Querformat). */
    body.ci-sa{padding-left:env(safe-area-inset-left,0px);padding-right:env(safe-area-inset-right,0px);}
    .d-none{display:none!important;}
    .card{width:100%;max-width:460px;background:#fff;overflow:hidden;flex:1 1 auto;min-height:0;display:flex;flex-direction:column;}
    .card>.hd{flex:0 0 auto;}
    .hd{position:relative;background:linear-gradient(135deg,var(--navy) 0%,var(--navy2) 65%,var(--blue) 130%);color:#fff;padding:20px 22px 18px;}
    /* Standalone (vom Home-Bildschirm): Navy-Header füllt randlos bis oben, Inhalt unter die Dynamic Island. */
    body.ci-sa .hd{padding-top:calc(20px + env(safe-area-inset-top,0px));}
    .hd .back{position:absolute;top:14px;right:16px;width:38px;height:38px;display:flex;align-items:center;justify-content:center;border-radius:50%;background:rgba(255,255,255,.14);color:#fff;text-decoration:none;-webkit-tap-highlight-color:transparent;transition:background .12s ease,transform .08s ease;}
    .hd .back:active{transform:scale(.92);background:rgba(255,255,255,.24);}
    .hd .back svg{width:22px;height:22px;}
    body.ci-sa .hd .back{top:calc(14px + env(safe-area-inset-top,0px));right:calc(16px + env(safe-area-inset-right,0px));}
    .hd .k{font-size:11px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;opacity:.8;}
    .hd .t{font-size:22px;font-weight:800;margin-top:3px;}
    .hd .no{font-size:13px;opacity:.85;margin-top:2px;}
    .bd{padding:14px 16px 4px;}
    /* Kachel-Layout (Datenblatt) */
    .bd .kachel{background:#f6f9fc;border:1px solid var(--line);border-radius:14px;padding:11px 14px;}
    .bd .k-l{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin-bottom:3px;}
    .bd .k-v{font-size:16px;font-weight:800;color:var(--navy);line-height:1.2;word-break:break-word;}
    .bd .k-v .muted{font-size:12px;font-weight:600;}
    .bd .k-sub{font-size:11.5px;font-weight:600;color:var(--muted);margin-top:2px;}
    /* Zeit-Kachel: antippbar zum Umschalten der Zeiten (Boarding/Check-in/Check-out) */
    .bd .kachel.k-tap{cursor:pointer;-webkit-tap-highlight-color:transparent;}
    .bd .kachel.k-tap:active{background:#eef4f9;}
    .bd .k-l{display:flex;align-items:center;gap:5px;}
    .bd .k-cyc{width:12px;height:12px;color:var(--blue);opacity:.75;flex:0 0 auto;}
    .bd .k-plate{margin-bottom:10px;padding:11px 14px 14px;overflow:visible;}
    .bd .k-plate .plate-wrap{margin:5px 0 0;}
    .bd .k-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;}
    .plate-wrap{position:relative;margin:2px 0 14px;}
    .plate{border:2px solid #111;border-radius:8px;text-align:center;font-family:"EuroPlate",ui-monospace,monospace;font-size:30px;font-weight:700;letter-spacing:1px;padding:9px 6px;color:#111;cursor:text;-webkit-text-stroke:.6px currentColor;}
    .plate.editing{outline:2px solid var(--blue);outline-offset:1px;padding-right:64px;text-align:left;padding-left:14px;}
    .plate-save{position:absolute;right:6px;top:6px;bottom:6px;width:50px;border:0;border-radius:6px;background:var(--green);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;}
    .plate-save svg{width:22px;height:22px;}
    /* Info-Button (Kennzeichen-Herkunft) als Badge an der Plaketten-Ecke */
    .plate-info{position:absolute;top:-14px;right:-14px;width:44px;height:44px;border-radius:50%;background:#fff;color:var(--blue);
        border:2px solid var(--blue);box-shadow:0 3px 10px rgba(13,44,68,.26);display:flex;align-items:center;justify-content:center;cursor:pointer;z-index:3;-webkit-tap-highlight-color:transparent;}
    .plate-info svg{width:26px;height:26px;}
    .plate-info:active{transform:scale(.92);}
    .plate.editing ~ .plate-info{display:none;}
    /* Kennzeichen-Herkunft-Modal */
    .pl-head{display:flex;align-items:center;gap:14px;margin-bottom:14px;}
    .pl-code{flex:0 0 auto;font-family:"EuroPlate",ui-monospace,monospace;font-weight:700;font-size:26px;letter-spacing:1px;
        border:2px solid #111;border-radius:7px;padding:6px 12px;color:#111;-webkit-text-stroke:.5px currentColor;background:#fff;}
    .pl-region{font-size:18px;font-weight:800;color:var(--navy);line-height:1.2;}
    .pl-sub{font-size:13px;color:var(--muted);margin-top:2px;}
    .pl-rows{border-top:1px solid var(--line);margin-bottom:14px;}
    .pl-row{display:flex;justify-content:space-between;gap:12px;padding:9px 0;border-bottom:1px solid var(--line);font-size:14.5px;}
    .pl-row span{color:var(--muted);} .pl-row b{color:var(--ink);text-align:right;}
    .pl-alt{background:#fff8e6;border:1px solid #f3e3b0;border-radius:10px;padding:9px 12px;font-size:13px;color:#8a6400;margin-bottom:14px;}
    .pl-alt span{font-weight:800;}
    .pl-mapwrap{background:#eef3f7;border-radius:12px;padding:10px;display:flex;justify-content:center;}
    .pl-map{width:auto;max-width:100%;max-height:46vh;height:auto;display:block;}
    .pl-reg{fill:#fff;stroke:#9fb3c4;stroke-width:.8;}
    .pl-reg.active{fill:#1b6ca8;stroke:#0d2c44;stroke-width:1;}
    .pl-mark-halo{fill:rgba(214,57,57,.25);}
    .pl-pin{fill:#d63939;stroke:#fff;stroke-width:1.2;}
    .pl-pin-dot{fill:#fff;}
    .pl-empty{color:var(--muted);text-align:center;padding:24px 6px;font-size:14.5px;}
    .muted{color:var(--muted);font-weight:400;}
    .row{display:flex;justify-content:space-between;gap:12px;padding:9px 0;border-top:1px solid var(--line);font-size:14.5px;}
    .row:first-of-type{border-top:0;}
    .row .l{color:var(--muted);} .row .v{font-weight:600;text-align:right;}
    .row .v .v-sub{display:block;font-size:12px;font-weight:500;color:var(--muted);margin-top:1px;}
    .badges{display:flex;gap:8px;margin:14px 0 2px;}
    .badge{flex:1;border-radius:12px;padding:11px;text-align:center;font-weight:700;font-size:14px;}
    .b-open{background:rgba(214,57,57,.10);color:var(--red);} .b-ok{background:rgba(47,179,68,.12);color:#1d7a33;} .b-warn{background:rgba(243,181,63,.16);color:#8a6400;}
    /* Status-Badge im Header (ganz unten rechts, flush in der Ecke) */
    .hstat{position:absolute;right:0;bottom:0;font-size:16.25px;font-weight:700;padding:8px 16px;border-radius:8px 0 0 0;white-space:nowrap;}
    .hstat.s-blue{background:#2f86d6;color:#fff;} .hstat.s-orange{background:#f59f00;color:#3a2c00;}
    .hstat.s-green{background:#2fb344;color:#fff;} .hstat.s-red{background:#d63939;color:#fff;}
    .btn.btn-out{background:#eef4f9;color:var(--navy);}
    .btn-cancel{margin-top:22px;background:#fff;color:var(--navy);border:0;border-radius:12px;padding:15px 28px;font-size:16px;font-weight:700;cursor:pointer;}
    .done-skip{margin-top:14px;font-size:12.5px;opacity:.7;}
    .sec{padding:14px 22px;border-top:1px solid var(--line);}
    .sec-h{font-weight:700;font-size:15px;color:var(--navy);margin-bottom:10px;}
    .fld-l{display:block;font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin:8px 0 5px;}
    .sel{width:100%;padding:12px;border:1px solid var(--line);border-radius:10px;font-size:15px;background:#fff;color:var(--ink);}
    .amount{margin:12px 0;font-size:15px;}
    .amount strong{font-size:22px;color:var(--navy);}
    .pay-types{display:flex;gap:8px;}
    .pt{flex:1;padding:13px 6px;border:2px solid var(--line);background:#fff;border-radius:12px;font-size:14px;font-weight:700;color:var(--ink);cursor:pointer;}
    .pt.on{border-color:var(--blue);background:rgba(27,108,168,.08);color:var(--blue);}
    .btn{display:block;width:100%;border:0;border-radius:12px;padding:15px;font-size:16px;font-weight:700;text-align:center;cursor:pointer;margin-top:12px;}
    .btn-pay{background:var(--green);color:#fff;} .btn-primary{background:var(--blue);color:#fff;} .btn-light{background:#eef4f9;color:var(--navy);}
    .btn-warn{background:#f59f00;color:#fff;} .btn-checkin{background:var(--green);color:#fff;} .btn-danger{background:var(--red);color:#fff;}
    .btn[disabled]{background:#e7eef4;color:#9bacba;cursor:not-allowed;}
    #ci-action-btn{padding:18px;font-size:17px;border-radius:14px;box-shadow:0 4px 14px rgba(13,44,68,.10);}
    .lnk{background:none;border:0;color:#1d7a33;font-weight:700;text-decoration:underline;cursor:pointer;font-size:13.5px;padding:0;margin-left:4px;}
    .modal{width:100%;max-width:420px;background:#fff;border-radius:0;overflow:hidden;box-shadow:0 20px 60px rgba(0,0,0,.35);}
    .modal-h{display:flex;align-items:center;justify-content:space-between;background:var(--navy);color:#fff;padding:15px 18px;font-size:16px;font-weight:800;}
    .modal-x{background:none;border:0;color:#cfe2f2;font-size:26px;line-height:1;cursor:pointer;padding:0 2px;}
    .modal-b{padding:16px 18px 20px;}
    .hint{font-size:12px;color:var(--muted);text-align:center;margin-top:8px;}
    .modal-err{background:rgba(214,57,57,.10);color:var(--red);border-radius:10px;padding:11px 13px;font-size:13.5px;font-weight:600;margin-top:12px;}
    .note{border-radius:10px;padding:12px 14px;font-size:14px;}
    .note.ok{background:rgba(47,179,68,.10);color:#1d7a33;} .note.warn{background:rgba(243,181,63,.16);color:#8a6400;}
    .note.info{background:rgba(27,108,168,.10);color:#155a8a;}
    .status{padding:6px 22px 16px;font-size:13.5px;font-weight:600;text-align:center;}
    .status.ok{color:#1d7a33;} .status.err{color:var(--red);}
    /* ---- Footer-Tab-Navigation + Panes (App-Shell) ---- */
    /* Slider füllt den Platz zwischen Header und Tab-Footer; jede Pane scrollt vertikal für sich. */
    .slider{position:relative;overflow:hidden;touch-action:pan-y;flex:1 1 auto;min-height:0;}
    .track{display:flex;align-items:stretch;width:100%;height:100%;transition:transform .28s cubic-bezier(.22,.61,.36,1);}
    .track.dragging{transition:none;}
    .pane{flex:0 0 100%;width:100%;min-width:0;height:100%;overflow-y:auto;-webkit-overflow-scrolling:touch;touch-action:pan-y;}
    @media (prefers-reduced-motion: reduce){ .track{transition:none;} }
    /* Bottom-Abstand: Android (kein Home-Indikator → inset≈0) per Basiswert;
       iPhone mit Home-Indikator (inset>0) sitzt knapp darüber (Basis + inset). */
    .tabbar{display:none;}   /* Fuß-Tabs durch Navigations-Karten ersetzt; Element bleibt für die Slider-Engine im DOM */
    /* Fixierter Haupt-Aktions-Button unten (wo vorher die Tabs waren) */
    .actionbar{flex:0 0 auto;width:100%;max-width:460px;background:#fff;border-top:1px solid var(--line);
            box-shadow:0 -2px 14px rgba(13,44,68,.10);padding:8px 16px max(6px, env(safe-area-inset-bottom,0px));}
    .actionbar .btn{margin-top:0;padding:18px;font-size:17px;border-radius:14px;}
    /* Navigations-Karten (Übersicht) */
    .navcards{padding:4px 16px 18px;display:flex;flex-direction:column;gap:10px;}
    .navcard{display:flex;align-items:center;gap:13px;width:100%;background:#fff;border:1px solid var(--line);border-radius:16px;
            padding:13px 14px;cursor:pointer;text-align:left;font:inherit;box-shadow:0 2px 8px rgba(13,44,68,.05);-webkit-tap-highlight-color:transparent;}
    .navcard:active{filter:brightness(.97);}
    .nc-ic{flex:0 0 auto;width:44px;height:44px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;}
    .nc-ic svg{width:23px;height:23px;}
    .ic-green{background:#2fb344;} .ic-blue{background:#1b6ca8;} .ic-amber{background:#f0a82e;} .ic-teal{background:#1c9c8f;}
    .nc-tx{flex:1;min-width:0;display:flex;flex-direction:column;}
    .nc-t{font-size:16px;font-weight:800;color:var(--navy);line-height:1.2;}
    .nc-s{font-size:13px;color:var(--muted);margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
    .nc-chev{flex:0 0 auto;width:20px;height:20px;color:#b6c2cf;}
    .nc-amt{flex:0 0 auto;margin-left:auto;font-weight:900;font-size:21px;white-space:nowrap;letter-spacing:-.3px;}
    .nc-amt.open{color:#d63939;} .nc-amt.paid{color:#2fb344;}
    /* Notizen/Fotos als zwei Tabs nebeneinander */
    .navtabs{display:grid;grid-template-columns:repeat(auto-fit,minmax(0,1fr));gap:10px;}
    .navtab{display:flex;flex-direction:column;align-items:center;gap:6px;background:#fff;border:1px solid var(--line);border-radius:16px;padding:16px 10px;cursor:pointer;font:inherit;box-shadow:0 2px 8px rgba(13,44,68,.05);-webkit-tap-highlight-color:transparent;}
    .navtab:active{filter:brightness(.97);}
    .navtab-t{font-size:15px;font-weight:800;color:var(--navy);}
    .navtab-s{font-size:12.5px;color:var(--muted);}
    /* Unterseiten-Kopf mit Zurück */
    .subhd{display:flex;align-items:center;gap:11px;padding:12px 16px 4px;}
    .subback{display:inline-flex;align-items:center;gap:3px;background:#eef4f9;border:0;border-radius:10px;padding:8px 12px;font:inherit;font-size:13px;font-weight:700;color:var(--navy);cursor:pointer;-webkit-tap-highlight-color:transparent;}
    .subback svg{width:16px;height:16px;}
    .subhd-t{font-size:16px;font-weight:800;color:var(--navy);}
    /* Kontakt-Ansicht */
    .kt-card{background:#fff;border:1px solid var(--line);border-radius:16px;padding:6px 16px;box-shadow:0 2px 8px rgba(13,44,68,.05);}
    .kt-name{font-size:18px;font-weight:800;color:var(--navy);padding:12px 0 4px;}
    .kt-row{display:flex;flex-direction:column;gap:2px;padding:11px 0;border-top:1px solid var(--line);}
    .kt-lbl{font-size:11.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);font-weight:700;}
    .kt-val{font-size:16px;color:var(--navy);word-break:break-word;line-height:1.4;}
    .kt-val a{color:#1b6ca8;text-decoration:none;}
    .kt-call{display:flex;align-items:center;justify-content:center;gap:9px;margin-top:14px;background:#2fb344;color:#fff;border-radius:14px;padding:15px;font-size:17px;font-weight:800;text-decoration:none;box-shadow:0 4px 14px rgba(47,179,68,.28);-webkit-tap-highlight-color:transparent;}
    .kt-call svg{width:22px;height:22px;}
    .kt-call:active{filter:brightness(.95);}
    /* Rechnungsbetrag riesig (rot offen / grün bezahlt) + Posten */
    .rg-box{text-align:center;padding:20px 14px 6px;}
    .rg-amount{font-size:54px;font-weight:900;line-height:1;letter-spacing:-1.5px;}
    .rg-amount.open{color:#d63939;} .rg-amount.paid{color:#2fb344;}
    .rg-status{margin-top:8px;font-size:13px;font-weight:800;text-transform:uppercase;letter-spacing:.06em;}
    .rg-status.open{color:#d63939;} .rg-status.paid{color:#2fb344;}
    .nc-s.open{color:#d63939;font-weight:700;} .nc-s.paid{color:#2fb344;font-weight:700;}
    .rg-pos{margin:8px 16px 0;border:1px solid var(--line);border-radius:14px;overflow:hidden;}
    .rg-pos .rp{display:flex;justify-content:space-between;gap:10px;padding:11px 14px;border-top:1px solid var(--line);font-size:14.5px;}
    .rg-pos .rp:first-child{border-top:0;}
    .rg-pos .rp .rp-n{color:var(--ink);} .rg-pos .rp .rp-q{color:var(--muted);font-weight:400;}
    .rg-pos .rp .rp-p{font-weight:700;white-space:nowrap;color:var(--navy);}
    .rg-pos .rp .rp-r{display:inline-flex;align-items:center;gap:10px;flex:0 0 auto;}
    .rg-pos .rp.rp-total{background:#f6f9fc;font-weight:800;}
    .rp-del{width:26px;height:26px;border:0;border-radius:50%;background:rgba(214,57,57,.12);color:#d63939;font-size:19px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;-webkit-tap-highlight-color:transparent;}
    .rp-del:active{filter:brightness(.92);}
    .rg-addwrap{margin:10px 16px 0;}
    .rg-addbtn{display:flex;align-items:center;justify-content:center;gap:7px;width:100%;background:#eef4f9;border:1px dashed #b6c8d8;border-radius:12px;padding:13px;font:inherit;font-size:15px;font-weight:700;color:var(--blue);cursor:pointer;-webkit-tap-highlight-color:transparent;}
    .rg-addbtn svg{width:18px;height:18px;}
    .rg-picker{margin-top:10px;display:flex;flex-direction:column;gap:8px;}
    .rg-pick{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;background:#fff;border:1px solid var(--line);border-radius:12px;padding:13px 14px;font:inherit;font-size:14.5px;font-weight:600;color:var(--navy);cursor:pointer;text-align:left;-webkit-tap-highlight-color:transparent;}
    .rg-pick:active{filter:brightness(.97);}
    .rg-pick-p{font-weight:800;white-space:nowrap;color:var(--blue);}
    .tabbar .tab{flex:1;border:0;background:none;cursor:pointer;-webkit-tap-highlight-color:transparent;font:inherit;
            display:flex;flex-direction:column;align-items:center;gap:3px;padding:11px 4px 10px;position:relative;
            font-size:11px;font-weight:700;color:var(--muted);transition:color .14s ease;}
    .tabbar .tab svg{width:23px;height:23px;}
    .tabbar .tab.active{color:var(--blue);}
    .tabbar .tab.active::before{content:"";position:absolute;top:0;left:22%;right:22%;height:3px;border-radius:0 0 3px 3px;background:var(--blue);}
    .tabbar .tab:active{background:rgba(27,108,168,.06);}
    .tb-badge{position:absolute;top:5px;left:calc(50% + 7px);min-width:16px;height:16px;padding:0 4px;border-radius:9px;
            background:var(--blue);color:#fff;font-size:10px;font-weight:800;line-height:16px;text-align:center;}
    /* ---- Notizen ---- */
    .nt-hint{margin:14px 22px 0;background:#fff8e6;border:1px solid #f3e3b0;border-radius:12px;padding:12px 14px;}
    .nt-hint-h{display:flex;align-items:center;gap:7px;font-weight:800;font-size:13.5px;color:#8a6400;margin-bottom:6px;}
    .nt-hint-h svg{width:18px;height:18px;flex:0 0 auto;}
    .nt-hint-b{font-size:14px;line-height:1.45;color:var(--ink);word-break:break-word;}
    .nt-hint-reply{margin-top:10px;padding-top:10px;border-top:1px dashed #e6d28f;}
    .nt-hint-rh{font-size:12px;color:#8a6400;font-weight:700;cursor:pointer;list-style:none;display:flex;align-items:center;gap:6px;}
    .nt-hint-reply summary::-webkit-details-marker{display:none;}
    .nt-hint-rh::before{content:"\25B8";display:inline-block;transition:transform .15s ease;}
    .nt-hint-reply[open] .nt-hint-rh::before{transform:rotate(90deg);}
    .nt-hint-rh::after{content:" – anzeigen";font-weight:400;opacity:.8;}
    .nt-hint-reply[open] .nt-hint-rh::after{content:" – ausblenden";}
    .nt-hint-reply .nt-hint-b{margin-top:6px;}
    .nt-block{padding:14px 22px;}
    .nt-title{display:flex;align-items:center;gap:7px;}
    .nt-title svg{width:18px;height:18px;flex:0 0 auto;}
    .nt-add{margin:4px 0 16px;}
    .nt-add textarea{width:100%;border:1px solid var(--line);border-radius:10px;padding:11px 12px;font:inherit;font-size:14.5px;resize:vertical;background:#f6f9fc;}
    .nt-add textarea:focus{outline:2px solid var(--blue);outline-offset:1px;background:#fff;}
    .nt-add .btn{margin-top:10px;}
    .nt-list{display:flex;flex-direction:column;gap:10px;}
    .nt-item{background:#f6f9fc;border:1px solid var(--line);border-radius:12px;padding:11px 13px;}
    .nt-item-text{font-size:14.5px;line-height:1.45;color:var(--ink);word-break:break-word;}
    .nt-item-meta{font-size:11.5px;color:var(--muted);margin-top:7px;}
    .nt-item-act{display:flex;gap:16px;margin-top:9px;}
    .nt-a{border:0;background:none;cursor:pointer;font:inherit;font-size:13px;font-weight:700;color:var(--blue);padding:2px 0;}
    .nt-a.nt-del{color:var(--red);}
    .nt-eta{width:100%;border:1px solid var(--blue);border-radius:10px;padding:10px 12px;font:inherit;font-size:14.5px;resize:vertical;}
    .nt-eactions{display:flex;justify-content:flex-end;gap:18px;margin-top:8px;}
    .nt-empty{color:var(--muted);font-size:14px;text-align:center;padding:18px 0;}
    .nt-foot{color:var(--muted);font-size:12.5px;margin-top:14px;text-align:center;}
    /* Hervorheben-Option + Badge */
    .nt-hl-opt{display:flex;align-items:center;gap:8px;margin:10px 0 0;font-size:13.5px;color:var(--ink);cursor:pointer;}
    .nt-hl-opt input{width:18px;height:18px;flex:0 0 auto;}
    .nt-item.nt-hl{border-color:#f3c34f;background:#fff8e6;}
    .nt-hlbadge{display:inline-block;background:#f3b53f;color:#3a2c00;font-size:11px;font-weight:800;border-radius:6px;padding:2px 8px;margin-bottom:7px;}
    /* Endlos-Lauftext (hervorgehobene Notizen) */
    .ci-marquee{display:flex;align-items:center;gap:8px;background:linear-gradient(90deg,#f3b53f,#ffd87a);color:#3a2c00;padding:8px 12px;font-weight:700;font-size:14px;}
    .ci-marquee .ci-mq-ic{width:18px;height:18px;flex:0 0 auto;}
    .ci-mq-win{flex:1 1 auto;overflow:hidden;}
    .ci-mq-track{display:inline-flex;white-space:nowrap;will-change:transform;animation:ci-mq 18s linear infinite;}
    .ci-mq-seg{padding:0 1.6em;}
    @keyframes ci-mq{from{transform:translateX(0);}to{transform:translateX(-50%);}}
    @media (prefers-reduced-motion: reduce){ .ci-mq-track{animation:none;} }
    .foot{margin-top:16px;font-size:12px;color:var(--muted);text-align:center;}
    .overlay{position:fixed;inset:0;background:rgba(13,44,68,.92);display:flex;align-items:center;justify-content:center;z-index:50;}
    .done-box{text-align:center;color:#fff;padding:30px;}
    .check{width:92px;height:92px;border-radius:50%;background:var(--green);color:#fff;margin:0 auto 18px;display:flex;align-items:center;justify-content:center;}
    .check svg{width:50px;height:50px;}
    /* Check-out-Countdown: deutlich anders (rot/orange) als der Check-in-Countdown (navy/grün) */
    .overlay.out{background:rgba(122,28,28,.94);}
    .overlay.out .check{background:#e8590c;}
    .done-t{font-size:24px;font-weight:800;} .done-x{font-size:15px;opacity:.85;margin-top:8px;}
    .err{max-width:440px;background:#fff;border-radius:18px;padding:34px 26px;text-align:center;box-shadow:0 14px 40px rgba(13,44,68,.16);margin:60px auto;}
    .err h1{font-size:40px;color:var(--navy);} .err p{color:var(--muted);margin-top:8px;}
    /* ---- Fahrzeugfotos ---- */
    .ph-head{display:flex;align-items:center;justify-content:space-between;gap:10px;}
    .ph-title{display:flex;align-items:center;gap:7px;margin:0;} .ph-title svg{width:18px;height:18px;}
    .ph-count{background:var(--blue);color:#fff;border-radius:999px;font-size:12px;font-weight:800;padding:1px 8px;min-width:20px;text-align:center;}
    .ph-count.zero{display:none;}
    .ph-add{display:inline-flex;align-items:center;gap:5px;background:var(--blue);color:#fff;border:0;border-radius:10px;padding:9px 14px;font-size:14px;font-weight:700;cursor:pointer;}
    .ph-add svg{width:16px;height:16px;}
    .ph-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-top:12px;}
    .ph-thumb{position:relative;aspect-ratio:1/1;border-radius:10px;overflow:hidden;background:#e6eef4;cursor:pointer;}
    .ph-thumb img{width:100%;height:100%;object-fit:cover;display:block;}
    .ph-mbadge{position:absolute;left:5px;top:5px;display:inline-flex;align-items:center;gap:1px;background:rgba(214,57,57,.78);color:#fff;border-radius:999px;font-size:11px;font-weight:800;padding:1px 6px 1px 4px;}
    .ph-mbadge svg{width:12px;height:12px;}
    .ph-ndot{position:absolute;right:5px;bottom:5px;width:18px;height:18px;border-radius:50%;background:rgba(13,44,68,.82);display:flex;align-items:center;justify-content:center;}
    .ph-ndot svg{width:11px;height:11px;stroke:#fff;}
    .ph-empty{color:var(--muted);font-size:13.5px;margin-top:10px;}
    .ph-upmodal{max-width:460px;}
    .ph-pick{display:flex;gap:10px;}
    .ph-pickbtn{flex:1;display:flex;flex-direction:column;align-items:center;gap:6px;border:1.5px dashed var(--line);border-radius:12px;padding:18px 8px;cursor:pointer;color:var(--navy);font-weight:700;font-size:14px;}
    .ph-pickbtn svg{width:30px;height:30px;color:var(--blue);} .ph-pickbtn:active{background:#f2f7fb;}
    .ph-tiles{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:14px;}
    .ph-tile{border:1px solid var(--line);border-radius:10px;overflow:hidden;background:#fff;}
    .ph-tibox{position:relative;aspect-ratio:4/3;background:#e6eef4;}
    .ph-tibox img{width:100%;height:100%;object-fit:cover;display:block;}
    .ph-rm{position:absolute;top:4px;right:4px;width:26px;height:26px;border:0;border-radius:50%;background:rgba(13,44,68,.85);color:#fff;font-size:18px;line-height:24px;cursor:pointer;}
    .ph-st{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#fff;font-size:12.5px;font-weight:700;background:rgba(13,44,68,.55);}
    .ph-bar{position:absolute;left:0;bottom:0;height:4px;background:var(--green);width:0;transition:width .15s;}
    .ph-tnote{width:100%;border:0;border-top:1px solid var(--line);padding:8px 9px;font-size:13px;background:#fff;}
    .ph-tile.done .ph-tibox{outline:2px solid var(--green);outline-offset:-2px;}
    .ph-tile.err .ph-tibox{outline:2px solid var(--red);outline-offset:-2px;}
    .lb{background:rgba(8,20,30,.96);}
    .lb-bar{position:absolute;top:0;left:0;right:0;display:flex;align-items:center;justify-content:space-between;padding:10px 12px 26px;z-index:4;color:#fff;background:linear-gradient(180deg,rgba(0,0,0,.62),rgba(0,0,0,0));}
    .lb-pos{font-size:14px;font-weight:800;text-shadow:0 1px 3px rgba(0,0,0,.7);} .lb-actions{display:flex;gap:8px;}
    .lb-btn{background:rgba(0,0,0,.62);color:#fff;border:1px solid rgba(255,255,255,.28);border-radius:10px;min-width:44px;height:44px;font-size:22px;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;box-shadow:0 2px 8px rgba(0,0,0,.4);}
    .lb-btn.active{background:#1b6ca8;border-color:#1b6ca8;}
    .lb-btn svg{width:20px;height:20px;}
    .lb-stage{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;overflow:hidden;touch-action:none;}
    .lb-stage img{max-width:100%;max-height:100%;user-select:none;-webkit-user-drag:none;will-change:transform;}
    .ci-lb-spin{position:absolute;top:50%;left:50%;width:46px;height:46px;margin:-23px 0 0 -23px;border-radius:50%;border:4px solid rgba(255,255,255,.25);border-top-color:#fff;pointer-events:none;z-index:4;opacity:0;transition:opacity .12s ease;}
    .ci-lb-spin.on{opacity:1;animation:ci-lb-spin .8s linear infinite;}
    @keyframes ci-lb-spin{to{transform:rotate(360deg);}}
    @media (prefers-reduced-motion: reduce){ .ci-lb-spin.on{animation:none;} }
    .lb-nav{position:absolute;top:50%;transform:translateY(-50%);background:rgba(0,0,0,.5);color:#fff;border:1px solid rgba(255,255,255,.2);width:44px;height:66px;font-size:30px;cursor:pointer;z-index:4;}
    .lb-prev{left:0;border-radius:0 12px 12px 0;} .lb-next{right:0;border-radius:12px 0 0 12px;}
    .lb-info{position:absolute;left:0;right:0;bottom:0;background:linear-gradient(transparent,rgba(0,0,0,.78));color:#fff;padding:34px 16px 16px;z-index:4;font-size:13px;}
    .lb-info .lb-note{font-size:15px;font-weight:700;margin-bottom:3px;} .lb-info .lb-meta{opacity:.85;line-height:1.5;}
    .lb-info .lb-edit{color:#9fd0ff;text-decoration:underline;background:none;border:0;cursor:pointer;font-size:13px;padding:0;margin-top:5px;}
    /* Befund-Marker (mobil) */
    .ci-mlayer{position:absolute;pointer-events:none;z-index:3;transform-origin:50% 50%;}
    .ci-mdot{position:absolute;transform:translate(-50%,-50%) scale(var(--mInv,1));width:26px;height:26px;border-radius:50%;background:rgba(214,57,57,.92);color:#fff;border:2px solid #fff;font-size:12px;font-weight:800;cursor:pointer;pointer-events:auto;box-shadow:0 2px 8px rgba(0,0,0,.55);display:flex;align-items:center;justify-content:center;}
    .ci-mprev{position:absolute;z-index:7;width:210px;background:#0a141e;border:1px solid rgba(255,255,255,.22);border-radius:10px;overflow:hidden;box-shadow:0 10px 30px rgba(0,0,0,.6);display:none;}
    .ci-mprev.on{display:block;}
    .ci-mprev-img{width:100%;height:140px;background-repeat:no-repeat;background-color:#000;border-bottom:1px solid rgba(255,255,255,.12);}
    .ci-mprev-b{padding:8px 10px;color:#fff;}
    .ci-mprev-z{font-size:11px;font-weight:700;color:#9fd0ff;}
    .ci-mprev-note{font-size:13px;font-weight:600;margin:3px 0;word-break:break-word;} .ci-mprev-empty{opacity:.55;font-style:italic;font-weight:400;}
    .ci-mprev-meta{font-size:11px;opacity:.7;}
    .ci-mprev-act{display:flex;gap:6px;margin-top:6px;}
    .ci-mprev-act button{background:rgba(255,255,255,.16);color:#fff;border:0;border-radius:6px;padding:4px 9px;font-size:12px;cursor:pointer;}
    .ci-mprev-act .ci-mprev-x{margin-left:auto;}
    /* ---- ressourcenarme Animationen: nur opacity/transform, mit Reduced-Motion-Abschaltung ---- */
    @keyframes ciFadeUp{from{opacity:0;transform:translateY(10px);}to{opacity:1;transform:none;}}
    @keyframes ciFade{from{opacity:0;}to{opacity:1;}}
    @keyframes ciSheet{from{opacity:.3;transform:translateY(18px);}to{opacity:1;transform:none;}}
    @keyframes ciPop{0%{opacity:0;transform:scale(.5);}60%{transform:scale(1.08);}100%{opacity:1;transform:none;}}
    .card{animation:ciFadeUp .3s cubic-bezier(.22,.61,.36,1) both;}
    .overlay:not(.d-none){animation:ciFade .18s ease;}
    .modal{animation:ciSheet .24s cubic-bezier(.22,.61,.36,1);}
    .check{animation:ciPop .42s cubic-bezier(.34,1.56,.64,1) both;}
    .hstat{transition:background .25s ease;}
    .btn,.pt,.plate-save,.lnk{transition:transform .08s ease, filter .12s ease;}
    .btn:active,.pt:active,.plate-save:active{transform:scale(.97);}
    @media (prefers-reduced-motion: reduce){
      .card,.overlay,.modal,.check{animation:none!important;}
      .btn:active,.pt:active,.plate-save:active{transform:none;}
    }
    ';
}

Youez - 2016 - github.com/yon3zu
LinuXploit