HEX
Server: Apache/2.4.37 (CentOS Stream) OpenSSL/1.1.1k
System: Linux ysnet.com.tw 4.18.0-553.5.1.el8.x86_64 #1 SMP Tue May 21 05:46:01 UTC 2024 x86_64
User: test (521)
PHP: 7.4.33
Disabled: NONE
Upload Files
File: /var/www/test/Installationlist/form.php
<?php
// ============================================================
// form.php  ─  電子裝機單(第一頁)
// ============================================================
ob_start();          // 捕捉所有意外輸出,防止污染 JSON
session_start();

// ── OTP 寄信 API(fetch 呼叫時 action=send_otp)─────────────
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'send_otp') {
    ob_clean();      // 清掉任何已緩衝輸出
    header('Content-Type: application/json; charset=utf-8');

    $email = trim($_POST['email'] ?? '');
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        echo json_encode(['success' => false, 'message' => '電子信箱格式錯誤']);
        exit;
    }

    // 60 秒冷卻
    $lastSent = $_SESSION['otp_sent_at'] ?? 0;
    if (time() - $lastSent < 60) {
        $remain = 60 - (time() - $lastSent);
        echo json_encode(['success' => false, 'message' => "請等待 {$remain} 秒後再重新發送"]);
        exit;
    }

    // 產生 6 碼驗證碼
    $otp = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
    $_SESSION['otp_code']    = $otp;
    $_SESSION['otp_email']   = $email;
    $_SESSION['otp_expires'] = time() + 300;
    $_SESSION['otp_sent_at'] = time();
    $_SESSION['otp_verified']= false;

    $fromEmail = 'service@ysnet.com.tw';
    $subject   = '亞訊寬頻 ─ 電子信箱驗證碼';
    $headers   = "From: {$fromEmail}\r\nReply-To: {$fromEmail}\r\nContent-Type: text/plain; charset=UTF-8\r\n";
    $message   = "親愛的用戶您好,\n\n您的電子信箱驗證碼為:\n\n    {$otp}\n\n請於 5 分鐘內輸入此驗證碼完成驗證。\n若非本人操作,請忽略此封信件。\n\n亞訊寬頻 客服中心\nservice@ysnet.com.tw";

    if (mail($email, $subject, $message, $headers)) {
        echo json_encode(['success' => true, 'message' => '驗證碼已寄出']);
    } else {
        unset($_SESSION['otp_code'], $_SESSION['otp_email'],
              $_SESSION['otp_expires'], $_SESSION['otp_sent_at']);
        echo json_encode(['success' => false, 'message' => '寄信失敗,請確認信箱或聯繫客服']);
    }
    ob_end_flush();
    exit;
}

// ── OTP 驗證 API(fetch 呼叫時 action=verify_otp)────────────
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'verify_otp') {
    ob_clean();
    header('Content-Type: application/json; charset=utf-8');

    $entered = trim($_POST['otp'] ?? '');
    $email   = trim($_POST['email'] ?? '');

    if (!isset($_SESSION['otp_code']) || time() > ($_SESSION['otp_expires'] ?? 0)) {
        echo json_encode(['success' => false, 'message' => '驗證碼已過期,請重新發送']);
        ob_end_flush(); exit;
    }
    if ($_SESSION['otp_email'] !== $email) {
        echo json_encode(['success' => false, 'message' => '信箱不符,請重新發送驗證碼']);
        ob_end_flush(); exit;
    }
    if ($_SESSION['otp_code'] === $entered) {
        $_SESSION['otp_verified'] = true;
        echo json_encode(['success' => true, 'message' => '驗證成功']);
    } else {
        echo json_encode(['success' => false, 'message' => '驗證碼錯誤']);
    }
    ob_end_flush();
    exit;
}

// ── 正常頁面流程 ───────────────────────────────────────────
// 第一次帶參數進來:存入 Session 後重導向到乾淨 URL
// from_preview=1 時雖然也帶 user_id,但不應覆蓋 Session,直接略過
if (isset($_GET['user_id']) && !isset($_GET['from_preview'])) {
    $user_id = trim($_GET['user_id']);
    $enname  = trim($_GET['ENname']  ?? $_GET['NName'] ?? $_GET['enname'] ?? '');
    $enphone = trim($_GET['ENphone'] ?? '');

    if (empty($user_id)) {
        http_response_code(400);
        die('<p style="color:red;font-size:20px">缺少 user_id 參數</p>');
    }

    // 存入 Session(所有 FileMaker 傳入的欄位)
    $_SESSION['form_user_id']      = $user_id;
    $_SESSION['form_enname']       = $enname;
    $_SESSION['form_name']         = trim($_GET['name']          ?? '');
    $_SESSION['form_member']       = trim($_GET['member']        ?? '');
    $_SESSION['form_speed']        = trim($_GET['speed']         ?? '');
    $_SESSION['form_season']       = trim($_GET['season']        ?? '');
    $_SESSION['form_halfyear']     = trim($_GET['halfyear']      ?? '');
    $_SESSION['form_year']         = trim($_GET['year']          ?? '');
    $_SESSION['form_twoyear']      = trim($_GET['twoyear']       ?? '');
    $_SESSION['form_email']        = trim($_GET['email']         ?? '');
    $_SESSION['form_homenumber']   = trim($_GET['homenumber']    ?? '');
    $_SESSION['form_phone']        = trim($_GET['phone']         ?? '');
    $_SESSION['form_memberaddress']= trim($_GET['memberaddress'] ?? '');
    $_SESSION['form_basenumber']   = trim($_GET['basenumber']    ?? '');
    // PPPoE 或靜態 IP(FileMaker 的 If 條件二選一傳入)
    $_SESSION['form_pppoeid']      = trim($_GET['pppoeid']       ?? '');
    $_SESSION['form_pppoepw']      = trim($_GET['pppoepw']       ?? '');
    $_SESSION['form_ipaddress']    = trim($_GET['ipaddress']     ?? '');
    $_SESSION['form_fistcount']    = trim($_GET['fistcount']     ?? '');
    $_SESSION['form_remark']       = trim($_GET['remark']        ?? '');
    // ENphone(選填)
    $_SESSION['form_enphone']      = trim($_GET['ENphone']       ?? '');

    // endday(到期日,FileMaker 可能未補零,例:2026/6/17 → 需轉為 2026-06-17)
    $enddayRaw = trim($_GET['endday'] ?? '');
    $enddayNormalized = '';
    if ($enddayRaw !== '') {
        // 支援 2026/6/17、2026-6-17、2026/06/17 等格式
        $parts = preg_split('/[\/\-]/', $enddayRaw);
        if (count($parts) === 3) {
            $y = (int)$parts[0];
            $m = (int)$parts[1];
            $d = (int)$parts[2];
            if ($y > 0 && $m >= 1 && $m <= 12 && $d >= 1 && $d <= 31) {
                $enddayNormalized = sprintf('%04d-%02d-%02d', $y, $m, $d);
            }
        }
    }
    $_SESSION['form_endday'] = $enddayNormalized;

    // 重導向到乾淨 URL
    header('Location: form.php');
    exit;
}

$user_id = $_SESSION['form_user_id'] ?? '';
$enname  = $_SESSION['form_enname']  ?? '';
$enphone = $_SESSION['form_enphone'] ?? '';

if (empty($user_id)) {
    // 若 user_id 空但有 from_preview 參數,代表 Session 被清了
    if (!empty($_GET['from_preview'])) {
        die('<p style="color:red;font-size:18px;padding:20px">
            Session 已失效,請重新從 FileMaker 開啟連結<br>
            <small style="color:#888">(長時間無操作或瀏覽器重啟可能導致 Session 遺失)</small>
        </p>');
    }
    http_response_code(400);
    die('<p style="color:red;font-size:20px">請透過正確連結開啟此頁面</p>');
}

// ── 從 Session 讀取 FileMaker 傳入的資料(不再查詢 MySQL)────
$name          = $_SESSION['form_name']          ?? '';
$member        = $_SESSION['form_member']        ?? '';
$speed         = $_SESSION['form_speed']         ?? '';
$season        = $_SESSION['form_season']        ?? '';
$halfyear      = $_SESSION['form_halfyear']      ?? '';
$year          = $_SESSION['form_year']          ?? '';
$twoyear       = $_SESSION['form_twoyear']       ?? '';
$email         = $_SESSION['form_email']         ?? '';
$homenumber    = $_SESSION['form_homenumber']    ?? '';
$phone         = $_SESSION['form_phone']         ?? '';
$memberaddress = $_SESSION['form_memberaddress'] ?? '';
$basenumber    = $_SESSION['form_basenumber']    ?? '';
$pppoeid       = $_SESSION['form_pppoeid']       ?? '';
$pppoepw       = $_SESSION['form_pppoepw']       ?? '';
$ipaddress     = $_SESSION['form_ipaddress']     ?? '';
$fistcount     = $_SESSION['form_fistcount']     ?? '';
$remark        = $_SESSION['form_remark']        ?? '';
$endday        = $_SESSION['form_endday']         ?? '';
// useraddress 已合併到 memberaddress(FileMaker 側做拼接),此處設為空字串
$useraddress   = '';

$isSubscription = (strpos($speed, '定期') !== false) ? 1 : 0;
$plan_options = [
    "season"   => !empty($season)   ? ["price" => (int)$season,   "label" => "季繳"]   : null,
    "halfyear" => !empty($halfyear) ? ["price" => (int)$halfyear, "label" => "半年繳"] : null,
    "year"     => !empty($year)     ? ["price" => (int)$year,     "label" => "一年繳"] : null,
    "twoyear"  => !empty($twoyear)  ? ["price" => (int)$twoyear,  "label" => "兩年繳"] : null,
];

// 今日日期
$todayStr     = (new DateTime())->format('Y-m-d');
$todayDisplay = (new DateTime())->format('Y年m月d日');
?>
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="format-detection" content="telephone=no">
    <title>裝機申請書</title>
    <!-- Tabler Icons -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@3.19.0/dist/tabler-icons.min.css">
    <style>
        /* ── 基礎 reset ── */
        *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
        :root {
            --green:      #1D9E75;
            --green-lt:   #E1F5EE;
            --green-dk:   #0F6E56;
            --green-deep: #085041;
            --amber:      #FAEEDA;
            --amber-txt:  #633806;
            --red:        #E24B4A;
            --red-lt:     #FCEBEB;
            --gray-bg:    #F5F5F5;
            --gray-bd:    rgba(0,0,0,.15);
            --gray-bd2:   rgba(0,0,0,.28);
            --text:       #111;
            --text-sec:   #666;
            --text-tert:  #999;
            --radius-md:  8px;
            --radius-lg:  12px;
        }
        body {
            font-family: "Microsoft JhengHei", "PingFang TC", sans-serif;
            font-size: 15px;
            background: var(--gray-bg);
            color: var(--text);
            padding-bottom: 80px;
        }

        /* ── 頂部 Bar ── */
        .topbar {
            position: sticky; top: 0; z-index: 100;
            background: #fff;
            border-bottom: 0.5px solid var(--gray-bd);
            padding: 11px 16px;
            display: flex; align-items: center; gap: 10px;
        }
        .topbar-id {
            font-size: 13px; color: var(--text-sec);
        }
        .topbar-id span {
            font-family: monospace; font-size: 14px;
            color: var(--text); font-weight: 500;
        }
        .topbar-title {
            flex: 1; text-align: center;
            font-size: 16px; font-weight: 500; color: var(--text);
        }
        .topbar-remark {
            font-size: 13px; padding: 6px 12px;
            border: 0.5px solid var(--gray-bd2);
            border-radius: var(--radius-md);
            background: none; cursor: pointer; color: var(--text-sec);
            font-family: inherit;
        }

        /* ── 步驟 Tab ── */
        .step-tabs {
            background: #fff;
            border-bottom: 0.5px solid var(--gray-bd);
            display: flex; overflow-x: auto;
        }
        .step-tabs::-webkit-scrollbar { display: none; }
        .step-tab {
            flex: 1; min-width: 68px;
            padding: 9px 4px 7px;
            text-align: center; font-size: 12px;
            color: var(--text-sec); cursor: pointer;
            border-bottom: 2px solid transparent;
            transition: all .15s; white-space: nowrap;
        }
        .step-tab .dot {
            width: 6px; height: 6px; border-radius: 50%;
            background: var(--gray-bd2);
            margin: 0 auto 4px; transition: all .15s;
        }
        .step-tab.active { color: var(--green); border-bottom-color: var(--green); }
        .step-tab.active .dot { background: var(--green); }
        .step-tab.done .dot { background: #9FE1CB; }

        /* ── 通用卡片 ── */
        .card {
            background: #fff;
            border-radius: var(--radius-lg);
            border: 0.5px solid var(--gray-bd);
            overflow: hidden;
            margin-bottom: 12px;
        }
        .card-hdr {
            display: flex; align-items: center; gap: 8px;
            padding: 10px 14px;
            border-bottom: 0.5px solid var(--gray-bd);
            background: var(--gray-bg);
        }
        .card-hdr i { font-size: 16px; color: var(--green); }
        .card-hdr-txt { font-size: 13px; font-weight: 500; }
        .card-hdr-sub {
            margin-left: auto; font-size: 11px;
            background: var(--green-lt); color: var(--green-deep);
            padding: 2px 8px; border-radius: 20px;
        }

        /* ── 行 (label + value) ── */
        .field-row {
            display: flex; align-items: stretch;
            border-bottom: 0.5px solid var(--gray-bd);
        }
        .field-row:last-child { border-bottom: none; }
        .field-lbl {
            width: 80px; flex-shrink: 0;
            font-size: 12px; color: var(--text-sec);
            padding: 10px 10px 10px 14px;
            background: var(--gray-bg);
            display: flex; align-items: center; justify-content: center;
            text-align: center; line-height: 1.4;
        }
        .field-val {
            flex: 1; padding: 8px 12px;
            font-size: 14px; color: var(--text);
            display: flex; align-items: center; flex-wrap: wrap; gap: 4px;
        }
        .field-val.readonly { color: var(--text-sec); font-family: monospace; font-size: 13px; }
        .field-val.green { color: var(--green-dk); font-weight: 500; }

        /* ── Input ── */
        .f-input {
            width: 100%; font-size: 15px;
            background: transparent; border: none; outline: none;
            color: var(--text); font-family: inherit;
            padding: 2px 0;
        }
        .f-input::placeholder { color: #bbb; }
        .f-input:focus { outline: none; }
        .hint {
            font-size: 11px; margin-top: 2px; width: 100%;
        }
        .hint-ok  { color: var(--green); }
        .hint-err { color: var(--red); }

        /* ── 設備晶片 ── */
        .chip-grid { display: flex; flex-wrap: wrap; gap: 7px; padding: 12px 14px; }
        .chip {
            padding: 6px 12px; border-radius: 20px;
            font-size: 13px;
            border: 1px solid var(--gray-bd2);
            color: var(--text-sec);
            cursor: pointer; background: #fff;
            display: flex; align-items: center; gap: 4px;
            transition: all .12s;
            user-select: none;
        }
        .chip.selected {
            background: var(--green-lt);
            border-color: var(--green);
            color: var(--green-deep); font-weight: 500;
        }
        .chip i { font-size: 14px; }

        /* ── 借用設備群組 ── */
        .use-group {
            border-bottom: 0.5px solid var(--gray-bd);
            padding: 10px 14px;
        }
        .use-group:last-child { border-bottom: none; }
        .use-group-header {
            display: flex; align-items: center; gap: 8px; margin-bottom: 6px;
        }
        .use-select {
            flex: 1; font-size: 14px; padding: 6px 8px;
            border: 0.5px solid var(--gray-bd2);
            border-radius: var(--radius-md);
            background: #fff; color: var(--text);
            font-family: inherit;
        }
        .use-fields {
            display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
            margin-top: 6px;
        }
        .use-field-box {
            border: 0.5px solid var(--gray-bd2);
            border-radius: var(--radius-md);
            padding: 6px 10px;
        }
        .use-field-lbl { font-size: 11px; color: var(--text-tert); margin-bottom: 2px; }
        .use-field-input {
            width: 100%; font-size: 14px; background: transparent;
            border: none; outline: none; color: var(--text); font-family: inherit;
        }
        .use-field-input::placeholder { color: #ccc; }
        .add-btn {
            font-size: 13px; padding: 7px 14px;
            border: 0.5px solid var(--gray-bd2);
            border-radius: var(--radius-md);
            background: none; cursor: pointer;
            color: var(--text-sec); font-family: inherit;
            margin: 8px 14px;
            display: flex; align-items: center; gap: 4px;
        }
        .add-btn:active { background: var(--gray-bg); }
        .rm-btn {
            width: 26px; height: 26px; border-radius: 50%;
            border: 0.5px solid var(--gray-bd2);
            background: none; cursor: pointer;
            display: flex; align-items: center; justify-content: center;
            color: var(--text-tert); font-size: 16px; flex-shrink: 0;
        }

        /* ── 費率卡片 ── */
        .plan-grid {
            display: grid; grid-template-columns: 1fr 1fr;
            gap: 8px; padding: 12px 14px;
        }
        .plan-card {
            border: 1px solid var(--gray-bd2);
            border-radius: var(--radius-md);
            padding: 10px 12px; cursor: pointer;
            background: #fff; transition: all .12s;
        }
        .plan-card.selected {
            border-color: var(--green);
            background: var(--green-lt);
        }
        .plan-name { font-size: 13px; font-weight: 500; color: var(--text); }
        .plan-price {
            font-size: 18px; font-weight: 500;
            color: var(--green); margin-top: 2px;
        }
        .plan-price span { font-size: 11px; font-weight: 400; color: var(--text-sec); }

        /* 加費項目 */
        .extra-row {
            display: flex; align-items: center; gap: 8px;
            padding: 9px 14px;
            border-bottom: 0.5px solid var(--gray-bd);
        }
        .extra-row:last-child { border-bottom: none; }
        .extra-select {
            flex: 1; font-size: 13px; padding: 6px 8px;
            border: 0.5px solid var(--gray-bd2);
            border-radius: var(--radius-md);
            background: #fff; color: var(--text); font-family: inherit;
        }
        .extra-amount {
            width: 80px; font-size: 14px;
            padding: 6px 8px; text-align: right;
            border: 0.5px solid var(--gray-bd2);
            border-radius: var(--radius-md);
            background: #fff; color: var(--text);
            font-family: inherit;
        }

        /* 總計 & 押金摘要 */
        .total-row {
            display: flex; align-items: center; justify-content: space-between;
            padding: 12px 14px;
        }
        .total-lbl { font-size: 14px; color: var(--text-sec); }
        .total-amt { font-size: 22px; font-weight: 500; color: var(--green); }
        .deposit-row {
            display: flex; align-items: center; justify-content: space-between;
            padding: 8px 14px;
            border-top: 0.5px solid var(--gray-bd);
            font-size: 13px;
        }
        .deposit-lbl { color: var(--text-sec); }
        .deposit-amt { font-weight: 500; color: #BA7517; }

        /* ── 日期 ── */
        .date-row {
            display: flex; align-items: center;
            padding: 10px 14px;
            border-bottom: 0.5px solid var(--gray-bd);
        }
        .date-row:last-child { border-bottom: none; }
        .date-lbl { font-size: 13px; color: var(--text-sec); flex: 1; }
        .date-val {
            font-size: 14px; font-weight: 500; color: var(--text);
        }
        .date-val.accent { color: var(--green-dk); }
        .date-val.big { font-size: 16px; }

        /* 贈送群組 */
        .gift-group {
            display: flex; align-items: center; gap: 8px;
            padding: 9px 14px;
            border-bottom: 0.5px solid var(--gray-bd);
        }
        .gift-group:last-child { border-bottom: none; }
        .gift-select {
            flex: 1; font-size: 14px; padding: 6px 8px;
            border: 0.5px solid var(--gray-bd2);
            border-radius: var(--radius-md);
            background: #fff; color: var(--text); font-family: inherit;
        }
        .gift-qty {
            width: 60px; font-size: 14px; padding: 6px 8px;
            border: 0.5px solid var(--gray-bd2);
            border-radius: var(--radius-md);
            background: #fff; color: var(--text); font-family: inherit;
        }

        /* ── 底部 ── */
        .bottom-bar {
            position: sticky; bottom: 0;
            background: #fff;
            border-top: 0.5px solid var(--gray-bd);
            padding: 10px 16px;
            display: flex; align-items: center; justify-content: space-between;
        }
        .bottom-summary {}
        .bottom-summary-lbl { font-size: 11px; color: var(--text-sec); }
        .bottom-summary-main { font-size: 13px; color: var(--text-sec); margin-top: 1px; }
        .bottom-summary-main span { color: var(--green); font-weight: 500; font-size: 15px; }
        .bottom-nav { display: flex; gap: 8px; }
        .nav-btn {
            padding: 9px 14px;
            border: 0.5px solid var(--gray-bd2);
            border-radius: var(--radius-md);
            background: none; cursor: pointer;
            font-size: 14px; color: var(--text-sec);
            font-family: inherit;
        }
        .nav-btn.primary {
            background: var(--green); border-color: var(--green);
            color: #fff; font-weight: 500;
            padding: 9px 20px;
        }
        .nav-btn:disabled { opacity: .4; cursor: not-allowed; }

        /* ── 步驟頁面 ── */
        .step-page { display: none; padding: 12px 16px 0; }
        .step-page.active { display: block; }

        /* ── 備註 Modal ── */
        .modal-bg {
            display: none;
            position: fixed; inset: 0; z-index: 9999;
            background: rgba(0,0,0,.35);
            align-items: center; justify-content: center;
        }
        .modal-bg.open { display: flex; }
        .modal-box {
            background: #fff; border-radius: var(--radius-lg);
            padding: 20px; width: 88vw; max-width: 360px;
            position: relative;
        }
        .modal-title { font-size: 15px; font-weight: 500; margin-bottom: 12px; }
        .modal-close {
            position: absolute; top: 10px; right: 12px;
            font-size: 22px; background: none; border: none;
            cursor: pointer; color: var(--text-sec);
            line-height: 1;
        }
        .modal-content {
            font-size: 14px; line-height: 1.8;
            color: var(--text-sec); white-space: pre-line;
        }

        /* 信箱驗證 modal */
        .otp-box {
            border: 0.5px solid var(--gray-bd2);
            border-radius: var(--radius-md);
            padding: 12px; margin-top: 12px;
            background: #F9FFF9;
        }
        .otp-input {
            width: 100%; font-size: 22px; letter-spacing: 8px;
            text-align: center; padding: 10px;
            border: 1.5px solid var(--gray-bd2);
            border-radius: var(--radius-md); margin: 8px 0;
            font-family: monospace;
        }
        .otp-btn {
            width: 100%; padding: 10px;
            background: var(--green); color: #fff;
            border: none; border-radius: var(--radius-md);
            font-size: 14px; cursor: pointer; font-family: inherit;
        }
        .otp-resend {
            text-align: center; font-size: 12px;
            color: var(--green); margin-top: 8px;
            cursor: pointer;
        }

        /* 警示列 */
        .warn-bar {
            background: var(--amber); border-radius: var(--radius-md);
            padding: 10px 14px; display: flex; gap: 8px;
            font-size: 12px; color: var(--amber-txt);
            margin-bottom: 12px; align-items: flex-start;
        }
        .warn-bar i { font-size: 15px; flex-shrink: 0; }

        /* 首裝優惠顯示 */
        .fistcount-box {
            padding: 10px 14px;
            font-size: 13px; color: var(--text-sec); line-height: 1.7;
        }
    </style>
</head>
<body>

<?php if (!empty($dbError)): ?>
<div style="background:#FCEBEB;color:#A32D2D;padding:12px 16px;font-size:14px">
    <i class="ti ti-alert-circle"></i> <?= htmlspecialchars($dbError) ?>
</div>
<?php endif; ?>

<!-- ── 頂部 Bar ── -->
<div class="topbar">
    <button class="topbar-remark" id="remark-btn" type="button">
        <i class="ti ti-notes"></i> 備註
    </button>
    <div class="topbar-title">裝機申請書</div>
    <div style="text-align:right;line-height:1.5">
        <div style="font-size:11px;color:var(--text-sec)">帳號 <span style="font-family:monospace;font-size:12px;color:var(--text)"><?= htmlspecialchars($pppoeid) ?></span></div>
        <div style="font-size:11px;color:var(--text-sec)">密碼 <span style="font-family:monospace;font-size:12px;color:var(--text)"><?= htmlspecialchars($pppoepw) ?></span></div>
    </div>
</div>

<!-- ── 步驟 Tab ── -->
<div class="step-tabs" id="step-tabs">
    <div class="step-tab active" onclick="goStep(0)"><div class="dot"></div>申請人</div>
    <div class="step-tab" onclick="goStep(1)"><div class="dot"></div>設備</div>
    <div class="step-tab" onclick="goStep(2)"><div class="dot"></div>費率</div>
    <div class="step-tab" onclick="goStep(3)"><div class="dot"></div>日期</div>
</div>

<!-- ════════════════════════════════════════
     STEP 0 ─ 申請人資訊
═════════════════════════════════════════ -->
<div class="step-page active" id="page-0">

    <!-- 系統帶入 -->
    <div class="card">
        <div class="card-hdr">
            <i class="ti ti-database"></i>
            <span class="card-hdr-txt">系統帶入資料</span>
            <span class="card-hdr-sub">自動填入</span>
        </div>
        <div class="field-row">
            <div class="field-lbl">社區</div>
            <div class="field-val readonly"><?= htmlspecialchars($member) ?></div>
        </div>
        <div class="field-row">
            <div class="field-lbl">裝機地址</div>
            <div class="field-val readonly" style="font-size:13px">
                <?= htmlspecialchars($memberaddress) ?>
                <?= htmlspecialchars($useraddress) ?>
            </div>
        </div>
        <div class="field-row">
            <div class="field-lbl">申請速率</div>
            <div class="field-val green"><?= htmlspecialchars($speed) ?> Mbps</div>
        </div>
        <div class="field-row">
            <div class="field-lbl">據點</div>
            <div class="field-val readonly"><?= htmlspecialchars($basenumber) ?></div>
        </div>
        <div class="field-row">
            <div class="field-lbl">PPPoE<br>帳號</div>
            <div class="field-val readonly" style="font-family:monospace;font-size:13px">
                <?= htmlspecialchars($pppoeid) ?>
            </div>
        </div>
        <div class="field-row">
            <div class="field-lbl">PPPoE<br>密碼</div>
            <div class="field-val readonly" style="font-family:monospace;font-size:13px">
                <?= htmlspecialchars($pppoepw) ?>
            </div>
        </div>
        <?php if (!empty($fistcount)): ?>
        <div class="field-row">
            <div class="field-lbl">首裝優惠</div>
            <div class="field-val">
                <div class="fistcount-box" style="padding:0">
                    <?= nl2br(htmlspecialchars($fistcount)) ?>
                </div>
            </div>
        </div>
        <?php endif; ?>
    </div>

    <!-- 申請人填寫 -->
    <div class="card">
        <div class="card-hdr">
            <i class="ti ti-user"></i>
            <span class="card-hdr-txt">申請人填寫</span>
        </div>
        <div class="field-row">
            <div class="field-lbl">姓名</div>
            <div class="field-val">
                <input class="f-input" type="text" id="applicant_name" name="applicant_name"
                    value="<?= htmlspecialchars($name) ?>" placeholder="請輸入姓名">
            </div>
        </div>
        <div class="field-row">
            <div class="field-lbl">證件類型</div>
            <div class="field-val">
                <select id="id-type" style="font-size:14px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);padding:5px 8px;background:#fff;font-family:inherit;width:100%">
                    <option value="twid">身分證字號</option>
                    <option value="taxid">統一編號</option>
                    <option value="arc">舊式居留證</option>
                </select>
            </div>
        </div>
        <div class="field-row">
            <div class="field-lbl">證件號碼</div>
            <div class="field-val" style="flex-direction:column;align-items:flex-start">
                <input class="f-input" type="text" id="applicant_usernumber" name="applicant_usernumber"
                    placeholder="請輸入證件號碼" maxlength="10"
                    inputmode="latin" autocomplete="off">
                <div class="hint" id="twid-hint"></div>
            </div>
        </div>
        <div class="field-row">
            <div class="field-lbl">行動電話</div>
            <div class="field-val" style="flex-direction:column;align-items:flex-start">
                <input class="f-input" type="tel" id="applicant_phone" name="applicant_phone"
                    value="<?= htmlspecialchars($phone) ?>" placeholder="0912-345-678" maxlength="12">
                <div class="hint" id="phone-hint"></div>
            </div>
        </div>
        <div class="field-row">
            <div class="field-lbl">住宅電話</div>
            <div class="field-val" style="flex-direction:column;align-items:flex-start">
                <input class="f-input" type="tel" id="applicant_homenumber" name="applicant_homenumber"
                    value="<?= htmlspecialchars($homenumber) ?>" placeholder="02-xxxxxxxx" maxlength="12">
                <div class="hint" id="home-hint"></div>
            </div>
        </div>
        <div class="field-row" id="email-row">
            <div class="field-lbl">電子信箱</div>
            <div class="field-val" style="flex-direction:column;align-items:flex-start">
                <div style="display:flex;width:100%;gap:6px;align-items:center">
                    <input class="f-input" type="email" id="applicant_email" name="applicant_email"
                        value="<?= htmlspecialchars($email) ?>"
                        placeholder="example@mail.com" style="flex:1">
                    <button type="button" id="send-otp-btn"
                        style="font-size:12px;padding:5px 10px;border:0.5px solid var(--green);border-radius:var(--radius-md);background:none;cursor:pointer;color:var(--green);white-space:nowrap;font-family:inherit">
                        發送驗證
                    </button>
                </div>
                <div class="hint" id="email-hint"></div>
                <!-- OTP 輸入區 (預設隱藏) -->
                <div class="otp-box" id="otp-box" style="display:none;width:100%">
                    <div style="font-size:12px;color:var(--text-sec)">
                        驗證碼已寄送至 <strong id="otp-target-email"></strong>,請於 5 分鐘內輸入
                    </div>
                    <input class="otp-input" type="text" id="otp-input"
                        maxlength="6" placeholder="000000" inputmode="numeric">
                    <button class="otp-btn" type="button" onclick="verifyOTP()">確認驗證碼</button>
                    <div style="display:flex;gap:8px;margin-top:8px">
                        <div class="otp-resend" id="otp-resend" onclick="sendOTP(true)" style="flex:1">重新發送</div>
                        <div style="flex:1;text-align:center;font-size:12px;color:var(--red);cursor:pointer"
                            onclick="cancelOTP()">取消重新填寫</div>
                    </div>
                </div>
            </div>
        </div>
    </div>

</div><!-- /page-0 -->

<!-- ════════════════════════════════════════
     STEP 1 ─ 設備資訊
═════════════════════════════════════════ -->
<div class="step-page" id="page-1">

    <!-- 室內資訊箱設備 -->
    <div class="card">
        <div class="card-hdr">
            <i class="ti ti-server"></i>
            <span class="card-hdr-txt">室內資訊箱設備</span>
        </div>
        <div id="info-container"></div>
        <button type="button" class="add-btn" id="add-info-btn" onclick="addInfoGroup()">
            <i class="ti ti-plus" style="font-size:14px"></i> 新增設備
        </button>
    </div>

    <!-- 室內網路設備 -->
    <div class="card">
        <div class="card-hdr">
            <i class="ti ti-wifi"></i>
            <span class="card-hdr-txt">室內網路設備</span>
        </div>
        <div id="router-container"></div>
        <button type="button" class="add-btn" id="add-router-btn" onclick="addRouterGroup()">
            <i class="ti ti-plus" style="font-size:14px"></i> 新增設備
        </button>
    </div>

    <!-- 使用設備 -->
    <div class="card">
        <div class="card-hdr">
            <i class="ti ti-package"></i>
            <span class="card-hdr-txt">使用設備</span>
        </div>
        <div id="use-container"></div>
        <button type="button" class="add-btn" id="add-use-btn" onclick="addUseGroup()">
            <i class="ti ti-plus" style="font-size:14px"></i> 新增設備
        </button>
    </div>

</div><!-- /page-1 -->

<!-- ════════════════════════════════════════
     STEP 2 ─ 費率
═════════════════════════════════════════ -->
<div class="step-page" id="page-2">

    <div class="card">
        <div class="card-hdr">
            <i class="ti ti-receipt"></i>
            <span class="card-hdr-txt">選擇繳費方案</span>
            <?php if ($endday && !$isSubscription): ?>
            <span style="margin-left:auto;font-size:11px;color:var(--text-tert)">(選填,已有到期日)</span>
            <?php endif; ?>
        </div>
        <?php if ($isSubscription): ?>
        <div style="padding:14px">
            <div class="plan-card selected" id="plan-sub"
                 data-key="subscription" data-price="0"
                 onclick="selectPlan(this)">
                <div class="plan-name">定期定額月扣</div>
                <div class="plan-price">信用卡扣款</div>
            </div>
        </div>
        <?php else: ?>
        <div class="plan-grid" id="plan-grid">
            <?php
            $planMap = [
                'season'   => '季繳',
                'halfyear' => '半年繳',
                'year'     => '一年繳',
                'twoyear'  => '兩年繳',
            ];
            foreach ($planMap as $key => $label):
                if (!empty($plan_options[$key])):
                    $price = number_format((int)$plan_options[$key]['price']);
            ?>
            <div class="plan-card" data-key="<?= $key ?>" data-price="<?= (int)$plan_options[$key]['price'] ?>"
                onclick="selectPlan(this)">
                <div class="plan-name"><?= $label ?></div>
                <div class="plan-price">$<?= $price ?><span>/期</span></div>
            </div>
            <?php endif; endforeach; ?>
        </div>
        <!-- 取消選擇:避免誤點方案後無法取消 -->
        <div id="plan-clear-wrap" style="display:none;padding:0 14px 14px">
            <button type="button" onclick="clearPlanSelection()"
                style="width:100%;padding:9px;font-size:13px;color:var(--text-sec);background:none;border:0.5px dashed var(--gray-bd2);border-radius:var(--radius-md);cursor:pointer;font-family:inherit;display:flex;align-items:center;justify-content:center;gap:5px">
                <i class="ti ti-x" style="font-size:13px"></i> 取消選擇方案
            </button>
        </div>
        <?php endif; ?>
    </div>

    <!-- 加收項目 -->
    <div class="card">
        <div class="card-hdr">
            <i class="ti ti-plus"></i>
            <span class="card-hdr-txt">加收項目</span>
            <button type="button" onclick="addExtraRow()"
                style="margin-left:auto;font-size:12px;padding:4px 10px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);background:none;cursor:pointer;color:var(--text-sec);font-family:inherit">
                + 新增
            </button>
        </div>
        <div id="extra-container"></div>
    </div>

    <!-- 費用合計 -->
    <div class="card">
        <div class="total-row">
            <span class="total-lbl">應繳費用總計</span>
            <?php if($isSubscription): ?>
            <span class="total-amt" id="total-display" style="font-size:13px;color:var(--green-dk)">信用卡定期定額繳費</span>
            <?php else: ?>
            <span class="total-amt" id="total-display">$0</span>
            <?php endif; ?>
        </div>
        <div id="deposit-summary" style="display:none">
            <div class="deposit-row">
                <span class="deposit-lbl">借用設備押金</span>
                <span class="deposit-amt" id="deposit-display">$0</span>
            </div>
        </div>
    </div>

</div><!-- /page-2 -->

<!-- ════════════════════════════════════════
     STEP 3 ─ 日期
═════════════════════════════════════════ -->
<div class="step-page" id="page-3">

    <div class="card">
        <div class="card-hdr">
            <i class="ti ti-calendar"></i>
            <span class="card-hdr-txt">裝機與租期</span>
        </div>
        <div class="date-row">
            <span class="date-lbl">裝機日</span>
            <span class="date-val"><?= $todayDisplay ?></span>
        </div>
        <div class="date-row">
            <span class="date-lbl">起租日</span>
            <span class="date-val accent" id="rent-start">—</span>
        </div>
        <div class="date-row">
            <span class="date-lbl">到期日</span>
            <span class="date-val accent" id="rent-end">—</span>
        </div>
    </div>

    <!-- 贈送月份(定期定額不顯示) -->
    <div class="card" id="gift-card" <?php if($isSubscription): ?>style="display:none"<?php endif; ?>>
        <div class="card-hdr">
            <i class="ti ti-gift"></i>
            <span class="card-hdr-txt">贈送月份</span>
            <button type="button" onclick="addGiftGroup()"
                style="margin-left:auto;font-size:12px;padding:4px 10px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);background:none;cursor:pointer;color:var(--text-sec);font-family:inherit">
                + 新增
            </button>
        </div>
        <div id="gift-container">
            <div class="gift-empty" style="padding:12px 14px;font-size:13px;color:var(--text-tert)">
                目前無贈送月份
            </div>
        </div>
    </div>

    <!-- 含贈送後到期日(無贈送時隱藏) -->
    <div class="card" id="real-end-card" style="display:none">
        <div class="date-row" style="background:var(--green-lt);border-radius:var(--radius-lg)">
            <span class="date-lbl" style="font-weight:500;color:var(--green-deep)">含贈送後到期</span>
            <span class="date-val big accent" id="real-end">—</span>
        </div>
    </div>

</div><!-- /page-3 -->

<!-- ── 底部 ── -->
<div class="bottom-bar">
    <div class="bottom-summary">
        <div class="bottom-summary-lbl">目前費用</div>
        <div class="bottom-summary-main"><span id="bar-total">$0</span></div>
        <div id="bar-deposit-wrap" style="display:none;font-size:11px;color:#BA7517;margin-top:2px">
            押金 <span id="bar-deposit">$0</span>
        </div>
    </div>
    <div class="bottom-nav">
        <button class="nav-btn" id="prev-btn" onclick="navigate(-1)" disabled>
            <i class="ti ti-arrow-left"></i>
        </button>
        <button class="nav-btn primary" id="next-btn" onclick="navigate(1)">
            下一步 <i class="ti ti-arrow-right"></i>
        </button>
    </div>
</div>

<!-- ── 備註 Modal ── -->
<div class="modal-bg" id="remark-modal">
    <div class="modal-box">
        <button class="modal-close" onclick="document.getElementById('remark-modal').classList.remove('open')">&times;</button>
        <div class="modal-title"><i class="ti ti-notes" style="color:var(--green)"></i> 備註</div>
        <div class="modal-content" id="remark-content"><?= htmlspecialchars($remark ?: '無備註') ?></div>
    </div>
</div>

<!-- ── Hidden form (送出用) ── -->
<form id="submit-form" method="POST" action="preview.php" style="display:none">
    <input type="hidden" name="user_id"         value="<?= htmlspecialchars($user_id) ?>">
    <input type="hidden" name="member"          value="<?= htmlspecialchars($member) ?>">
    <input type="hidden" name="memberaddress"   value="<?= htmlspecialchars($memberaddress) ?>">
    <input type="hidden" name="useraddress"     value="<?= htmlspecialchars($useraddress) ?>">
    <input type="hidden" name="speed"           value="<?= htmlspecialchars($speed) ?>">
    <input type="hidden" name="basenumber"      value="<?= htmlspecialchars($basenumber) ?>">
    <input type="hidden" name="pppoeid"         value="<?= htmlspecialchars($pppoeid) ?>">
    <input type="hidden" name="pppoepw"         value="<?= htmlspecialchars($pppoepw) ?>">
    <input type="hidden" name="fistcount"       value="<?= htmlspecialchars($fistcount) ?>">
    <!-- JS 填入 -->
    <input type="hidden" name="applicant_name"       id="f_name">
    <input type="hidden" name="applicant_usernumber" id="f_id">
    <input type="hidden" name="enname"             value="<?= htmlspecialchars($enname) ?>">
    <input type="hidden" name="enphone"            value="<?= htmlspecialchars($enphone) ?>">
    <input type="hidden" name="id_type"              id="f_idtype">
    <input type="hidden" name="applicant_phone"      id="f_phone">
    <input type="hidden" name="applicant_homenumber" id="f_home">
    <input type="hidden" name="applicant_email"      id="f_email">
    <input type="hidden" name="info_devices"         id="f_info">
    <input type="hidden" name="info_qty"             id="f_info_qty">
    <input type="hidden" name="router_devices"       id="f_router">
    <input type="hidden" name="router_qty"           id="f_router_qty">
    <input type="hidden" name="use_devices"          id="f_use">
    <input type="hidden" name="fee_type"             id="f_fee_type">
    <input type="hidden" name="fee_amount"           id="f_fee_amount">
    <input type="hidden" name="extra_fees"           id="f_extra">
    <input type="hidden" name="total_fee"            id="f_total">
    <input type="hidden" name="deposit_total"        id="f_deposit">
    <input type="hidden" name="install_date"         value="<?= $todayStr ?>">
    <input type="hidden" name="rental_start_date"    id="f_rent_start">
    <input type="hidden" name="rental_end_date"      id="f_rent_end">
    <input type="hidden" name="gift_months"          id="f_gift">
    <input type="hidden" name="real_end_date"        id="f_real_end">
</form>

<!-- ════════════════════════════
     JavaScript
════════════════════════════ -->
<script>
// ── PHP 資料注入 ──────────────────────
const PHP = {
    isSubscription: <?= $isSubscription ?>,
    planOptions: <?= json_encode($plan_options) ?>,
    today: '<?= $todayStr ?>',
    remark: <?= json_encode($remark ?: '無備註') ?>,
    enname: <?= json_encode($enname) ?>,
    enphone: <?= json_encode($enphone) ?>,
    endday: <?= json_encode($endday) ?>
};

// enname 存入 sessionStorage 作為備援(避免 PHP Session 遺失)
if(PHP.enname) {
    sessionStorage.setItem('ys_enname', PHP.enname);
}
if(PHP.enphone) {
    sessionStorage.setItem('ys_enphone', PHP.enphone);
}

// ── 步驟控制 ─────────────────────────
let currentStep = 0;
const totalSteps = 4;
let otpVerified = false;   // 宣告在這裡,供 goStep 使用
let _pauseSave = false;    // 宣告在最前面,restoreFromSS 才能正確使用

function goStep(n) {
    // 從 step0 離開時:只有信箱「有填且格式正確但未驗證」才阻擋
    if (currentStep === 0 && n !== 0) {
        const emailVal = document.getElementById('applicant_email').value.trim();
        const emailOk  = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailVal);
        if (emailVal && emailOk && !otpVerified) {
            alert('請完成電子信箱驗證後才能繼續');
            document.getElementById('applicant_email').focus();
            return;
        }
    }
    document.querySelectorAll('.step-page').forEach((p,i) => {
        p.classList.toggle('active', i === n);
    });
    document.querySelectorAll('.step-tab').forEach((t,i) => {
        t.classList.toggle('active', i === n);
        t.classList.toggle('done', i < n);
    });
    currentStep = n;
    document.getElementById('prev-btn').disabled = n === 0;
    const nb = document.getElementById('next-btn');
    if (n === totalSteps - 1) {
        nb.innerHTML = '確認送出 <i class="ti ti-send"></i>';
        nb.onclick = () => submitForm();
    } else {
        nb.innerHTML = '下一步 <i class="ti ti-arrow-right"></i>';
        nb.onclick = () => navigate(1);
    }
}

function navigate(dir) {
    const next = currentStep + dir;
    if (next < 0 || next >= totalSteps) return;
    goStep(next);
}

// ── 備註 ─────────────────────────────
document.getElementById('remark-btn').onclick = () => {
    document.getElementById('remark-modal').classList.add('open');
};
document.getElementById('remark-modal').addEventListener('click', function(e) {
    if (e.target === this) this.classList.remove('open');
});

// ── 行動電話格式(4-3-3,自動補上 "-")──────
document.getElementById('applicant_phone').addEventListener('input', function() {
    let raw = this.value.replace(/\D/g,'').slice(0,10);
    let formatted = raw;
    if (raw.length > 7) {
        formatted = raw.slice(0,4) + '-' + raw.slice(4,7) + '-' + raw.slice(7);
    } else if (raw.length > 4) {
        formatted = raw.slice(0,4) + '-' + raw.slice(4);
    }
    this.value = formatted;
    const ok = /^09\d{2}-\d{3}-\d{3}$/.test(this.value);
    const hint = document.getElementById('phone-hint');
    if (!this.value) { hint.textContent=''; return; }
    hint.textContent = ok ? '✔ 格式正確' : '✘ 格式錯誤(例:0912-345-678)';
    hint.className = 'hint ' + (ok ? 'hint-ok' : 'hint-err');
});

// ── 住宅電話格式 ──────────────────────
document.getElementById('applicant_homenumber').addEventListener('input', function() {
    let raw = this.value.replace(/\D/g,'');
    this.value = raw.length > 2 ? raw.slice(0,2)+'-'+raw.slice(2) : raw;
    const ok = /^0\d-\d{7,8}$/.test(this.value);
    const hint = document.getElementById('home-hint');
    if (!this.value) { hint.textContent=''; return; }
    hint.textContent = ok ? '✔ 格式正確' : '✘ 格式錯誤(例:02-12345678)';
    hint.className = 'hint ' + (ok ? 'hint-ok' : 'hint-err');
});

// ── 身分證驗證 ────────────────────────
function isValidTWID(v){ return /^[A-Z][12]\d{8}$/.test(v); }
function isValidTaxID(v){
    if(!/^\d{8}$/.test(v)) return false;
    const w=[1,2,1,2,1,2,4,1]; let s=0;
    for(let i=0;i<8;i++){const p=+v[i]*w[i];s+=Math.floor(p/10)+(p%10);}
    return s%10===0 || (v[6]==='7' && (s+1)%10===0);
}
function isValidARC(v){ return /^[A-Z]{2}\d{8}$/.test(v); }

document.getElementById('applicant_usernumber').addEventListener('input', function() {
    const type = document.getElementById('id-type').value;
    let val = this.value.toUpperCase().replace(type==='taxid' ? /\D/g : /[^A-Z0-9]/g, '');
    this.value = val;
    const hint = document.getElementById('twid-hint');
    if (!val) { hint.textContent=''; return; }
    let ok = false, msg = '';
    if (type==='twid'){ ok=isValidTWID(val); msg='身分證'; }
    else if(type==='taxid'){ ok=isValidTaxID(val); msg='統一編號'; }
    else { ok=isValidARC(val); msg='居留證'; }
    hint.textContent = ok ? `✔ ${msg}格式正確` : `✘ 格式錯誤`;
    hint.className = 'hint ' + (ok ? 'hint-ok' : 'hint-err');
});

document.getElementById('id-type').addEventListener('change', function() {
    const input = document.getElementById('applicant_usernumber');
    input.value = '';
    document.getElementById('twid-hint').textContent = '';
    input.maxLength = this.value==='taxid' ? 8 : 10;
});

// ── 電子信箱 OTP ──────────────────────
// otpVerified 已在頂部宣告

document.getElementById('applicant_email').addEventListener('input', function(){
    const ok = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.value);
    const hint = document.getElementById('email-hint');
    if(!this.value){hint.textContent='';return;}
    hint.textContent = ok ? '✔ 格式正確' : '✘ 格式錯誤';
    hint.className = 'hint '+(ok?'hint-ok':'hint-err');
    otpVerified = false;
    document.getElementById('otp-box').style.display='none';
});

document.getElementById('send-otp-btn').addEventListener('click', () => sendOTP(false));

function sendOTP(isResend) {
    const email = document.getElementById('applicant_email').value.trim();
    if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)){
        alert('請先輸入正確的電子信箱格式'); return;
    }

    const btn = document.getElementById('send-otp-btn');
    btn.textContent = '寄送中…';
    btn.disabled = true;

    const fd = new FormData();
    fd.append('action', 'send_otp');
    fd.append('email', email);

    // 使用目前頁面 URL(保留所有 query string)
    fetch(window.location.href, { method:'POST', body:fd })
        .then(r => {
            if(!r.ok) throw new Error('HTTP ' + r.status);
            return r.text();
        })
        .then(text => {
            let data;
            try { data = JSON.parse(text); }
            catch(e) {
                console.error('回應非 JSON:', text);
                throw new Error('伺服器回應格式錯誤');
            }
            if(data.success){
                document.getElementById('otp-target-email').textContent = email;
                document.getElementById('otp-box').style.display = 'block';
                document.getElementById('otp-input').value = '';
                document.getElementById('email-hint').textContent = isResend ? '✔ 已重新發送驗證碼' : '✔ 驗證碼已寄出';
                document.getElementById('email-hint').className = 'hint hint-ok';
                btn.textContent = '60秒後可重發';
                let sec = 59;
                const timer = setInterval(()=>{
                    btn.textContent = sec + '秒後可重發';
                    if(sec-- <= 0){
                        clearInterval(timer);
                        btn.textContent = '重新發送';
                        btn.disabled = false;
                    }
                }, 1000);
            } else {
                alert(data.message);
                btn.textContent = isResend ? '重新發送' : '發送驗證';
                btn.disabled = false;
            }
        })
        .catch(err =>{
            alert('發送失敗:' + err.message + '\n請確認網路或聯繫客服');
            btn.textContent = isResend ? '重新發送' : '發送驗證';
            btn.disabled = false;
        });
}

function verifyOTP() {
    const entered = document.getElementById('otp-input').value.trim();
    const email   = document.getElementById('applicant_email').value.trim();
    if(!entered){ alert('請輸入驗證碼'); return; }

    const fd = new FormData();
    fd.append('action', 'verify_otp');
    fd.append('otp', entered);
    fd.append('email', email);

    fetch(window.location.href, { method:'POST', body:fd })
        .then(r => {
            if(!r.ok) throw new Error('HTTP ' + r.status);
            return r.text();
        })
        .then(text => {
            let data;
            try { data = JSON.parse(text); }
            catch(e) { throw new Error('伺服器回應格式錯誤'); }
            if(data.success){
                otpVerified = true;
                document.getElementById('otp-box').style.display='none';
                document.getElementById('email-hint').textContent='✔ 信箱已驗證';
                document.getElementById('email-hint').className='hint hint-ok';
                // 驗證成功後整個按鈕直接隱藏
                document.getElementById('send-otp-btn').style.display = 'none';
                document.getElementById('applicant_email').readOnly = true;
                SS.set('otp_verified', '1');
                SS.set('applicant_email', document.getElementById('applicant_email').value);
            } else {
                document.getElementById('otp-input').style.borderColor='var(--red)';
                setTimeout(()=>document.getElementById('otp-input').style.borderColor='',1500);
                alert(data.message);
            }
        })
        .catch(err=>alert('驗證失敗:' + err.message));
}

function cancelOTP() {
    otpVerified = false;
    document.getElementById('otp-box').style.display='none';
    document.getElementById('otp-input').value='';
    document.getElementById('email-hint').textContent='';
    document.getElementById('applicant_email').readOnly = false;
    document.getElementById('applicant_email').value = '';
    document.getElementById('applicant_email').focus();
    const btn = document.getElementById('send-otp-btn');
    btn.textContent='發送驗證';
    btn.disabled=false;
    btn.style.cssText='font-size:12px;padding:5px 10px;border:0.5px solid var(--green);border-radius:var(--radius-md);background:none;cursor:pointer;color:var(--green);white-space:nowrap;font-family:inherit';
}

// ══════════════════════════════════════════
// 室內資訊箱設備(新增列模式)
// ══════════════════════════════════════════
const INFO_DEVICES = ['無','光貓','光轉B','100M有線分享器','1000M有線分享器',
    '100M無線分享器','1000M無線分享器','MESH','100M集線器','1000M集線器','UY','Panel'];
let infoIdx = 0;

function getInfoSelected() {
    const vals = [];
    document.querySelectorAll('.info-group select.dev-select').forEach(s => {
        if(s.value) vals.push(s.value);
    });
    return vals;
}

function rebuildInfoOptions() {
    const taken = getInfoSelected();
    document.querySelectorAll('.info-group').forEach(g => {
        const sel = g.querySelector('select.dev-select');
        const cur = sel.value;
        const hasWu = taken.includes('無');
        sel.innerHTML = '<option value="">— 選擇設備 —</option>' +
            INFO_DEVICES.map(d => {
                if(d === '無' && hasWu && cur !== '無') return '';
                if(d !== '' && d !== cur && d !== '無' && taken.includes(d)) return '';
                return `<option value="${d}" ${d===cur?'selected':''}>${d}</option>`;
            }).join('');
        const qtySel = g.querySelector('.info-qty-sel');
        if(qtySel) qtySel.style.display = (cur && cur !== '無') ? 'inline-block' : 'none';
    });
    const hasWu = taken.includes('無');
    document.getElementById('add-info-btn').style.display = hasWu ? 'none' : 'flex';
}

function addInfoGroup() {
    const container = document.getElementById('info-container');
    const idx = infoIdx++;
    const div = document.createElement('div');
    div.className = 'use-group info-group';
    div.dataset.idx = idx;
    div.innerHTML = `
        <div class="use-group-header" style="align-items:center;gap:6px">
            <select class="use-select dev-select" onchange="onInfoChange(this)" style="flex:1">
                <option value="">— 選擇設備 —</option>
                ${INFO_DEVICES.map(d=>`<option value="${d}">${d}</option>`).join('')}
            </select>
            <select class="info-qty-sel" style="display:none;font-size:14px;padding:5px 6px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);background:#fff;font-family:inherit;width:58px;flex-shrink:0">
                ${Array.from({length:5},(_,i)=>`<option>${i+1}</option>`).join('')}
            </select>
            <button type="button" class="rm-btn" onclick="removeInfoGroup(this)">
                <i class="ti ti-x"></i>
            </button>
        </div>
    `;
    container.appendChild(div);
    rebuildInfoOptions();
}

function onInfoChange(sel) {
    const g = sel.closest('.info-group');
    const qtySel = g.querySelector('.info-qty-sel');
    const show = sel.value && sel.value !== '無';
    qtySel.style.display = show ? 'inline-block' : 'none';
    rebuildInfoOptions();
}

function removeInfoGroup(btn) {
    btn.closest('.info-group').remove();
    rebuildInfoOptions();
}

// ══════════════════════════════════════════
// 室內網路設備(新增列模式)
// ══════════════════════════════════════════
const ROUTER_DEVICES = ['單機','100M有線分享器','1000M有線分享器',
    '100M無線分享器','1000M無線分享器','MESH','電視盒','100M集線器','1000M集線器'];
let routerIdx = 0;

function getRouterSelected() {
    const vals = [];
    document.querySelectorAll('.router-group select.dev-select').forEach(s => {
        if(s.value) vals.push(s.value);
    });
    return vals;
}

function rebuildRouterOptions() {
    const taken = getRouterSelected();
    const hasSingle = taken.includes('單機');
    document.querySelectorAll('.router-group').forEach(g => {
        const sel = g.querySelector('select.dev-select');
        const cur = sel.value;
        sel.innerHTML = '<option value="">— 選擇設備 —</option>' +
            ROUTER_DEVICES.map(d => {
                if(d === '單機' && hasSingle && cur !== '單機') return '';
                if(d !== '' && d !== cur && d !== '單機' && taken.includes(d)) return '';
                return `<option value="${d}" ${d===cur?'selected':''}>${d}</option>`;
            }).join('');
        // 數量(內嵌在 header 同列)
        const qtySel = g.querySelector('select.router-qty-sel');
        if(qtySel) qtySel.style.display = (cur && cur !== '單機') ? 'inline-block' : 'none';
        // 電視盒品牌
        const tvWrap = g.querySelector('.tv-brand-wrap');
        if(tvWrap) tvWrap.style.display = (cur === '電視盒') ? 'inline-flex' : 'none';
    });
    document.getElementById('add-router-btn').style.display = hasSingle ? 'none' : 'flex';
}

function addRouterGroup() {
    const container = document.getElementById('router-container');
    const idx = routerIdx++;
    const div = document.createElement('div');
    div.className = 'use-group router-group';
    div.dataset.idx = idx;
    div.innerHTML = `
        <div class="use-group-header" style="align-items:center;gap:6px">
            <select class="use-select dev-select" onchange="onRouterChange(this)" style="flex:1">
                <option value="">— 選擇設備 —</option>
                ${ROUTER_DEVICES.map(d=>`<option value="${d}">${d}</option>`).join('')}
            </select>
            <select class="router-qty-sel" style="display:none;width:58px;font-size:14px;padding:5px 4px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);background:#fff;font-family:inherit;flex-shrink:0">
                ${Array.from({length:5},(_,i)=>`<option>${i+1}</option>`).join('')}
            </select>
            <div class="tv-brand-wrap" style="display:none;align-items:center;gap:6px;flex-shrink:0">
                <select class="tv-brand-sel" style="font-size:13px;padding:5px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);background:#fff;font-family:inherit"
                    onchange="toggleTvOther(this)">
                    <option value="cm">公司</option>
                    <option value="or">其他品牌</option>
                </select>
                <input type="text" class="tv-other-inp" placeholder="輸入廠牌"
                    style="display:none;font-size:13px;padding:5px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);width:80px">
            </div>
            <button type="button" class="rm-btn" onclick="removeRouterGroup(this)">
                <i class="ti ti-x"></i>
            </button>
        </div>
    `;
    container.appendChild(div);
    rebuildRouterOptions();
}

function onRouterChange(sel) {
    rebuildRouterOptions();
}

function removeRouterGroup(btn) {
    btn.closest('.router-group').remove();
    rebuildRouterOptions();
}

function toggleTvOther(sel) {
    const inp = sel.closest('.tv-brand-wrap').querySelector('.tv-other-inp');
    inp.style.display = sel.value === 'or' ? 'inline-block' : 'none';
}

// ══════════════════════════════════════════
// 使用設備(先選用途 → 再選設備 → 填料號/押金/據點)
// ══════════════════════════════════════════
const usePurposeDevices = {
    'gift'  : ['1000M無線分享器', '100M無線分享器'],
    'borrow': ['光貓', '光轉B'],
    'node'  : ['1000M集線器', '100M集線器', '光轉A']
};
let useIdx = 0;

function addUseGroup() {
    const container = document.getElementById('use-container');
    const idx = useIdx++;
    const div = document.createElement('div');
    div.className = 'use-group';
    div.dataset.idx = idx;
    div.innerHTML = `
        <div class="use-group-header" style="display:flex;align-items:center;gap:6px;padding:10px 14px;border-bottom:0.5px solid var(--gray-bd)">
            <!-- 用途(先選) -->
            <select id="use-purpose-${idx}" class="use-purpose-sel"
                style="flex:1;font-size:14px;padding:8px 10px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);background:#fff;font-family:inherit;color:var(--text);min-width:0"
                onchange="onUsePurposeChange(${idx})">
                <option value="">— 選擇用途 —</option>
                <option value="gift">贈品</option>
                <option value="borrow">借用</option>
                <option value="node">據點</option>
            </select>
            <!-- 設備(選完用途才顯示) -->
            <select id="use-device-${idx}" class="use-device-sel"
                style="display:none;flex:1.4;font-size:14px;padding:8px 10px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);background:#fff;font-family:inherit;color:var(--text);min-width:0"
                onchange="onUseDeviceChange(${idx})">
                <option value="">— 選擇設備 —</option>
            </select>
            <button type="button"
                style="width:30px;height:30px;border:none;border-radius:50%;background:var(--gray-bg);display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0;color:var(--text-sec)"
                onclick="removeUseGroup(this)">
                <i class="ti ti-x" style="font-size:13px"></i>
            </button>
        </div>
        <!-- 動態欄位(選完設備才顯示,料號 + 押金/據點,寬度一致) -->
        <div id="use-fields-${idx}" style="display:none;padding:10px 14px">
            <div style="display:flex;gap:8px;flex-wrap:wrap">
                <div style="flex:1;min-width:140px">
                    <div style="font-size:12px;color:var(--text-sec);margin-bottom:4px">料號</div>
                    <input type="text" id="use-sn-${idx}" placeholder="輸入料號"
                        style="width:100%;font-size:14px;padding:9px 12px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);font-family:monospace;background:#fff;height:40px"
                        oninput="saveAllToSS()">
                </div>
                <!-- 押金(借用時顯示,鎖死 1000) -->
                <div id="use-deposit-wrap-${idx}" style="display:none;flex:1;min-width:140px">
                    <div style="font-size:12px;color:var(--text-sec);margin-bottom:4px">押金(元)</div>
                    <input type="text" id="use-dp-${idx}" value="" readonly
                        style="width:100%;font-size:14px;padding:9px 12px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);font-family:inherit;background:#f5f5f5;color:#666;height:40px;cursor:not-allowed">
                </div>
                <!-- 據點編號(據點時顯示) -->
                <div id="use-nodepos-wrap-${idx}" style="display:none;flex:1;min-width:140px">
                    <div style="font-size:12px;color:var(--text-sec);margin-bottom:4px">據點編號</div>
                    <input type="text" id="use-node-pos-${idx}" placeholder="輸入據點編號"
                        style="width:100%;font-size:14px;padding:9px 12px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);font-family:inherit;background:#fff;height:40px"
                        oninput="saveAllToSS()">
                </div>
            </div>
        </div>
    `;
    container.appendChild(div);
    updateAddUseBtn();
}

function onUsePurposeChange(idx) {
    const purpose   = document.getElementById('use-purpose-'+idx).value;
    const deviceSel = document.getElementById('use-device-'+idx);
    const fieldsW   = document.getElementById('use-fields-'+idx);

    // 重置設備與欄位
    deviceSel.value = '';
    fieldsW.style.display = 'none';

    // 切換用途時,若不是借用,立即清空押金值,避免殘留舊值被誤算
    if (purpose !== 'borrow') {
        const dp = document.getElementById('use-dp-'+idx);
        if (dp) dp.value = '';
    }

    if (purpose && usePurposeDevices[purpose]) {
        deviceSel.innerHTML = '<option value="">— 選擇設備 —</option>' +
            usePurposeDevices[purpose].map(d => `<option value="${d}">${d}</option>`).join('');
        deviceSel.style.display = 'inline-block';
    } else {
        deviceSel.style.display = 'none';
        deviceSel.innerHTML = '<option value="">— 選擇設備 —</option>';
    }
    calcDeposit();
    if(!_pauseSave) saveAllToSS();
}

function onUseDeviceChange(idx) {
    const purpose = document.getElementById('use-purpose-'+idx).value;
    const device  = document.getElementById('use-device-'+idx).value;
    const fieldsW = document.getElementById('use-fields-'+idx);
    const dpW     = document.getElementById('use-deposit-wrap-'+idx);
    const npW     = document.getElementById('use-nodepos-wrap-'+idx);

    if (device) {
        fieldsW.style.display = 'block';
        dpW.style.display = (purpose === 'borrow') ? 'block' : 'none';
        npW.style.display = (purpose === 'node')   ? 'block' : 'none';
        if (purpose === 'borrow') {
            const dp = document.getElementById('use-dp-'+idx);
            if (dp) dp.value = '1000';
        } else {
            // 非借用:確保押金欄位為空,不參與計算
            const dp = document.getElementById('use-dp-'+idx);
            if (dp) dp.value = '';
        }
    } else {
        fieldsW.style.display = 'none';
    }
    calcDeposit();
    if(!_pauseSave) saveAllToSS();
}

function removeUseGroup(btn) {
    btn.closest('.use-group').remove();
    calcDeposit();
    updateAddUseBtn();
    if(!_pauseSave) saveAllToSS();
}

function updateAddUseBtn(){
    const count = document.querySelectorAll('#use-container > .use-group').length;
    document.getElementById('add-use-btn').style.display = count >= 5 ? 'none' : 'flex';
}

// 統一的押金加總函式:只計算「用途=借用」的項目,
// 避免贈品/據點因隱藏的 use-dp 輸入框殘留數值而被誤算進押金。
function getTotalDeposit() {
    let total = 0;
    document.querySelectorAll('#use-container > .use-group').forEach(g => {
        const idx = g.dataset.idx;
        const purpose = document.getElementById('use-purpose-'+idx)?.value || '';
        if (purpose === 'borrow') {
            const dp = document.getElementById('use-dp-'+idx);
            total += parseInt(dp?.value) || 0;
        }
    });
    return total;
}

function calcDeposit() {
    const total = getTotalDeposit();
    const fmt = '$' + total.toLocaleString();
    document.getElementById('deposit-display').textContent = fmt;
    const show = total > 0;
    document.getElementById('deposit-summary').style.display = show ? 'block' : 'none';
    // 底部押金
    const barWrap = document.getElementById('bar-deposit-wrap');
    if(barWrap){
        barWrap.style.display = show ? 'block' : 'none';
        document.getElementById('bar-deposit').textContent = fmt;
    }
    calcTotal();
}

// ══════════════════════════════════════════
// 贈送月份(已選選項不重複;只有最後一列可改原因;
//          住戶介紹 → 介紹用戶資訊欄位;他網轉入 → 他網名稱欄位)
// ══════════════════════════════════════════
const giftTypes = ['首裝優惠','他網轉入','總幹事介紹','業務贈送','主管贈送','LINE推廣','住戶介紹','團購優惠'];
let giftIdx = 0;

function getGiftSelected() {
    const vals = [];
    document.querySelectorAll('.gift-group select.gift-reason').forEach(s => {
        if(s.value) vals.push(s.value);
    });
    return vals;
}

function rebuildGiftOptions() {
    const groups = document.querySelectorAll('.gift-group');
    const taken  = getGiftSelected();
    groups.forEach((g, i) => {
        const isLast = (i === groups.length - 1);
        const sel = g.querySelector('select.gift-reason');
        const cur = sel.value;
        sel.disabled = !isLast;
        sel.innerHTML = giftTypes.map(t => {
            if(t !== cur && taken.includes(t)) return '';
            return `<option value="${t}" ${t===cur?'selected':''}>${t}</option>`;
        }).join('');
    });
}

function _buildGiftGroupHTML(idx, reason, months, intro, network) {
    const introVal   = (intro   || '').replace(/"/g,'&quot;');
    const networkVal = (network || '').replace(/"/g,'&quot;');
    return `
        <div style="display:flex;flex-wrap:wrap;align-items:center;gap:6px;width:100%">
            <select class="gift-select gift-reason" style="flex:1;min-width:110px"
                onchange="onGiftReasonChange(${idx})">
                ${giftTypes.map(t => `<option value="${t}" ${t===reason?'selected':''}>${t}</option>`).join('')}
            </select>
            <input type="text" class="gift-network" id="gift-network-${idx}"
                value="${networkVal}" placeholder="他網名稱"
                style="display:${reason==='他網轉入'?'inline-block':'none'};width:130px;font-size:14px;padding:6px 8px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);background:#fff;font-family:inherit"
                oninput="saveAllToSS()">
            <select class="gift-qty" style="width:60px"
                onchange="calcDates();saveAllToSS()">
                ${Array.from({length:12},(_,i)=>`<option value="${i+1}" ${(i+1)===parseInt(months)?'selected':''}>${i+1}月</option>`).join('')}
            </select>
            <button type="button" class="rm-btn" onclick="removeGift(this)">
                <i class="ti ti-x"></i>
            </button>
        </div>
        <input type="text" class="gift-intro" id="gift-intro-${idx}"
            value="${introVal}" placeholder="介紹用戶資訊(姓名/用戶編號等)"
            style="display:${reason==='住戶介紹'?'block':'none'};width:100%;margin-top:6px;font-size:14px;padding:7px 10px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);background:#fff;font-family:inherit"
            oninput="saveAllToSS()">
    `;
}

function addGiftGroup() {
    const container = document.getElementById('gift-container');
    // 移除所有靜態空白提示
    container.querySelectorAll('.gift-empty').forEach(el => el.remove());
    [...container.childNodes].forEach(n => { if(n.nodeType === 3) n.remove(); });

    const taken = getGiftSelected();
    const avail = giftTypes.filter(t => !taken.includes(t));
    if(avail.length === 0){ alert('所有贈送類型已新增完畢'); return; }

    const idx = giftIdx++;
    const defaultReason = avail[0];
    const div = document.createElement('div');
    div.className = 'gift-group';
    div.dataset.idx = idx;
    div.style.cssText = 'display:flex;flex-direction:column;padding:9px 14px;border-bottom:0.5px solid var(--gray-bd)';
    div.innerHTML = _buildGiftGroupHTML(idx, defaultReason, 1, '', '');
    container.appendChild(div);
    rebuildGiftOptions();
    calcDates();
    saveAllToSS();
}

// 還原專用:直接插入指定原因+月數+附加欄位,不受過濾邏輯影響
function restoreGiftGroup(reason, months, intro, network) {
    const container = document.getElementById('gift-container');
    container.querySelectorAll('.gift-empty').forEach(el => el.remove());
    [...container.childNodes].forEach(n => { if(n.nodeType === 3) n.remove(); });

    const idx = giftIdx++;
    const div = document.createElement('div');
    div.className = 'gift-group';
    div.dataset.idx = idx;
    div.style.cssText = 'display:flex;flex-direction:column;padding:9px 14px;border-bottom:0.5px solid var(--gray-bd)';
    div.innerHTML = _buildGiftGroupHTML(idx, reason, months, intro, network);
    container.appendChild(div);
    // 插入完所有 gift 後再統一 rebuild(由呼叫端決定時機)
}

function onGiftReasonChange(idx) {
    const group = document.querySelector(`.gift-group[data-idx="${idx}"]`);
    if (!group) return;
    const reason = group.querySelector('.gift-reason')?.value || '';
    const introInp   = document.getElementById('gift-intro-'+idx);
    const networkInp = document.getElementById('gift-network-'+idx);
    if (introInp)   introInp.style.display   = (reason === '住戶介紹') ? 'block' : 'none';
    if (networkInp) networkInp.style.display = (reason === '他網轉入') ? 'inline-block' : 'none';
    rebuildGiftOptions();
    calcDates();
    if(!_pauseSave) saveAllToSS();
}

function removeGift(btn){
    btn.closest('.gift-group').remove();
    if(document.querySelectorAll('.gift-group').length === 0){
        // 重新放回空白提示
        const container = document.getElementById('gift-container');
        container.innerHTML = '<div class="gift-empty" style="padding:12px 14px;font-size:13px;color:var(--text-tert)">目前無贈送月份</div>';
    }
    rebuildGiftOptions();
    calcDates();
    saveAllToSS();
}

function calcDatesFromDOM(){ calcDates(); }
let selectedPlanPrice = 0;
let selectedPlanType  = '';

function selectPlan(card) {
    document.querySelectorAll('.plan-card').forEach(c=>c.classList.remove('selected'));
    card.classList.add('selected');
    selectedPlanPrice = parseInt(card.dataset.price) || 0;
    selectedPlanType  = card.dataset.key || '';
    calcDates();
    calcTotal();
    // 顯示取消選擇按鈕(定期定額無此按鈕,DOM 不存在則略過)
    const clearWrap = document.getElementById('plan-clear-wrap');
    if(clearWrap) clearWrap.style.display = 'block';
    // 立即存入 sessionStorage,避免重整後消失
    if(!_pauseSave) saveAllToSS();
}

function clearPlanSelection() {
    document.querySelectorAll('.plan-card').forEach(c=>c.classList.remove('selected'));
    selectedPlanPrice = 0;
    selectedPlanType  = '';
    const clearWrap = document.getElementById('plan-clear-wrap');
    if(clearWrap) clearWrap.style.display = 'none';
    calcDates();
    calcTotal();
    if(!_pauseSave) saveAllToSS();
}

// ── 加收項目 ─────────────────────────
let extraIdx = 0;
const extraTypes = ['配線費用','裝機費用','周邊設備'];

function addExtraRow() {
    const container = document.getElementById('extra-container');
    const idx = extraIdx++;
    const div = document.createElement('div');
    div.className = 'extra-row';
    div.dataset.idx = idx;
    div.style.cssText = 'display:flex;align-items:center;gap:6px;padding:9px 14px;border-bottom:0.5px solid var(--gray-bd)';
    div.innerHTML = `
        <select class="extra-select"
            style="flex:1;font-size:14px;padding:7px 10px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);background:#fff;color:var(--text);font-family:inherit;min-width:0;height:38px"
            onchange="onExtraTypeChange(this,${idx})">
            ${extraTypes.map(t=>`<option>${t}</option>`).join('')}
        </select>
        <input class="extra-sn-inline" type="text" id="extra-sn-inp-${idx}"
            placeholder="料號" inputmode="numeric" maxlength="10"
            style="display:none;width:100px;font-size:14px;padding:7px 8px;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);background:#fff;color:var(--text);font-family:inherit;flex-shrink:0;height:38px"
            oninput="this.value=this.value.replace(/\\D/g,'').slice(0,10);saveAllToSS()">
        <input class="extra-amount" type="text" placeholder="金額"
            inputmode="numeric" maxlength="4"
            style="width:72px;font-size:14px;padding:7px 8px;text-align:right;border:0.5px solid var(--gray-bd2);border-radius:var(--radius-md);background:#fff;color:var(--text);font-family:inherit;flex-shrink:0;height:38px"
            oninput="this.value=this.value.replace(/\\D/g,'').slice(0,4);calcTotal();saveAllToSS()">
        <button type="button" class="rm-btn"
            onclick="this.closest('.extra-row').remove();calcTotal();saveAllToSS()">
            <i class="ti ti-x"></i>
        </button>
    `;
    container.appendChild(div);
}

function onExtraTypeChange(sel, idx) {
    const snInp = document.getElementById('extra-sn-inp-'+idx);
    if(snInp) snInp.style.display = sel.value === '周邊設備' ? 'inline-block' : 'none';
    // 不直接呼叫 saveAllToSS,改由 change 事件 bubble 觸發,
    // 這樣在 _pauseSave=true 期間不會提前覆蓋資料
    if(!_pauseSave) saveAllToSS();
}

// ── 日期計算 ─────────────────────────
function fmtDate(d) {
    if(!d || isNaN(d.getTime())) return '—';
    return `${d.getFullYear()}年${String(d.getMonth()+1).padStart(2,'0')}月${String(d.getDate()).padStart(2,'0')}日`;
}

function addMonths(dateStr, months) {
    const d = new Date(dateStr);
    const day = d.getDate();
    d.setDate(1);
    d.setMonth(d.getMonth() + months);
    const lastDay = new Date(d.getFullYear(), d.getMonth()+1, 0).getDate();
    d.setDate(Math.min(day, lastDay));
    return d;
}
function calcDates() {
    const today = new Date(PHP.today);
    const rentStart = new Date(today);
    rentStart.setDate(rentStart.getDate() + 1);
    document.getElementById('rent-start').textContent = fmtDate(rentStart);

    const monthsMap = { season:3, halfyear:6, year:12, twoyear:24 };
    const months = monthsMap[selectedPlanType] || 0;
    let rentEnd = null;

    if (PHP.endday) {
        // ── 有 FileMaker 帶入到期日:以 endday 為基準 ──
        if (months > 0) {
            // 有選擇方案:到期日 = endday + 方案月份
            rentEnd = addMonths(PHP.endday, months);
        } else {
            // 未選擇方案:到期日直接顯示 endday
            rentEnd = new Date(PHP.endday + 'T00:00:00');
        }
        document.getElementById('rent-end').textContent = fmtDate(rentEnd);
    } else if (months > 0) {
        // ── 無 endday:照舊以今天為基準 + 方案月份 ──
        rentEnd = addMonths(PHP.today, months);
        document.getElementById('rent-end').textContent = fmtDate(rentEnd);
    } else {
        document.getElementById('rent-end').textContent = '—';
    }

    // 含贈送後到期:只有真正有贈送群組時才顯示
    const giftGroups = document.querySelectorAll('#gift-container .gift-group');
    const realEndCard = document.getElementById('real-end-card');
    if (giftGroups.length === 0 || !rentEnd) {
        if(realEndCard) realEndCard.style.display = 'none';
        return;
    }
    let gifted = 0;
    giftGroups.forEach(g => { gifted += parseInt(g.querySelector('.gift-qty')?.value) || 0; });
    if (gifted > 0) {
        const realEnd = addMonths(rentEnd.toISOString().split('T')[0], gifted);
        document.getElementById('real-end').textContent = fmtDate(realEnd);
        if(realEndCard) realEndCard.style.display = 'block';
    } else {
        if(realEndCard) realEndCard.style.display = 'none';
    }
}

function calcTotal() {
    let total = selectedPlanPrice;
    document.querySelectorAll('.extra-amount').forEach(inp => {
        total += parseInt(inp.value) || 0;
    });
    // 定期定額:費用欄顯示文字,底部也改顯示文字
    const totalEl = document.getElementById('total-display');
    const barEl   = document.getElementById('bar-total');
    if(PHP.isSubscription && total === 0){
        if(totalEl) totalEl.textContent = '信用卡定期定額繳費';
        if(barEl)   barEl.textContent   = '信用卡扣款';
    } else {
        if(totalEl) totalEl.textContent = '$' + total.toLocaleString();
        if(barEl)   barEl.textContent   = '$' + total.toLocaleString();
    }

    // 底部押金顯示(只計算用途=借用的項目)
    const deposit = getTotalDeposit();
    const wrap = document.getElementById('bar-deposit-wrap');
    const amt  = document.getElementById('bar-deposit');
    if(wrap && amt){
        wrap.style.display = deposit > 0 ? 'block' : 'none';
        amt.textContent = '$' + deposit.toLocaleString();
    }
    SS.set('total', total);
}

// ══════════════════════════════════════════
// sessionStorage 持久化
// ══════════════════════════════════════════
const SS = {
    set(k, v) { try { sessionStorage.setItem('ys_'+k, typeof v === 'object' ? JSON.stringify(v) : String(v)); } catch(e){} },
    // null = key 不存在;空字串 '' = 存在但為空
    get(k) { try { return sessionStorage.getItem('ys_'+k); } catch(e){ return null; } },
    getJ(k, def=null) { try { const v=sessionStorage.getItem('ys_'+k); return v!==null ? JSON.parse(v) : def; } catch(e){ return def; } },
    clear() { try { Object.keys(sessionStorage).filter(k=>k.startsWith('ys_')).forEach(k=>sessionStorage.removeItem(k)); } catch(e){} }
};

function saveAllToSS() {
    if(_pauseSave) return;  // restore 期間不存,避免覆蓋正確資料

    // 申請人欄位
    ['applicant_name','applicant_usernumber','applicant_phone','applicant_homenumber','applicant_email'].forEach(id => {
        const el = document.getElementById(id);
        if(el) SS.set(id, el.value);
    });
    SS.set('id_type', document.getElementById('id-type').value);
    SS.set('otp_verified', otpVerified ? '1' : '0');

    // 設備
    const infoList = [];
    document.querySelectorAll('.info-group').forEach(g => {
        const sel = g.querySelector('select.dev-select');
        if(!sel || !sel.value) return;
        const qty = g.querySelector('select.info-qty-sel')?.value || '1';
        infoList.push({ device: sel.value, qty });
    });
    SS.set('info_list', infoList);

    const routerList = [];
    document.querySelectorAll('.router-group').forEach(g => {
        const sel = g.querySelector('select.dev-select');
        if(!sel || !sel.value) return;
        const qty = g.querySelector('select.router-qty-sel')?.value || '1';
        const tvBrand = g.querySelector('select.tv-brand-sel')?.value || '';
        const tvOther = g.querySelector('input.tv-other-inp')?.value || '';
        routerList.push({ device: sel.value, qty, tvBrand, tvOther });
    });
    SS.set('router_list', routerList);

    // 使用設備
    const uses = [];
    document.querySelectorAll('#use-container > .use-group').forEach(g => {
        const idx     = g.dataset.idx;
        const purpose = document.getElementById('use-purpose-'+idx)?.value || '';
        const device  = document.getElementById('use-device-'+idx)?.value  || '';
        if (!purpose || !device) return;
        const sn      = document.getElementById('use-sn-'+idx)?.value || '';
        const dp      = purpose === 'borrow' ? (document.getElementById('use-dp-'+idx)?.value || '1000') : '';
        const nodePos = purpose === 'node'   ? (document.getElementById('use-node-pos-'+idx)?.value || '') : '';
        uses.push({ device, purpose, sn, dp, nodePos });
    });
    SS.set('use_list', uses);

    // 費率
    SS.set('plan_type', selectedPlanType);
    SS.set('plan_price', selectedPlanPrice);

    // 加收
    const extras = [];
    document.querySelectorAll('.extra-row').forEach(row => {
        const type = row.querySelector('.extra-select')?.value || '';
        const amt  = parseInt(row.querySelector('.extra-amount')?.value) || 0;
        const idx  = row.dataset.idx;
        const sn   = document.getElementById('extra-sn-inp-'+idx)?.value || '';
        extras.push({ type, amt, sn });
    });
    SS.set('extras', extras);

    // 贈送月份
    const gifts = [];
    document.querySelectorAll('.gift-group').forEach(g => {
        const idx     = g.dataset.idx;
        const reason  = g.querySelector('.gift-reason').value;
        const months  = g.querySelector('.gift-qty').value;
        const intro   = document.getElementById('gift-intro-'+idx)?.value   || '';
        const network = document.getElementById('gift-network-'+idx)?.value || '';
        gifts.push({ reason, months, intro, network });
    });
    SS.set('gifts', gifts);
}

function restoreFromSS() {
    _pauseSave = true;

    // 申請人 — 只在 SS 中有值時才填入(不覆蓋空字串到有值的欄位)
    ['applicant_name','applicant_usernumber','applicant_phone','applicant_homenumber'].forEach(id => {
        const v = SS.get(id);
        if(v !== null){          // null = 從未存過,不動;'' = 存過空字串,填入
            const el = document.getElementById(id);
            if(el) el.value = v;
        }
    });

    // 身分證類型
    const idType = SS.get('id_type');
    if(idType !== null){
        const el = document.getElementById('id-type');
        if(el) el.value = idType;
    }

    // Email
    const emailV = SS.get('applicant_email');
    if(emailV !== null && emailV !== ''){
        const el = document.getElementById('applicant_email');
        if(el){
            el.value = emailV;
            if(SS.get('otp_verified') === '1'){
                otpVerified = true;
                el.readOnly = true;
                const hint = document.getElementById('email-hint');
                if(hint){ hint.textContent='✔ 信箱已驗證'; hint.className='hint hint-ok'; }
                const btn = document.getElementById('send-otp-btn');
                if(btn) btn.style.display='none';
            }
        }
    }

    // 設備 - 資訊箱
    const infoList = SS.getJ('info_list', []);
    infoList.forEach(item => {
        addInfoGroup();
        const allG = document.querySelectorAll('#info-container .info-group');
        const g = allG[allG.length - 1]; if(!g) return;
        const sel = g.querySelector('select.dev-select');
        if(sel){ sel.value = item.device; onInfoChange(sel); }
        const qtySel = g.querySelector('select.info-qty-sel');
        if(qtySel) qtySel.value = item.qty;
    });

    // 設備 - 網路
    const routerList = SS.getJ('router_list', []);
    routerList.forEach(item => {
        addRouterGroup();
        const allG = document.querySelectorAll('#router-container .router-group');
        const g = allG[allG.length - 1]; if(!g) return;
        const sel = g.querySelector('select.dev-select');
        if(sel){ sel.value = item.device; onRouterChange(sel); }
        const qtySel = g.querySelector('select.router-qty-sel');
        if(qtySel) qtySel.value = item.qty;
        if(item.tvBrand){ const tb=g.querySelector('select.tv-brand-sel'); if(tb) tb.value=item.tvBrand; }
        if(item.tvOther){ const to=g.querySelector('input.tv-other-inp'); if(to){ to.value=item.tvOther; to.style.display='inline-block'; } }
    });

    // 使用設備
    const useList = SS.getJ('use_list', []);
    useList.forEach(item => {
        addUseGroup();
        const allUse = document.querySelectorAll('#use-container > .use-group');
        const g = allUse[allUse.length - 1]; if(!g) return;
        const idx = g.dataset.idx;

        const purposeSel = document.getElementById('use-purpose-'+idx);
        if(purposeSel && item.purpose){
            purposeSel.value = item.purpose;
            onUsePurposeChange(idx);
        }
        const deviceSel = document.getElementById('use-device-'+idx);
        if(deviceSel && item.device){
            deviceSel.value = item.device;
            onUseDeviceChange(idx);
        }
        const sn = document.getElementById('use-sn-'+idx);
        if(sn) sn.value = item.sn || '';
        if(item.purpose === 'borrow'){
            const dp = document.getElementById('use-dp-'+idx);
            if(dp) dp.value = item.dp || '1000';
        }
        if(item.purpose === 'node'){
            const np = document.getElementById('use-node-pos-'+idx);
            if(np) np.value = item.nodePos || '';
        }
    });
    calcDeposit();

    // 費率 — restore 期間 selectPlan 內的 saveAllToSS 被 _pauseSave 擋住,不會誤存
    const planType = SS.get('plan_type');
    if(planType){
        const card = document.querySelector(`.plan-card[data-key="${planType}"]`);
        if(card){
            document.querySelectorAll('.plan-card').forEach(c=>c.classList.remove('selected'));
            card.classList.add('selected');
            selectedPlanPrice = parseInt(card.dataset.price) || 0;
            selectedPlanType  = planType;
            const clearWrap = document.getElementById('plan-clear-wrap');
            if(clearWrap) clearWrap.style.display = 'block';
        }
    }

    // 加收
    const extras = SS.getJ('extras', []);
    extras.forEach(item => {
        addExtraRow();
        const rows = document.querySelectorAll('#extra-container .extra-row');
        const row = rows[rows.length-1]; if(!row) return;
        const idx = row.dataset.idx;
        const sel = row.querySelector('.extra-select');
        if(sel){ sel.value = item.type; onExtraTypeChange(sel, idx); }
        // 用 String() 轉換避免 0 被 || '' 轉成空字串
        const inp = row.querySelector('.extra-amount');
        if(inp) inp.value = (item.amt != null && item.amt !== '') ? String(item.amt) : '';
        if(item.sn != null && item.sn !== ''){
            const snInp = document.getElementById('extra-sn-inp-'+idx);
            if(snInp){ snInp.value = String(item.sn); snInp.style.display = 'inline-block'; }
        }
    });
    calcTotal();

    // 贈送月份 —— 使用還原專用函式,所有項目插入後再統一 rebuild
    const gifts = SS.getJ('gifts', []);
    gifts.forEach(item => {
        restoreGiftGroup(item.reason, item.months, item.intro, item.network);
    });
    if(gifts.length > 0) rebuildGiftOptions();
    calcDates();

    _pauseSave = false;

    // 若為定期定額方案但 selectedPlanType 還是空(SS 沒有存),補選一次
    if(PHP.isSubscription && !selectedPlanType){
        const subCard = document.getElementById('plan-sub');
        if(subCard){
            subCard.classList.add('selected');
            selectedPlanType  = 'subscription';
            selectedPlanPrice = 0;
        }
    }

    // restore 完成後立即存一次,確保 SS 與 DOM 同步
    saveAllToSS();
}

// 自動存檔(input/change 事件),restore 期間暫停
document.addEventListener('input',  () => { if(!_pauseSave) saveAllToSS(); });
document.addEventListener('change', () => { if(!_pauseSave) saveAllToSS(); });

// ── 送出驗證與打包 ───────────────────
function collectFormData() {
    // 申請人
    document.getElementById('f_name').value   = document.getElementById('applicant_name').value;
    document.getElementById('f_id').value     = document.getElementById('applicant_usernumber').value;
    document.getElementById('f_idtype').value = document.getElementById('id-type').value;
    document.getElementById('f_phone').value  = document.getElementById('applicant_phone').value;
    document.getElementById('f_home').value   = document.getElementById('applicant_homenumber').value;
    document.getElementById('f_email').value  = document.getElementById('applicant_email').value;

    // enname:PHP 靜態輸出已有值,若為空則從 sessionStorage 補
    const ennameInput = document.querySelector('input[name="enname"]');
    if(ennameInput && !ennameInput.value) {
        const ssEnname = sessionStorage.getItem('ys_enname') || PHP.enname || '';
        ennameInput.value = ssEnname;
    }

    // 設備 - 資訊箱
    const infoList = [];
    document.querySelectorAll('.info-group').forEach(g => {
        const sel = g.querySelector('select.dev-select');
        if(!sel || !sel.value) return;
        const qty = g.querySelector('select.info-qty-sel')?.value || '1';
        infoList.push({ device: sel.value, qty });
    });
    document.getElementById('f_info').value = JSON.stringify(infoList);
    document.getElementById('f_info_qty').value = ''; // 已整合進 JSON

    // 設備 - 網路
    const routerList = [];
    document.querySelectorAll('.router-group').forEach(g => {
        const sel = g.querySelector('select.dev-select');
        if(!sel || !sel.value) return;
        const qty = g.querySelector('select.router-qty-sel')?.value || '1';
        const tvBrand = g.querySelector('select.tv-brand-sel')?.value || '';
        const tvOther = g.querySelector('input.tv-other-inp')?.value || '';
        routerList.push({ device: sel.value, qty, tvBrand, tvOther });
    });
    document.getElementById('f_router').value = JSON.stringify(routerList);
    document.getElementById('f_router_qty').value = '';

    // 使用設備
    const uses = [];
    document.querySelectorAll('#use-container > .use-group').forEach(g => {
        const idx     = g.dataset.idx;
        const purpose = document.getElementById('use-purpose-'+idx)?.value || '';
        const device  = document.getElementById('use-device-'+idx)?.value  || '';
        if(!purpose || !device) return;
        const sn      = document.getElementById('use-sn-'+idx)?.value || '';
        const dp      = purpose === 'borrow' ? (document.getElementById('use-dp-'+idx)?.value || '1000') : '';
        const nodePos = purpose === 'node'   ? (document.getElementById('use-node-pos-'+idx)?.value || '') : '';
        uses.push({ device, purpose, sn, dp, nodePos });
    });
    document.getElementById('f_use').value = JSON.stringify(uses);

    // 費率
    document.getElementById('f_fee_type').value   = selectedPlanType;
    document.getElementById('f_fee_amount').value = selectedPlanPrice;

    // 加收(包含周邊設備的料號)
    const extras = [];
    document.querySelectorAll('#extra-container .extra-row').forEach(row => {
        const type = row.querySelector('.extra-select')?.value || '';
        const amt  = parseInt(row.querySelector('.extra-amount')?.value) || 0;
        const idx  = row.dataset.idx;
        const sn   = document.getElementById('extra-sn-inp-'+idx)?.value || '';
        if(type) extras.push({ type, amt, sn });
    });
    document.getElementById('f_extra').value = JSON.stringify(extras);

    // 金額
    let total = selectedPlanPrice;
    document.querySelectorAll('.extra-amount').forEach(i=>total+=parseInt(i.value)||0);
    document.getElementById('f_total').value = total;

    // 押金(只計算用途=借用的項目)
    document.getElementById('f_deposit').value = getTotalDeposit();

    // 日期
    const today = new Date(PHP.today);
    const rStart = new Date(today); rStart.setDate(rStart.getDate()+1);
    document.getElementById('f_rent_start').value = rStart.toISOString().split('T')[0];

    const monthsMap = {season:3,halfyear:6,year:12,twoyear:24};
    const m = monthsMap[selectedPlanType]||0;
    let rEnd = null;
    if (PHP.endday) {
        // 有 FileMaker 帶入到期日:以 endday 為基準
        rEnd = m ? addMonths(PHP.endday, m) : new Date(PHP.endday + 'T00:00:00');
    } else if (m > 0) {
        rEnd = addMonths(PHP.today, m);
    }
    document.getElementById('f_rent_end').value = rEnd ? rEnd.toISOString().split('T')[0] : '';

    // 贈送
    const gifts = [];
    document.querySelectorAll('.gift-group').forEach(g=>{
        const idx = g.dataset.idx;
        gifts.push({
            reason : g.querySelector('.gift-reason').value,
            months : g.querySelector('.gift-qty').value,
            intro  : document.getElementById('gift-intro-'+idx)?.value   || '',
            network: document.getElementById('gift-network-'+idx)?.value || ''
        });
    });
    document.getElementById('f_gift').value = JSON.stringify(gifts);

    let gifted=0; gifts.forEach(g=>gifted+=parseInt(g.months));
    let realEnd = rEnd ? addMonths(rEnd.toISOString().split('T')[0], gifted) : null;
    document.getElementById('f_real_end').value = realEnd ? realEnd.toISOString().split('T')[0] : '';
}

function submitForm() {
    const missing = [];

    // 身分證
    const idVal  = document.getElementById('applicant_usernumber').value.trim();
    const idType = document.getElementById('id-type').value;
    if(!idVal){
        missing.push('請輸入證件號碼');
    } else {
        if(idType==='twid' && !isValidTWID(idVal)) missing.push('身分證字號格式錯誤');
        if(idType==='taxid'&& !isValidTaxID(idVal)) missing.push('統一編號格式錯誤');
        if(idType==='arc'  && !isValidARC(idVal))   missing.push('居留證格式錯誤');
    }

    // 信箱
    const emailVal = document.getElementById('applicant_email').value.trim();
    if(!emailVal){ missing.push('請輸入電子信箱'); }
    else if(!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(emailVal)){ missing.push('電子信箱格式錯誤'); }
    else if(!otpVerified){ missing.push('請完成電子信箱驗證'); }

    // 設備
    const infoCount = document.querySelectorAll('.info-group select.dev-select')
        .length > 0 && [...document.querySelectorAll('.info-group select.dev-select')].some(s=>s.value);
    const routerCount = document.querySelectorAll('.router-group select.dev-select')
        .length > 0 && [...document.querySelectorAll('.router-group select.dev-select')].some(s=>s.value);
    if(!infoCount)   missing.push('請選擇至少一項室內資訊箱設備');
    if(!routerCount) missing.push('請選擇至少一項室內網路設備');

    // 費率
    if(!selectedPlanType && !PHP.isSubscription && !PHP.endday) missing.push('請選擇費率方案');

    if(missing.length){
        // 跳到有問題的步驟
        if(missing.some(m=>m.includes('信箱')||m.includes('身分')||m.includes('證件'))) goStep(0);
        else if(missing.some(m=>m.includes('設備'))) goStep(1);
        else if(missing.some(m=>m.includes('費率'))) goStep(2);
        alert(missing.join('\n'));
        return;
    }

    collectFormData();
    document.getElementById('submit-form').submit();
}

// 初始化
<?php if ($isSubscription): ?>
// 定期定額:先強制選取一次
(function(){
    const subCard = document.getElementById('plan-sub');
    if(subCard) selectPlan(subCard);
})();
<?php endif; ?>
calcDates();
restoreFromSS();
<?php if ($isSubscription): ?>
// restoreFromSS 後強制確認,並修正費用顯示
(function(){
    const subCard = document.getElementById('plan-sub');
    if(subCard) selectPlan(subCard);
    const totalEl = document.getElementById('total-display');
    const barEl   = document.getElementById('bar-total');
    if(totalEl && totalEl.textContent.startsWith('$0')) totalEl.textContent = '信用卡定期定額繳費';
    if(barEl   && barEl.textContent.startsWith('$0'))   barEl.textContent   = '信用卡扣款';
})();
<?php endif; ?>
// 若從 preview 返回,OTP 已驗過,直接標記
<?php if (!empty($_GET['from_preview'])): ?>
(function(){
    // ── DEBUG:列出所有 ys_ 開頭的 sessionStorage key ──
    const ssDebug = {};
    Object.keys(sessionStorage).filter(k=>k.startsWith('ys_')).forEach(k=>{ ssDebug[k]=sessionStorage.getItem(k); });
    console.log('[from_preview] sessionStorage dump:', JSON.stringify(ssDebug, null, 2));

    // ════════════════════════════════════════════════
    // 根本解法:直接從 PHP Session 注入所有資料到 DOM
    // 不依賴 sessionStorage,確保資料一定在
    // ════════════════════════════════════════════════
    const previewData = <?= json_encode([
        'applicant_name'       => $_SESSION['preview']['applicant_name']       ?? '',
        'applicant_phone'      => $_SESSION['preview']['applicant_phone']      ?? '',
        'applicant_homenumber' => $_SESSION['preview']['applicant_homenumber'] ?? '',
        'applicant_usernumber' => $_SESSION['preview']['applicant_usernumber'] ?? '',
        'applicant_email'      => $_SESSION['preview']['applicant_email']      ?? '',
        'id_type'              => $_SESSION['preview']['id_type']              ?? 'twid',
        'plan_type'            => $_SESSION['preview']['fee_type']             ?? '',
        'plan_price'           => (int)($_SESSION['preview']['fee_amount']     ?? 0),
        'otp_verified'         => !empty($_SESSION['otp_verified']),
        // FileMaker 靜態欄位(debug 用)
        '_debug_member'        => $_SESSION['form_member']        ?? 'MISSING',
        '_debug_address'       => $_SESSION['form_memberaddress'] ?? 'MISSING',
        '_debug_speed'         => $_SESSION['form_speed']         ?? 'MISSING',
        '_debug_pppoeid'       => $_SESSION['form_pppoeid']       ?? 'MISSING',
    ]) ?>;

    // 1. 申請人欄位直接填入 DOM
    const fieldMap = {
        'applicant_name':       'applicant_name',
        'applicant_phone':      'applicant_phone',
        'applicant_homenumber': 'applicant_homenumber',
        'applicant_usernumber': 'applicant_usernumber',
        'applicant_email':      'applicant_email',
    };
    Object.entries(fieldMap).forEach(([key, id]) => {
        const el = document.getElementById(id);
        if(el && previewData[key]) el.value = previewData[key];
    });

    // 2. 身分證類型
    if(previewData.id_type){
        const el = document.getElementById('id-type');
        if(el) el.value = previewData.id_type;
    }

    // 3. Email OTP 狀態
    if(previewData.otp_verified || <?= !empty($_SESSION['otp_verified']) ? 'true' : 'false' ?>){
        otpVerified = true;
        const emailEl = document.getElementById('applicant_email');
        if(emailEl) emailEl.readOnly = true;
        const hint = document.getElementById('email-hint');
        if(hint){ hint.textContent='✔ 信箱已驗證'; hint.className='hint hint-ok'; }
        const btn = document.getElementById('send-otp-btn');
        if(btn) btn.style.display='none';
        SS.set('otp_verified','1');
    }

    // 4. 費率方案 — 直接從 PHP Session 的 fee_type 選中對應 card
    if(previewData.plan_type){
        const card = document.querySelector('.plan-card[data-key="'+previewData.plan_type+'"]');
        if(card){
            document.querySelectorAll('.plan-card').forEach(c=>c.classList.remove('selected'));
            card.classList.add('selected');
            selectedPlanType  = previewData.plan_type;
            selectedPlanPrice = previewData.plan_price || (parseInt(card.dataset.price)||0);
            const clearWrap1 = document.getElementById('plan-clear-wrap');
            if(clearWrap1) clearWrap1.style.display = 'block';
            console.log('[from_preview] 已還原費率:', selectedPlanType, selectedPlanPrice);
        } else {
            console.warn('[from_preview] 找不到費率 card:', previewData.plan_type);
        }
    }

    // 5. 同步到 sessionStorage(確保下次重整也能還原)
    SS.set('applicant_name',       previewData.applicant_name);
    SS.set('applicant_phone',      previewData.applicant_phone);
    SS.set('applicant_homenumber', previewData.applicant_homenumber);
    SS.set('applicant_usernumber', previewData.applicant_usernumber);
    SS.set('applicant_email',      previewData.applicant_email);
    SS.set('id_type',              previewData.id_type);
    SS.set('plan_type',            previewData.plan_type);
    SS.set('plan_price',           previewData.plan_price);

    // 5b. 設備與加收 — 從 PHP Session 補回 sessionStorage
    // (這些是 JS 存的,若 sessionStorage 已有就不覆蓋)
    <?php
    $prevInfoDev   = $_SESSION['preview']['info_devices']   ?? '[]';
    $prevRouterDev = $_SESSION['preview']['router_devices'] ?? '[]';
    $prevUseDev    = $_SESSION['preview']['use_devices']    ?? '[]';
    $prevExtras    = $_SESSION['preview']['extra_fees']     ?? '[]';
    ?>
    (function(){
        const devSessions = {
            'info_list':    <?= json_encode(json_decode($prevInfoDev,   true) ?: []) ?>,
            'router_list':  <?= json_encode(json_decode($prevRouterDev, true) ?: []) ?>,
            'use_list':     <?= json_encode(json_decode($prevUseDev,    true) ?: []) ?>,
            'extras':       <?= json_encode(json_decode($prevExtras,    true) ?: []) ?>,
        };
        // 強制覆蓋 SS
        Object.entries(devSessions).forEach(([k, v]) => SS.set(k, v));

        // ── 資訊箱設備還原 ──
        document.querySelectorAll('#info-container .info-group').forEach(g=>g.remove());
        devSessions['info_list'].forEach(item => {
            addInfoGroup();
            const allG = document.querySelectorAll('#info-container .info-group');
            const g = allG[allG.length-1]; if(!g) return;
            const sel = g.querySelector('select.dev-select');
            if(sel){ sel.value = item.device; onInfoChange(sel); }
            const qtySel = g.querySelector('select.info-qty-sel');
            if(qtySel) qtySel.value = item.qty || '1';
        });

        // ── 網路設備還原 ──
        document.querySelectorAll('#router-container .router-group').forEach(g=>g.remove());
        devSessions['router_list'].forEach(item => {
            addRouterGroup();
            const allG = document.querySelectorAll('#router-container .router-group');
            const g = allG[allG.length-1]; if(!g) return;
            const sel = g.querySelector('select.dev-select');
            if(sel){ sel.value = item.device; onRouterChange(sel); }
            const qtySel = g.querySelector('select.router-qty-sel');
            if(qtySel) qtySel.value = item.qty || '1';
            if(item.tvBrand){ const tb=g.querySelector('select.tv-brand-sel'); if(tb) tb.value=item.tvBrand; }
            if(item.tvOther){ const to=g.querySelector('input.tv-other-inp'); if(to){ to.value=item.tvOther; to.style.display='inline-block'; } }
        });

        // ── 使用設備還原 ──
        document.querySelectorAll('#use-container > .use-group').forEach(g=>g.remove());
        updateAddUseBtn();
        devSessions['use_list'].forEach(item => {
            addUseGroup();
            const allUse = document.querySelectorAll('#use-container > .use-group');
            const g = allUse[allUse.length-1]; if(!g) return;
            const idx = g.dataset.idx;

            const purposeSel = document.getElementById('use-purpose-'+idx);
            if(purposeSel && item.purpose){
                purposeSel.value = item.purpose;
                onUsePurposeChange(idx);
            }
            const deviceSel = document.getElementById('use-device-'+idx);
            if(deviceSel && item.device){
                deviceSel.value = item.device;
                onUseDeviceChange(idx);
            }
            const sn = document.getElementById('use-sn-'+idx);
            if(sn) sn.value = item.sn || '';
            if(item.purpose === 'borrow'){
                const dp = document.getElementById('use-dp-'+idx);
                if(dp) dp.value = item.dp || '1000';
            }
            if(item.purpose === 'node'){
                const np = document.getElementById('use-node-pos-'+idx);
                if(np) np.value = item.nodePos || '';
            }
        });
        calcDeposit();

        // ── 加收項目還原 ──
        document.querySelectorAll('#extra-container .extra-row').forEach(r=>r.remove());
        devSessions['extras'].forEach(item => {
            addExtraRow();
            const rows = document.querySelectorAll('#extra-container .extra-row');
            const row = rows[rows.length-1]; if(!row) return;
            const idx = row.dataset.idx;
            const sel = row.querySelector('.extra-select');
            if(sel){ sel.value = item.type; onExtraTypeChange(sel, idx); }
            const inp = row.querySelector('.extra-amount');
            if(inp) inp.value = (item.amt != null) ? String(item.amt) : '';
            if(item.sn){ const snInp=document.getElementById('extra-sn-inp-'+idx); if(snInp){ snInp.value=String(item.sn); snInp.style.display='inline-block'; } }
        });
        calcTotal();
    })();

    // 6. 贈送月份 — 從 PHP Session 注入
    <?php
    $sessionGifts = [];
    if (!empty($_SESSION['preview']['gift_months'])) {
        $sessionGifts = json_decode($_SESSION['preview']['gift_months'], true) ?: [];
    }
    ?>
    const fromPreviewGifts = <?= json_encode($sessionGifts) ?>;
    if(fromPreviewGifts && fromPreviewGifts.length > 0){
        document.querySelectorAll('#gift-container .gift-group').forEach(g => g.remove());
        const gc = document.getElementById('gift-container');
        if(!gc.querySelector('.gift-group')){
            gc.innerHTML = '<div class="gift-empty" style="padding:12px 14px;font-size:13px;color:var(--color-text-tert,#999)">目前無贈送月份</div>';
        }
        fromPreviewGifts.forEach(function(item){
            restoreGiftGroup(item.reason, item.months, item.intro || '', item.network || '');
        });
        rebuildGiftOptions();
        SS.set('gifts', fromPreviewGifts);
    }

    // 7. 費率再次確認(from_preview 時 restoreFromSS 可能已還原,這裡再補一次確保正確)
    if(previewData.plan_type){
        const card = document.querySelector('.plan-card[data-key="'+previewData.plan_type+'"]');
        if(card && selectedPlanType !== previewData.plan_type){
            document.querySelectorAll('.plan-card').forEach(c=>c.classList.remove('selected'));
            card.classList.add('selected');
            selectedPlanType  = previewData.plan_type;
            selectedPlanPrice = previewData.plan_price || (parseInt(card.dataset.price)||0);
        }
        const clearWrap2 = document.getElementById('plan-clear-wrap');
        if(clearWrap2 && selectedPlanType) clearWrap2.style.display = 'block';
    }

    // 8. 最後重新計算,確保日期、金額同步,然後存入 SS
    calcDates();
    calcTotal();
    // 手動存入 plan_type/plan_price,避免 saveAllToSS 因時序問題存入空值
    SS.set('plan_type',  selectedPlanType);
    SS.set('plan_price', selectedPlanPrice);
    saveAllToSS();
    console.log('[from_preview] 還原完成,plan_type=', selectedPlanType);
})();
<?php endif; ?>
</script>

</body>
</html>