<?php

require_once __DIR__ . '/builder_frontend_runtime.php';

if (!function_exists('cmsRenderFrontendBuilderOverlay')) {
    function cmsRenderFrontendBuilderOverlay(array $options = [])
    {
        $boot = [
            'edit_mode' => !empty($options['edit_mode']),
            'can_operate' => !empty($options['can_operate']),
            'builder_type' => (string) ($options['builder_type'] ?? 'home'),
            'builder_ref_id' => intval($options['builder_ref_id'] ?? 0),
            'builder_page_id' => intval($options['builder_page_id'] ?? 0),
            'builder_version' => intval($options['builder_version'] ?? 0),
            'builder_token' => (string) ($options['builder_token'] ?? ''),
            'csrf_token' => (string) ($options['csrf_token'] ?? ''),
            'api_url' => (string) ($options['api_url'] ?? '/admin/main.php?act=builder_api'),
            'upload_url' => (string) ($options['upload_url'] ?? '/admin/main.php?act=upload'),
            'exit_url' => (string) ($options['exit_url'] ?? '/'),
            'page_title' => (string) ($options['page_title'] ?? ''),
            'layout' => is_array($options['layout'] ?? null) ? $options['layout'] : ['page' => [], 'sections' => []],
            'published_layout' => is_array($options['published_layout'] ?? null) ? $options['published_layout'] : null,
            'default_layout' => is_array($options['default_layout'] ?? null) ? $options['default_layout'] : ['page' => [], 'sections' => []],
            'definitions' => is_array($options['definitions'] ?? null) ? $options['definitions'] : [],
            'column_links' => is_array($options['column_links'] ?? null) ? array_values($options['column_links']) : [],
        ];

        if (!$boot['edit_mode'] && cmsBuilderCanUseLightweightRuntime($boot['layout'])) {
            return cmsRenderFrontendBuilderRuntime($options);
        }

        ob_start();
        ?>
<style id="cms-frontend-builder-style">
html.cms-frontend-builder-edit{scroll-behavior:smooth}
html.cms-frontend-builder-edit body{padding-top:64px}
html.cms-builder-shell-frame,html.cms-builder-shell-frame body{height:auto;min-height:100%}
body.cms-builder-device-mobile{margin:0 auto;background:#fff}
body.cms-builder-device-tablet{max-width:768px;margin:0 auto;background:#fff;box-shadow:0 0 0 1px rgba(148,163,184,.22),0 20px 60px rgba(15,23,42,.12)}
.cms-builder-topbar{position:fixed;left:0;right:0;top:0;z-index:9000;height:64px;display:flex;align-items:center;justify-content:space-between;gap:16px;padding:0 18px;background:rgba(15,23,42,.96);backdrop-filter:blur(14px);color:#fff;box-shadow:0 18px 44px rgba(15,23,42,.28)}
.cms-builder-topbar-left{display:flex;align-items:center;gap:14px;min-width:0}
.cms-builder-topbar-title{font:700 15px/1.2 "Microsoft YaHei",sans-serif}
.cms-builder-topbar-meta{margin-top:4px;font:500 12px/1.2 "Microsoft YaHei",sans-serif;color:rgba(255,255,255,.72)}
.cms-builder-topbar-actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.cms-builder-presence{display:inline-flex;align-items:center;gap:7px;max-width:210px;padding:7px 10px;border:1px solid rgba(255,255,255,.12);border-radius:999px;background:rgba(255,255,255,.08);color:rgba(255,255,255,.78);font:600 11px/1.2 "Microsoft YaHei",sans-serif;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cms-builder-presence:before{content:"";width:7px;height:7px;flex:0 0 7px;border-radius:50%;background:#34d399;box-shadow:0 0 0 3px rgba(52,211,153,.14)}.cms-builder-presence.has-others{border-color:rgba(251,191,36,.35);background:rgba(251,191,36,.12);color:#fde68a}.cms-builder-presence.has-others:before{background:#fbbf24;box-shadow:0 0 0 3px rgba(251,191,36,.16)}
.cms-builder-btn{appearance:none;border:0;border-radius:12px;padding:10px 14px;cursor:pointer;font:600 13px/1 "Microsoft YaHei",sans-serif}
.cms-builder-ui-hidden{display:none!important}
.cms-builder-btn[disabled],.cms-builder-hover-edit[disabled],.cms-builder-hover-icon[disabled],.cms-builder-panel-tool[disabled]{opacity:.45;cursor:not-allowed}
.cms-builder-btn-primary{background:#f97316;color:#fff}
.cms-builder-btn-secondary{background:rgba(255,255,255,.12);color:#fff}
.cms-builder-btn-light{background:#f8fafc;color:#0f172a;border:1px solid #cbd5e1}
.cms-builder-btn-danger{background:#dc2626;color:#fff;box-shadow:0 12px 24px rgba(220,38,38,.3)}
.cms-builder-btn-danger:hover{background:#b91c1c}
.cms-builder-dock{position:fixed;left:18px;top:118px;z-index:9010;display:flex;flex-direction:column;gap:16px;cursor:grab}
.cms-builder-dock.is-hidden{display:none}
.cms-builder-dock.is-dragging{cursor:grabbing}
.cms-builder-dock-btn{display:flex;align-items:center;gap:12px;min-width:104px;padding:14px 18px;border:0;border-radius:999px;background:linear-gradient(180deg,#6d8df8,#5f7eeb);color:#fff;box-shadow:0 10px 22px rgba(78,108,218,.34), inset 0 1px 0 rgba(255,255,255,.24);cursor:pointer;font:700 14px/1 "Microsoft YaHei",sans-serif}
.cms-builder-dock-btn.is-active{background:linear-gradient(180deg,#4f72eb,#3f60d9);transform:translateX(4px)}
.cms-builder-dock-btn-icon{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;flex:0 0 18px}
.cms-builder-dock-btn-icon svg{width:18px;height:18px;stroke:currentColor}
.cms-builder-panel{position:fixed;top:82px;z-index:9090;width:308px;max-height:calc(100vh - 96px);display:flex;flex-direction:column;border-radius:18px;background:rgba(255,255,255,.96);box-shadow:0 22px 54px rgba(15,23,42,.18);backdrop-filter:blur(12px);border:1px solid rgba(148,163,184,.18);overflow:hidden}
.cms-builder-panel-left{left:14px;width:338px;background:#fff}
.cms-builder-panel-right{right:14px}
.cms-builder-settings-backdrop{position:fixed;inset:0;z-index:9095;background:rgba(15,23,42,.42);backdrop-filter:blur(2px)}
.cms-builder-settings-backdrop.is-hidden{display:none}
.cms-builder-history-modal{position:fixed;inset:0;z-index:9110;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(15,23,42,.5);backdrop-filter:blur(5px)}.cms-builder-history-modal.is-hidden{display:none}.cms-builder-history-card{width:min(680px,100%);max-height:82vh;overflow:hidden;border-radius:20px;background:#fff;box-shadow:0 30px 80px rgba(15,23,42,.28)}.cms-builder-history-head{display:flex;align-items:center;justify-content:space-between;padding:20px 22px;border-bottom:1px solid #e8edf3}.cms-builder-history-head h2{margin:0;color:#172033;font:700 17px/1.3 "Microsoft YaHei",sans-serif}.cms-builder-history-head p{margin:5px 0 0;color:#8a98ad;font:500 11px/1.5 "Microsoft YaHei",sans-serif}.cms-builder-history-list{max-height:60vh;overflow:auto;padding:10px}.cms-builder-history-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:14px;align-items:center;padding:13px 12px;border-bottom:1px solid #edf2f7}.cms-builder-history-row:last-child{border-bottom:0}.cms-builder-history-row strong,.cms-builder-history-row span{display:block}.cms-builder-history-row strong{color:#25324a;font:700 13px/1.4 "Microsoft YaHei",sans-serif}.cms-builder-history-row span{margin-top:4px;color:#94a3b8;font:500 11px/1.4 "Microsoft YaHei",sans-serif}
.cms-builder-preflight-modal{position:fixed;inset:0;z-index:2147482900;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(15,23,42,.56);backdrop-filter:blur(6px)}
.cms-builder-preflight-modal.is-hidden{display:none}
.cms-builder-preflight-card{width:min(860px,100%);max-height:86vh;overflow:hidden;border:1px solid rgba(226,232,240,.94);border-radius:22px;background:#f8fafc;box-shadow:0 32px 90px rgba(15,23,42,.32)}
.cms-builder-preflight-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:20px 22px;border-bottom:1px solid #e2e8f0;background:#fff}
.cms-builder-preflight-head h2{margin:0;color:#172033;font:800 18px/1.35 "Microsoft YaHei",sans-serif}
.cms-builder-preflight-head p{margin:6px 0 0;color:#64748b;font:500 12px/1.7 "Microsoft YaHei",sans-serif}
.cms-builder-preflight-body{max-height:calc(86vh - 150px);overflow:auto;padding:18px}
.cms-builder-preflight-summary{display:grid;grid-template-columns:minmax(150px,1.35fr) repeat(3,minmax(100px,1fr));gap:10px;margin-bottom:14px}
.cms-builder-preflight-score,.cms-builder-preflight-stat{padding:14px 15px;border:1px solid #e2e8f0;border-radius:14px;background:#fff}
.cms-builder-preflight-score strong{display:block;color:#0f172a;font:900 28px/1 "Microsoft YaHei",sans-serif}.cms-builder-preflight-score span,.cms-builder-preflight-stat span{display:block;margin-top:7px;color:#64748b;font:600 11px/1.4 "Microsoft YaHei",sans-serif}
.cms-builder-preflight-stat strong{display:block;color:#334155;font:900 22px/1 "Microsoft YaHei",sans-serif}.cms-builder-preflight-stat.is-error strong{color:#dc2626}.cms-builder-preflight-stat.is-warning strong{color:#d97706}.cms-builder-preflight-stat.is-info strong{color:#2563eb}
.cms-builder-preflight-result{margin-bottom:12px;padding:12px 14px;border:1px solid #bbf7d0;border-radius:13px;background:#f0fdf4;color:#047857;font:700 12px/1.7 "Microsoft YaHei",sans-serif}.cms-builder-preflight-result.has-errors{border-color:#fecaca;background:#fef2f2;color:#b91c1c}
.cms-builder-preflight-list{display:grid;gap:9px}.cms-builder-preflight-item{display:grid;grid-template-columns:82px minmax(0,1fr);gap:12px;padding:14px;border:1px solid #e2e8f0;border-radius:14px;background:#fff}.cms-builder-preflight-level{align-self:start;padding:5px 8px;border-radius:999px;background:#eff6ff;color:#1d4ed8;text-align:center;font:800 11px/1.2 "Microsoft YaHei",sans-serif}.cms-builder-preflight-item.is-error .cms-builder-preflight-level{background:#fef2f2;color:#dc2626}.cms-builder-preflight-item.is-warning .cms-builder-preflight-level{background:#fff7ed;color:#c2410c}.cms-builder-preflight-copy strong{display:block;color:#172033;font:800 13px/1.4 "Microsoft YaHei",sans-serif}.cms-builder-preflight-copy p{margin:5px 0 0;color:#64748b;font:500 12px/1.65 "Microsoft YaHei",sans-serif}.cms-builder-preflight-fix{margin-top:7px;color:#2563eb;font:700 11px/1.5 "Microsoft YaHei",sans-serif}.cms-builder-preflight-path{margin-top:4px;color:#94a3b8;font:500 10px/1.4 Consolas,monospace;word-break:break-all}
.cms-builder-preflight-actions{display:flex;justify-content:flex-end;padding:14px 18px;border-top:1px solid #e2e8f0;background:#fff}
.cms-builder-confirm-modal{position:fixed;inset:0;z-index:9130;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(15,23,42,.56);backdrop-filter:blur(6px)}.cms-builder-confirm-modal.is-hidden{display:none}.cms-builder-confirm-card{width:min(440px,100%);padding:24px;border:1px solid rgba(226,232,240,.9);border-radius:20px;background:#fff;box-shadow:0 30px 90px rgba(15,23,42,.3)}.cms-builder-confirm-card h2{margin:0;color:#172033;font:700 18px/1.35 "Microsoft YaHei",sans-serif}.cms-builder-confirm-card p{margin:10px 0 0;color:#64748b;font:500 13px/1.75 "Microsoft YaHei",sans-serif}.cms-builder-confirm-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:22px}.cms-builder-confirm-actions .cms-builder-btn-secondary{background:#eef2f7;color:#334155}.cms-builder-confirm-card.is-danger .cms-builder-confirm-submit{background:#dc2626}.cms-builder-status[data-state="conflict"],.cms-builder-status[data-state="offline"]{color:#fbbf24}
.cms-builder-toast-stack{position:fixed;z-index:9120;right:18px;top:78px;display:grid;width:min(360px,calc(100vw - 28px));gap:9px}.cms-builder-toast{display:grid;grid-template-columns:10px minmax(0,1fr) 22px;gap:10px;align-items:center;padding:13px;border:1px solid rgba(226,232,240,.9);border-radius:14px;background:rgba(255,255,255,.97);box-shadow:0 18px 45px rgba(15,23,42,.18);color:#475569;font:600 12px/1.6 "Microsoft YaHei",sans-serif;animation:cms-builder-toast-in .2s ease}.cms-builder-toast>i{width:8px;height:8px;border-radius:50%;background:#2563eb}.cms-builder-toast.is-error>i{background:#dc2626}.cms-builder-toast.is-success>i{background:#059669}.cms-builder-toast.is-warning>i{background:#f59e0b}.cms-builder-toast button{border:0;background:transparent;color:#94a3b8;font-size:18px;cursor:pointer}@keyframes cms-builder-toast-in{from{opacity:0;transform:translateX(12px)}}
.cms-builder-settings-modal{z-index:9100;width:338px;max-width:calc(100vw - 48px);max-height:calc(100vh - 120px);background:#fff;box-shadow:0 28px 80px rgba(15,23,42,.28);border:1px solid rgba(226,232,240,.96)}
.cms-builder-settings-modal .cms-builder-panel-head{position:relative;padding:18px 20px 14px;background:linear-gradient(180deg,#fff,#f8fbff);overflow:hidden}
.cms-builder-settings-modal .cms-builder-panel-head:after{content:"芋头CMS";position:absolute;right:18px;top:50%;transform:translateY(-50%);font:800 16px/1 "Microsoft YaHei",sans-serif;letter-spacing:2px;color:rgba(15,23,42,.08);pointer-events:none;user-select:none;white-space:nowrap}
.cms-builder-settings-modal .cms-builder-panel-body{padding:18px;background:#f8fbff}
.cms-builder-panel.is-hidden{display:none}
.cms-builder-panel-head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:16px 16px 12px;border-bottom:1px solid #e2e8f0;background:linear-gradient(180deg,#fff,#f8fafc);cursor:move}
.cms-builder-panel-head-main{position:relative;z-index:1;flex:1;min-width:0}
.cms-builder-settings-modal .cms-builder-panel-tools{position:relative;z-index:1}
.cms-builder-panel-tools{display:flex;align-items:center;gap:8px}
.cms-builder-panel-tool{appearance:none;border:0;background:#e2e8f0;color:#334155;border-radius:999px;padding:8px 12px;min-width:44px;min-height:36px;cursor:pointer;font:700 12px/1 "Microsoft YaHei",sans-serif;display:inline-flex;align-items:center;justify-content:center;position:relative;z-index:4}
.cms-builder-panel-title{font:700 14px/1.2 "Microsoft YaHei",sans-serif;color:#0f172a}
.cms-builder-panel-desc{margin-top:6px;font:500 12px/1.6 "Microsoft YaHei",sans-serif;color:#64748b}
.cms-builder-panel-body{overflow:auto;padding:14px}
.cms-builder-panel-body-library{padding:0}
.cms-builder-no-copy{user-select:none;-webkit-user-select:none}
.cms-builder-no-copy input,.cms-builder-no-copy textarea,.cms-builder-no-copy select{user-select:text;-webkit-user-select:text}
.cms-builder-group{margin-bottom:16px;padding:14px;border:1px solid #e2e8f0;border-radius:14px;background:#fff}
.cms-builder-group:last-child{margin-bottom:0}
.cms-builder-group-title{font:700 13px/1.2 "Microsoft YaHei",sans-serif;color:#0f172a}
.cms-builder-group-desc{margin-top:6px;font:500 12px/1.6 "Microsoft YaHei",sans-serif;color:#64748b}
.cms-builder-list{display:grid;gap:10px;margin-top:12px}
.cms-builder-list button{width:100%;text-align:left;border:1px solid #e2e8f0;background:#fff;border-radius:12px;padding:12px;cursor:pointer}
.cms-builder-list button.is-active{border-color:#2563eb;background:#eff6ff}
.cms-builder-list button.is-dragging{opacity:.45}
.cms-builder-list button.cms-builder-tree-item{position:relative;width:calc(100% - var(--cms-tree-indent,0px));margin-left:var(--cms-tree-indent,0px)}
.cms-builder-list button.cms-builder-tree-item::before{content:"";position:absolute;left:-9px;top:0;bottom:50%;width:7px;border-left:1px solid #cbd5e1;border-bottom:1px solid #cbd5e1;border-radius:0 0 0 5px;pointer-events:none}
.cms-builder-list button.cms-builder-tree-root::before{display:none}
.cms-builder-list-title{font:700 13px/1.3 "Microsoft YaHei",sans-serif;color:#111827}
.cms-builder-list-meta{margin-top:5px;font:500 12px/1.5 "Microsoft YaHei",sans-serif;color:#64748b;word-break:break-all}
.cms-builder-fields{display:grid;gap:12px;margin-top:12px}
.cms-builder-field label{display:block;margin-bottom:6px;font:700 12px/1.2 "Microsoft YaHei",sans-serif;color:#334155}
.cms-builder-field input,.cms-builder-field textarea,.cms-builder-field select{width:100%;box-sizing:border-box;border:1px solid #cbd5e1;border-radius:10px;padding:10px 12px;background:#fff;color:#0f172a;font:500 13px/1.5 "Microsoft YaHei",sans-serif}
.cms-builder-field textarea{min-height:86px;resize:vertical}
.cms-builder-empty{padding:18px;border:1px dashed #cbd5e1;border-radius:14px;background:#fff;color:#64748b;font:500 12px/1.7 "Microsoft YaHei",sans-serif}
.cms-builder-highlight{outline:2px solid rgba(37,99,235,.86)!important;outline-offset:3px!important;cursor:pointer!important}
.cms-builder-selected{outline:3px solid #f97316!important;outline-offset:3px!important;box-shadow:0 0 0 6px rgba(249,115,22,.18)!important}
.cms-builder-free-transform{transform:var(--cms-builder-free-transform, none) var(--cms-builder-origin-transform, none)!important;transform-origin:var(--cms-builder-transform-origin, top left)!important}
.cms-builder-hover-toolbar{position:fixed;z-index:9085;display:flex;align-items:center;gap:0;min-height:40px;padding:6px 8px;border-radius:12px;background:rgba(255,255,255,.98);box-shadow:0 16px 32px rgba(15,23,42,.18);border:1px solid rgba(226,232,240,.96)}
.cms-builder-hover-edit{appearance:none;border:0;background:transparent;color:#0f172a;padding:0 12px;cursor:pointer;font:600 14px/28px "Microsoft YaHei",sans-serif}
.cms-builder-hover-edit:hover{color:#2563eb}
.cms-builder-hover-sep{width:1px;height:18px;background:#e2e8f0}
.cms-builder-hover-icon{width:34px;height:28px;border:0;background:transparent;color:#94a3b8;display:inline-flex;align-items:center;justify-content:center;cursor:pointer}
.cms-builder-hover-icon:hover{color:#4f6fe7}
.cms-builder-hover-icon.is-active{color:#f59e0b}
.cms-builder-hover-icon svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.8}
.cms-builder-resize-frame{position:fixed;box-sizing:border-box;border:1px dashed rgba(37,99,235,.82);background:rgba(59,130,246,.04);pointer-events:none}
.cms-builder-resize-frame.is-selected{border-color:#f97316;background:rgba(249,115,22,.04)}
.cms-builder-move-handle{position:absolute;left:50%;top:-30px;transform:translateX(-50%);height:24px;min-width:92px;padding:0 12px;border:0;border-radius:999px;background:#111827;color:#fff;box-shadow:0 10px 22px rgba(15,23,42,.22);cursor:move;font:700 12px/24px "Microsoft YaHei",sans-serif;pointer-events:auto}
.cms-builder-resize-handle{position:absolute;width:12px;height:12px;border-radius:999px;border:2px solid #fff;background:#2563eb;box-shadow:0 6px 16px rgba(37,99,235,.3);pointer-events:auto}
.cms-builder-resize-frame.is-selected .cms-builder-resize-handle{background:#f97316;box-shadow:0 6px 16px rgba(249,115,22,.28)}
.cms-builder-resize-handle.east{right:-7px;top:50%;margin-top:-6px;cursor:ew-resize}
.cms-builder-resize-handle.south{left:50%;bottom:-7px;margin-left:-6px;cursor:ns-resize}
.cms-builder-resize-handle.corner{right:-7px;bottom:-7px;cursor:nwse-resize}
.cms-builder-resize-handle.scale{left:-7px;top:-7px;cursor:nwse-resize;background:#0f172a;box-shadow:0 6px 16px rgba(15,23,42,.28)}
.cms-builder-is-resizing,.cms-builder-is-resizing *{cursor:nwse-resize!important}
.cms-builder-extra-zone{margin:36px auto 0;max-width:1200px;padding:0 20px}
.cms-builder-extra-item{margin-bottom:18px;padding:24px;border-radius:18px;background:#fff;border:1px dashed #cbd5e1;box-shadow:0 12px 28px rgba(15,23,42,.05)}
.cms-builder-status{display:inline-flex;align-items:center;padding:6px 10px;border-radius:999px;background:rgba(37,99,235,.14);color:#bfdbfe;font:700 11px/1 "Microsoft YaHei",sans-serif}
.cms-builder-status[data-state="saving"]{background:rgba(245,158,11,.18);color:#fde68a}.cms-builder-status[data-state="saved"]{background:rgba(16,185,129,.18);color:#a7f3d0}.cms-builder-status[data-state="error"]{background:rgba(239,68,68,.2);color:#fecaca}.cms-builder-status[data-state="dirty"]{background:rgba(148,163,184,.18);color:#e2e8f0}
.cms-builder-action-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-top:12px}
.cms-builder-action-grid .cms-builder-btn{width:100%}
.cms-builder-badges{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.cms-builder-badge{display:inline-flex;align-items:center;padding:4px 8px;border-radius:999px;background:#e2e8f0;color:#334155;font:700 11px/1 "Microsoft YaHei",sans-serif}
.cms-builder-block-hidden{opacity:.45}
.cms-builder-block-hidden-preview{opacity:.38!important;filter:grayscale(.12);outline:2px dashed rgba(249,115,22,.42)!important;outline-offset:3px!important}
.cms-builder-block-locked{box-shadow:inset 0 0 0 2px rgba(148,163,184,.55)}
.cms-builder-device-switch{display:inline-flex;gap:6px;padding:4px;border-radius:999px;background:rgba(255,255,255,.12)}
.cms-builder-device-switch button{border:0;border-radius:999px;padding:8px 12px;cursor:pointer;background:transparent;color:#fff;font:600 12px/1 "Microsoft YaHei",sans-serif}
.cms-builder-device-switch button.is-active{background:#fff;color:#0f172a}
.cms-builder-canvas-drop{outline:2px dashed #2563eb!important;outline-offset:4px!important}
.cms-builder-design-canvas{position:relative;z-index:9020;width:min(var(--cms-builder-canvas-width,1200px),calc(100vw - 40px));min-height:var(--cms-builder-canvas-height,720px);margin:32px auto 48px;border:1px solid rgba(37,99,235,.22);border-radius:24px;background:linear-gradient(180deg,rgba(248,251,255,.92),rgba(255,255,255,.98));box-shadow:inset 0 0 0 1px rgba(255,255,255,.75),0 24px 58px rgba(15,23,42,.08);overflow:visible}
.cms-builder-design-canvas:before{content:"自由装修画布";position:absolute;left:18px;top:-28px;padding:6px 12px;border-radius:999px;background:#111827;color:#fff;font:700 12px/1 "Microsoft YaHei",sans-serif;box-shadow:0 10px 24px rgba(15,23,42,.18)}
.cms-builder-design-canvas.is-empty{min-height:260px;display:flex;align-items:center;justify-content:center}
.cms-builder-design-canvas:after{content:"";position:absolute;inset:0;border-radius:24px;pointer-events:none;background-image:linear-gradient(to right,rgba(148,163,184,.08) 1px,transparent 1px),linear-gradient(to bottom,rgba(148,163,184,.08) 1px,transparent 1px);background-size:20px 20px;opacity:.6}
.cms-builder-design-empty{padding:26px 28px;border:1px dashed #b9c6e2;border-radius:18px;background:rgba(255,255,255,.82);color:#64748b;text-align:center;font:600 13px/1.8 "Microsoft YaHei",sans-serif}
.cms-builder-canvas-guides{position:absolute;inset:0;pointer-events:none;z-index:95}
.cms-builder-canvas-guide{position:absolute;background:rgba(249,115,22,.85);box-shadow:0 0 0 1px rgba(255,255,255,.72)}
.cms-builder-canvas-guide.is-vertical{top:0;bottom:0;width:1px}
.cms-builder-canvas-guide.is-horizontal{left:0;right:0;height:1px}
.cms-builder-design-block{position:absolute;box-sizing:border-box;min-width:48px;min-height:32px;border:1px solid transparent;border-radius:16px;cursor:move;touch-action:none;user-select:none;overflow:visible}
.cms-builder-design-block:hover{z-index:96!important;border-color:rgba(37,99,235,.52);box-shadow:0 0 0 4px rgba(37,99,235,.08)}
.cms-builder-design-block.is-selected{z-index:98!important;border-color:#f97316;box-shadow:0 0 0 4px rgba(249,115,22,.16),0 18px 38px rgba(15,23,42,.12)}
.cms-builder-design-block.is-hidden-preview{opacity:.38;border-style:dashed;border-color:rgba(249,115,22,.52)}
.cms-builder-design-block.is-hidden-preview .cms-builder-design-block-inner{filter:grayscale(.12)}
.cms-builder-design-block-inner{width:100%;height:100%;box-sizing:border-box;overflow:hidden;border-radius:inherit;background:#fff;pointer-events:none}
.cms-builder-design-block-inner *{pointer-events:none}
.cms-builder-design-block[data-canvas-type="text"] .cms-builder-design-block-inner{padding:18px 20px}
.cms-builder-design-block[data-canvas-type="button"] .cms-builder-design-block-inner{display:flex;align-items:center;justify-content:center;background:transparent}
.cms-builder-design-block img{max-width:100%;height:100%;object-fit:cover;display:block}
.cms-builder-canvas-toolbar{position:absolute;left:50%;top:-40px;transform:translateX(-50%);display:flex;align-items:center;gap:6px;padding:5px;border-radius:999px;background:#111827;box-shadow:0 12px 28px rgba(15,23,42,.22);white-space:nowrap;opacity:0;pointer-events:none;z-index:99}
.cms-builder-design-block.is-selected .cms-builder-canvas-toolbar,.cms-builder-design-block:hover .cms-builder-canvas-toolbar{opacity:1;pointer-events:auto}
.cms-builder-canvas-tool{height:24px;min-width:26px;border:0;border-radius:999px;background:transparent;color:#fff;cursor:pointer;font:700 11px/24px "Microsoft YaHei",sans-serif;padding:0 8px}
.cms-builder-canvas-tool:hover{background:rgba(255,255,255,.14)}
.cms-builder-canvas-tool.is-danger:hover{background:#ef4444}
.cms-builder-canvas-handle{position:absolute;width:12px;height:12px;border:2px solid #fff;border-radius:999px;background:#f97316;box-shadow:0 7px 18px rgba(249,115,22,.3);display:none;z-index:13}
.cms-builder-design-block.is-selected .cms-builder-canvas-handle{display:block}
.cms-builder-canvas-handle.n{left:50%;top:-7px;margin-left:-6px;cursor:ns-resize}
.cms-builder-canvas-handle.e{right:-7px;top:50%;margin-top:-6px;cursor:ew-resize}
.cms-builder-canvas-handle.s{left:50%;bottom:-7px;margin-left:-6px;cursor:ns-resize}
.cms-builder-canvas-handle.w{left:-7px;top:50%;margin-top:-6px;cursor:ew-resize}
.cms-builder-canvas-handle.ne{right:-7px;top:-7px;cursor:nesw-resize}
.cms-builder-canvas-handle.nw{left:-7px;top:-7px;cursor:nwse-resize}
.cms-builder-canvas-handle.se{right:-7px;bottom:-7px;cursor:nwse-resize}
.cms-builder-canvas-handle.sw{left:-7px;bottom:-7px;cursor:nesw-resize}
.cms-builder-canvas-meta{position:absolute;right:10px;bottom:-24px;display:none;padding:4px 8px;border-radius:999px;background:rgba(15,23,42,.82);color:#fff;font:700 11px/1 "Microsoft YaHei",sans-serif;z-index:12}
.cms-builder-design-block.is-selected .cms-builder-canvas-meta{display:block}
.cms-builder-canvas-field-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.cms-builder-canvas-inline-actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-top:12px}
.cms-builder-canvas-inline-actions .cms-builder-btn{width:100%}
.cms-builder-library-shell{display:flex;flex-direction:column;min-height:560px;background:#fff}
.cms-builder-library-topbar{display:flex;align-items:stretch;padding:0 8px;background:linear-gradient(180deg,#657dea,#5670dd);color:#fff;cursor:move}
.cms-builder-library-mode{width:54px;min-width:54px;height:58px;border:0;background:transparent;color:rgba(255,255,255,.82);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;cursor:pointer;font:600 11px/1 "Microsoft YaHei",sans-serif}
.cms-builder-library-mode svg{width:18px;height:18px;stroke:currentColor}
.cms-builder-library-mode.is-active{background:rgba(255,255,255,.12);color:#fff}
.cms-builder-library-close{margin-left:auto;align-self:center;width:44px;height:44px;border:0;border-radius:999px;background:rgba(12,18,54,.18);color:#fff;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;position:relative;z-index:5;flex:0 0 44px}
.cms-builder-library-close:hover,.cms-builder-panel-tool:hover{background:#cbd5e1;color:#0f172a}
.cms-builder-library-close svg{width:18px;height:18px;stroke:currentColor}
.cms-builder-library-content{display:flex;flex-direction:column;flex:1;min-height:0;background:#fff}
.cms-builder-library-tabs{display:flex;align-items:center;gap:28px;padding:0 18px;border-bottom:1px solid #e6ebf5;background:#fff}
.cms-builder-library-tab{position:relative;border:0;background:transparent;padding:16px 0 14px;color:#111827;cursor:pointer;font:500 14px/1 "Microsoft YaHei",sans-serif}
.cms-builder-library-tab.is-active{color:#3b5fe2}
.cms-builder-library-tab.is-active:after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:#5a78ef;border-radius:999px}
.cms-builder-style-shell{display:flex;flex-direction:column;min-height:0;flex:1;background:#fff}
.cms-builder-style-tabs{display:flex;align-items:center;gap:26px;padding:0 18px;border-bottom:1px solid #e6ebf5;background:#fff}
.cms-builder-style-tab{position:relative;border:0;background:transparent;padding:15px 0 13px;color:#111827;cursor:pointer;font:500 14px/1 "Microsoft YaHei",sans-serif}
.cms-builder-style-tab.is-active{color:#3b5fe2}
.cms-builder-style-tab.is-active:after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:#5a78ef;border-radius:999px}
.cms-builder-style-body{flex:1;min-height:0;overflow:auto;padding:14px 16px 18px;background:#fff}
.cms-builder-style-subtitle{margin:0 0 14px;color:#64748b;font:500 12px/1.7 "Microsoft YaHei",sans-serif}
.cms-builder-style-swatches{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:16px}
.cms-builder-style-swatch{width:22px;height:22px;border-radius:999px;border:2px solid transparent;box-shadow:0 1px 0 rgba(255,255,255,.8),0 0 0 1px rgba(15,23,42,.08);cursor:pointer}
.cms-builder-style-swatch.is-active{transform:scale(1.08);box-shadow:0 0 0 2px #fff,0 0 0 4px rgba(59,95,226,.26)}
.cms-builder-style-card-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
.cms-builder-style-preview-card{border:1px solid #e7edf7;border-radius:12px;background:#fff;overflow:hidden;cursor:pointer;box-shadow:0 8px 18px rgba(15,23,42,.04)}
.cms-builder-style-preview-card.is-active{border-color:#6a85ef;box-shadow:0 12px 24px rgba(79,111,231,.14)}
.cms-builder-style-preview-stage{padding:12px;height:94px;background:#fafbfd}
.cms-builder-style-preview-caption{padding:10px 12px;border-top:1px solid #eef2f8;color:#334155;font:600 12px/1.4 "Microsoft YaHei",sans-serif}
.cms-builder-style-blocks{display:grid;gap:12px}
.cms-builder-style-blockmap{padding:12px;border:1px solid #e7edf7;border-radius:14px;background:#fbfcff}
.cms-builder-style-map-bar,.cms-builder-style-map-main,.cms-builder-style-map-footer{display:flex;align-items:center;justify-content:center;border-radius:10px;font:600 12px/1 "Microsoft YaHei",sans-serif}
.cms-builder-style-map-bar{height:34px;background:#eef2ff;color:#5b74e8}
.cms-builder-style-map-main{height:74px;margin-top:10px;border:1px dashed #cbd5e1;background:#fff;color:#94a3b8}
.cms-builder-style-map-footer{height:34px;margin-top:10px;background:#18b7bf;color:#fff}
.cms-builder-style-quick-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px}
.cms-builder-style-quick-btn{border:1px solid #dbe3f4;border-radius:10px;background:#fff;padding:10px 8px;color:#334155;cursor:pointer;font:600 12px/1.2 "Microsoft YaHei",sans-serif}
.cms-builder-style-quick-btn:hover{border-color:#8ea4f7;color:#3b5fe2}
.cms-builder-style-choice{display:flex;flex-wrap:wrap;gap:10px}
.cms-builder-style-pill{border:1px solid #dbe3f4;border-radius:999px;background:#fff;padding:8px 14px;color:#334155;cursor:pointer;font:600 12px/1 "Microsoft YaHei",sans-serif}
.cms-builder-style-pill.is-active{border-color:#6a85ef;background:#eef2ff;color:#3b5fe2}
.cms-builder-style-range{display:flex;align-items:center;gap:10px}
.cms-builder-style-range input[type=range]{flex:1}
.cms-builder-style-preview-layout{padding:14px;border:1px solid #e7edf7;border-radius:14px;background:#fff}
.cms-builder-style-layout-frame{padding:10px;border-radius:12px;background:#fefce8}
.cms-builder-style-layout-top{height:32px;border-radius:8px;background:#f5f5f5;display:flex;align-items:center;justify-content:center;color:#555;font:600 12px/1 "Microsoft YaHei",sans-serif}
.cms-builder-style-layout-row{display:grid;grid-template-columns:54px 1fr 54px;gap:10px;margin-top:10px}
.cms-builder-style-layout-side,.cms-builder-style-layout-center,.cms-builder-style-layout-bottom{display:flex;align-items:center;justify-content:center;border-radius:8px;font:600 12px/1 "Microsoft YaHei",sans-serif}
.cms-builder-style-layout-side{height:86px;background:#18b7bf;color:#fff}
.cms-builder-style-layout-center{height:86px;background:#f5f5f5;color:#555}
.cms-builder-style-layout-bottom{height:28px;margin-top:10px;background:#f5f5f5;color:#555}
.cms-builder-style-note-box{padding:12px 14px;border:1px solid #dbe7ff;border-radius:12px;background:#f8fbff;color:#64748b;font:500 12px/1.8 "Microsoft YaHei",sans-serif}
.cms-builder-library-scroll{flex:1;min-height:0;overflow:auto;padding:12px 14px 18px}
.cms-builder-library-section{margin-bottom:12px}
.cms-builder-library-section:last-child{margin-bottom:0}
.cms-builder-library-group{padding:6px 4px 0}
.cms-builder-library-group-toggle{width:100%;display:flex;align-items:center;gap:8px;border:0;background:transparent;padding:8px 0;color:#111827;cursor:pointer;text-align:left;font:600 14px/1.2 "Microsoft YaHei",sans-serif}
.cms-builder-library-group-toggle svg{width:12px;height:12px;stroke:#7b88a8;transition:transform .18s ease}
.cms-builder-library-group-toggle.is-collapsed svg{transform:rotate(-90deg)}
.cms-builder-library-group-line{flex:1;height:1px;background:#e6ebf5}
.cms-builder-library-group-search{width:28px;height:28px;border:0;background:transparent;color:#9aa6c2;display:inline-flex;align-items:center;justify-content:center;cursor:default}
.cms-builder-library-group-search svg{width:18px;height:18px;stroke:currentColor}
.cms-builder-library-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:18px 10px;padding:14px 2px 4px}
.cms-builder-library-item{border:0;background:transparent;display:flex;flex-direction:column;align-items:center;justify-content:flex-start;gap:8px;padding:4px 0;color:#4a556f;cursor:pointer}
.cms-builder-library-item:hover{color:#3b5fe2}
.cms-builder-library-item-icon{width:34px;height:34px;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;color:#98a3bd}
.cms-builder-library-item:hover .cms-builder-library-item-icon{color:#6b84eb;background:#eef2ff}
.cms-builder-library-item-icon svg{width:28px;height:28px;stroke:currentColor}
.cms-builder-library-item-label{font:500 12px/1.4 "Microsoft YaHei",sans-serif;color:inherit;text-align:center}
.cms-builder-library-item.is-disabled{opacity:.42;cursor:not-allowed}
.cms-builder-library-item.is-disabled:hover{color:#4a556f}
.cms-builder-library-item.is-disabled:hover .cms-builder-library-item-icon{background:transparent;color:#98a3bd}
.cms-builder-library-empty{padding:16px;border:1px dashed #d7deef;border-radius:14px;background:#f8faff;color:#64748b;font:500 12px/1.8 "Microsoft YaHei",sans-serif}
.cms-builder-library-note{padding:14px 16px;border-bottom:1px solid #eef2f8;background:#f9fbff;color:#64748b;font:500 12px/1.7 "Microsoft YaHei",sans-serif}
.cms-builder-column-toolbar{display:flex;align-items:center;gap:10px;padding:14px 16px 10px}
.cms-builder-column-toolbar label{flex:0 0 auto;color:#334155;font:600 13px/1 "Microsoft YaHei",sans-serif}
.cms-builder-column-select{flex:1;min-width:0;height:36px;border:1px solid #d8e0f0;border-radius:10px;padding:0 12px;background:#fff;color:#0f172a;font:500 13px/1 "Microsoft YaHei",sans-serif}
.cms-builder-column-list{display:grid;gap:12px;padding:6px 14px 18px}
.cms-builder-column-card{display:flex;align-items:center;gap:10px;padding:10px 12px;border:1px solid #edf1f7;border-radius:12px;background:#fff;box-shadow:0 6px 18px rgba(15,23,42,.03)}
.cms-builder-column-card.is-active{border-color:#88a0f8;background:#f8faff}
.cms-builder-column-card.is-hidden{opacity:.72}
.cms-builder-column-card-main{flex:1;min-width:0;border:0;background:transparent;display:flex;align-items:center;gap:10px;padding:0;cursor:pointer;text-align:left}
.cms-builder-column-card-caret{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;color:#94a3b8}
.cms-builder-column-card-caret svg{width:14px;height:14px;stroke:currentColor}
.cms-builder-column-card-label{display:block;color:#0f172a;font:500 13px/1.5 "Microsoft YaHei",sans-serif;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cms-builder-column-card-meta{display:block;margin-top:2px;color:#94a3b8;font:500 11px/1.4 "Microsoft YaHei",sans-serif}
.cms-builder-column-card-actions{display:flex;align-items:center;gap:8px}
.cms-builder-column-card-action{width:28px;height:28px;border:0;border-radius:999px;background:#f8fafc;color:#7c8aa5;display:inline-flex;align-items:center;justify-content:center;cursor:pointer}
.cms-builder-column-card-action:hover{background:#eef2ff;color:#4f6fe7}
.cms-builder-column-card-action.is-danger:hover{background:#fff1f2;color:#e11d48}
.cms-builder-column-card-action svg{width:16px;height:16px;stroke:currentColor}
.cms-builder-material-wrap{display:flex;flex-direction:column;min-height:0}
.cms-builder-material-searchbar{display:flex;align-items:center;gap:10px;padding:14px 16px 10px}
.cms-builder-material-search{position:relative;flex:1;min-width:0}
.cms-builder-material-search input{width:100%;box-sizing:border-box;height:38px;border:1px solid #d9e2f1;border-radius:999px;padding:0 42px 0 14px;background:#fff;color:#0f172a;font:500 13px/1 "Microsoft YaHei",sans-serif}
.cms-builder-material-search-icon{position:absolute;right:12px;top:50%;transform:translateY(-50%);color:#9aa6c2}
.cms-builder-material-search-icon svg{width:18px;height:18px;stroke:currentColor}
.cms-builder-material-gear{width:36px;height:36px;border:1px solid #d9e2f1;border-radius:999px;background:#fff;color:#8b98b2;display:inline-flex;align-items:center;justify-content:center;cursor:pointer}
.cms-builder-material-gear svg{width:16px;height:16px;stroke:currentColor}
.cms-builder-material-scopes{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0;padding:0 16px}
.cms-builder-material-scope{height:38px;border:1px solid #dfe6f6;background:#f5f7fc;color:#64748b;cursor:pointer;font:600 13px/1 "Microsoft YaHei",sans-serif}
.cms-builder-material-scope:first-child{border-radius:10px 0 0 10px}
.cms-builder-material-scope:last-child{border-left:0;border-radius:0 10px 10px 0}
.cms-builder-material-scope.is-active{background:#dfe6ff;color:#3e63e0}
.cms-builder-material-cats{display:flex;align-items:center;gap:14px;padding:10px 16px 6px;overflow:auto;white-space:nowrap}
.cms-builder-material-cat{border:0;background:transparent;padding:6px 0;color:#334155;cursor:pointer;font:500 13px/1 "Microsoft YaHei",sans-serif}
.cms-builder-material-cat.is-active{color:#3b5fe2}
.cms-builder-material-cats-more{margin-left:auto;color:#94a3b8}
.cms-builder-material-cats-more svg{width:16px;height:16px;stroke:currentColor}
.cms-builder-material-list{display:grid;gap:16px;padding:12px 16px 18px}
.cms-builder-material-card{border:1px solid #e8edf6;border-radius:18px;background:#fff;box-shadow:0 10px 26px rgba(15,23,42,.05);overflow:hidden}
.cms-builder-material-card-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:14px 16px 8px}
.cms-builder-material-card-title{font:700 14px/1.4 "Microsoft YaHei",sans-serif;color:#0f172a}
.cms-builder-material-chip{display:inline-flex;align-items:center;justify-content:center;padding:4px 10px;border-radius:999px;background:#eef2ff;color:#4664df;font:700 11px/1 "Microsoft YaHei",sans-serif;white-space:nowrap}
.cms-builder-material-preview{margin:0 16px 12px;border:1px solid #edf2fb;border-radius:14px;background:linear-gradient(180deg,#fff,#fbfcff);overflow:hidden}
.cms-builder-material-preview-canvas{padding:12px;background:#fff}
.cms-builder-material-preview-surface{min-height:158px;border-radius:12px;background:linear-gradient(180deg,#ffffff,#f8fbff);padding:18px;box-shadow:inset 0 0 0 1px rgba(226,232,240,.8)}
.cms-builder-material-preview-header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}
.cms-builder-material-preview-title{font:700 14px/1.4 "Microsoft YaHei",sans-serif;color:#0f172a}
.cms-builder-material-preview-sub{font:500 11px/1.5 "Microsoft YaHei",sans-serif;color:#94a3b8}
.cms-builder-material-preview-compact{height:108px;border-radius:12px;background:linear-gradient(180deg,#fff,#f8fbff);padding:8px;box-shadow:inset 0 0 0 1px rgba(226,232,240,.8);overflow:hidden}
.cms-builder-material-preview-compact.scaled{padding:0;background:#fff}
.cms-builder-material-preview-compact-thumb{width:100%;height:100%;display:block;object-fit:cover;border-radius:10px}
.cms-builder-material-preview-compact-grid{display:grid;grid-template-columns:100px 1fr;gap:10px;height:100%}
.cms-builder-material-preview-compact-stack{display:grid;gap:7px}
.cms-builder-material-preview-compact-title{font:700 12px/1.4 "Microsoft YaHei",sans-serif;color:#0f172a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.cms-builder-material-preview-compact-text{font:500 11px/1.5 "Microsoft YaHei",sans-serif;color:#64748b;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.cms-builder-material-preview-compact-pills{display:flex;flex-wrap:wrap;gap:6px}
.cms-builder-material-preview-compact-pill{display:inline-flex;align-items:center;padding:4px 8px;border-radius:999px;background:#eef2ff;color:#4964d8;font:700 10px/1 "Microsoft YaHei",sans-serif}
.cms-builder-material-preview-compact-boxes{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;height:100%}
.cms-builder-material-preview-compact-box{border-radius:10px;background:#f8fbff;border:1px solid #e7edf7;display:flex;align-items:center;justify-content:center;color:#64748b;font:700 11px/1.3 "Microsoft YaHei",sans-serif;text-align:center;padding:6px}
.cms-builder-material-preview-compact-lines{display:grid;gap:7px}
.cms-builder-material-preview-compact-line{height:8px;border-radius:999px;background:#dbe3f4}
.cms-builder-material-preview-compact-line.sm{width:38%}
.cms-builder-material-preview-compact-line.md{width:62%}
.cms-builder-material-preview-compact-line.lg{width:88%}
.cms-builder-material-preview-grid{display:grid;gap:10px}
.cms-builder-material-preview-grid.cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.cms-builder-material-preview-grid.cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}
.cms-builder-material-preview-grid.cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}
.cms-builder-material-thumb{border-radius:12px;background:linear-gradient(135deg,#dbe7ff,#eef4ff);min-height:56px;position:relative;overflow:hidden}
.cms-builder-material-thumb.dark{background:linear-gradient(135deg,#0f172a,#334155)}
.cms-builder-material-thumb.wide{min-height:96px}
.cms-builder-material-thumb:before{content:"";position:absolute;inset:auto 12px 12px 12px;height:6px;border-radius:999px;background:rgba(255,255,255,.66)}
.cms-builder-material-line{height:8px;border-radius:999px;background:#dbe3f4}
.cms-builder-material-line.sm{width:38%}
.cms-builder-material-line.md{width:62%}
.cms-builder-material-line.lg{width:88%}
.cms-builder-material-pill{display:inline-flex;align-items:center;justify-content:center;padding:6px 10px;border-radius:999px;background:#eef2ff;color:#4964d8;font:700 11px/1 "Microsoft YaHei",sans-serif}
.cms-builder-material-row{display:flex;align-items:center;gap:10px}
.cms-builder-material-stat{padding:12px;border-radius:12px;background:#f8fbff;border:1px solid #e5ecf8}
.cms-builder-material-play{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:40px;height:40px;border-radius:999px;background:rgba(255,255,255,.9);box-shadow:0 10px 20px rgba(15,23,42,.16)}
.cms-builder-material-play:before{content:"";position:absolute;left:15px;top:11px;border-left:12px solid #4664df;border-top:8px solid transparent;border-bottom:8px solid transparent}
.cms-builder-material-actions{display:flex;gap:10px;padding:0 16px 14px}
.cms-builder-material-actions .cms-builder-btn{flex:1}
.cms-builder-material-result-empty{padding:28px 16px;border:1px dashed #d5deef;border-radius:16px;background:#f9fbff;color:#64748b;text-align:center;font:500 12px/1.8 "Microsoft YaHei",sans-serif}
.cms-builder-material-modal{position:fixed;inset:0;z-index:9070;background:rgba(15,23,42,.48);display:flex;align-items:center;justify-content:center;padding:24px}
.cms-builder-material-modal-card{width:min(920px,calc(100vw - 48px));max-height:calc(100vh - 48px);display:flex;flex-direction:column;border-radius:20px;background:#fff;box-shadow:0 28px 80px rgba(15,23,42,.28);overflow:hidden}
.cms-builder-material-modal-card.is-detached{position:absolute;margin:0}
.cms-builder-material-modal-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:18px 20px;border-bottom:1px solid #e5eaf5;cursor:move}
.cms-builder-material-modal-title{font:700 16px/1.4 "Microsoft YaHei",sans-serif;color:#0f172a}
.cms-builder-material-modal-body{overflow:auto;padding:20px;background:#f8fbff}
.cms-builder-material-modal-preview{border:1px solid #e7edf7;border-radius:18px;background:#fff;padding:18px}
.cms-builder-material-modal-actions{display:flex;gap:10px;padding:16px 20px;border-top:1px solid #e5eaf5;background:#fff}
.cms-builder-business-editor-note{margin-bottom:14px;padding:12px 14px;border:1px solid #dbe7ff;border-radius:14px;background:#f8fbff;color:#64748b;font:500 12px/1.8 "Microsoft YaHei",sans-serif}
.cms-builder-business-editor-meta{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin-bottom:14px}
.cms-builder-business-editor-chip{padding:10px 12px;border-radius:12px;background:#fff;border:1px solid #e2e8f0;color:#334155;font:600 12px/1.5 "Microsoft YaHei",sans-serif}
.cms-builder-business-editor-chip strong{display:block;margin-bottom:4px;color:#0f172a;font:700 12px/1.2 "Microsoft YaHei",sans-serif}
.cms-builder-overlay-layer{position:fixed;inset:0;z-index:9030;pointer-events:none}
.cms-builder-overlay-layer > *{pointer-events:auto}
.cms-builder-rich-editor-modal{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(15,23,42,.62);backdrop-filter:blur(6px)}
.cms-builder-rich-editor-modal.is-hidden{display:none}
.cms-builder-rich-editor-card{width:min(980px,100%);max-height:calc(100vh - 48px);overflow:auto;padding:22px;border:1px solid rgba(226,232,240,.9);border-radius:20px;background:#fff;box-shadow:0 30px 90px rgba(15,23,42,.34)}
.cms-builder-rich-editor-head{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;margin-bottom:16px}
.cms-builder-rich-editor-head h2{margin:0;color:#172033;font:700 18px/1.35 "Microsoft YaHei",sans-serif}
.cms-builder-rich-editor-head p{margin:6px 0 0;color:#64748b;font:500 12px/1.7 "Microsoft YaHei",sans-serif}
.cms-builder-rich-editor-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:16px}
@media (max-width:1280px){
html.cms-frontend-builder-edit body{padding-top:56px}
.cms-builder-topbar{height:auto;min-height:56px;padding:10px 12px;align-items:flex-start;flex-direction:column}
.cms-builder-dock{left:12px;top:110px}
.cms-builder-dock-btn{min-width:96px;padding:12px 16px}
.cms-builder-panel{width:280px}
}
@media (max-width:980px){
  .cms-builder-dock{position:static;flex-direction:row;flex-wrap:wrap;gap:10px;padding:12px}
  .cms-builder-dock-btn{min-width:auto}
  .cms-builder-panel{position:static;width:auto;max-height:none;height:auto;margin:12px 12px 0}
  .cms-builder-panel-left,.cms-builder-panel-right{left:auto;right:auto}
  .cms-builder-settings-backdrop{display:none}
  .cms-builder-settings-modal{position:static;width:auto;max-height:none;box-shadow:0 22px 54px rgba(15,23,42,.18)}
  .cms-builder-library-grid{grid-template-columns:repeat(3,minmax(0,1fr))}
  .cms-builder-material-preview-surface{padding:14px}
  .cms-builder-material-preview-grid.cols-3,.cms-builder-material-preview-grid.cols-4{grid-template-columns:repeat(2,minmax(0,1fr))}
  html.cms-frontend-builder-edit body{padding-top:0}
  .cms-builder-topbar{position:static}
  .cms-builder-rich-editor-modal{padding:10px}
  .cms-builder-rich-editor-card{max-height:calc(100vh - 20px);padding:14px;border-radius:16px}
  .cms-builder-rich-editor-actions .cms-builder-btn{flex:1}
  .cms-builder-preflight-modal{padding:10px}.cms-builder-preflight-card{max-height:calc(100vh - 20px);border-radius:16px}.cms-builder-preflight-body{max-height:calc(100vh - 166px);padding:12px}.cms-builder-preflight-summary{grid-template-columns:repeat(2,minmax(0,1fr))}.cms-builder-preflight-item{grid-template-columns:1fr}.cms-builder-preflight-level{justify-self:start}.cms-builder-preflight-actions .cms-builder-btn{width:100%;min-height:44px}
}
@media (pointer:coarse){.cms-builder-btn,.cms-builder-panel-tool,.cms-builder-list button,.cms-builder-dock-btn{min-height:44px}.cms-builder-action-grid{gap:10px}.cms-builder-hover-edit,.cms-builder-hover-icon{min-width:44px;min-height:44px}}
</style>
<link rel="stylesheet" href="/public/css/cms-ueditor.css?v=20260718">
<script src="/public/ueditor/cms-editor.js?v=20260718"></script>
<script>
window.CMS_FRONTEND_BUILDER_BOOT = <?php echo json_encode($boot, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
</script>
<div id="cms_frontend_builder_root"></div>
<script>
(function () {
    var boot = window.CMS_FRONTEND_BUILDER_BOOT || {};
    var BUILDER_FREE_CANVAS_ENABLED = false;
    var state = {
        page: boot.layout && boot.layout.page ? boot.layout.page : {},
        sections: boot.layout && Array.isArray(boot.layout.sections) ? boot.layout.sections : [],
        selectedPath: '',
        selectedNode: null,
        selectedCanvasBlockId: '',
        selectedCanvasBlockIds: [],
        blocks: [],
        saveTimer: null,
        saveInFlight: false,
        saveQueued: false,
        savePromise: null,
        retryTimer: null,
        collaboratorTimer: null,
        collaborators: [],
        collaboratorSignature: '',
        recoveryTimer: null,
        recoveryPrompted: false,
        retryAttempt: 0,
        online: navigator.onLine !== false,
        lastSavedAt: 0,
        serverVersion: parseInt(boot.builder_version || 0, 10) || 0,
        conflict: false,
        conflictPromptOpen: false,
        confirmResolve: null,
        confirmFocus: null,
        tabId: (function(){var key='cms_builder_tab:'+String(boot.builder_type||'home')+':'+String(boot.builder_ref_id||0);try{var id=sessionStorage.getItem(key);if(!id){id=String(Date.now())+'-'+Math.random().toString(36).slice(2);sessionStorage.setItem(key,id);}return id;}catch(error){return String(Date.now())+'-'+Math.random().toString(36).slice(2);}})(),
        changeRevision: 0,
        savedRevision: 0,
        dirty: false,
        allowExit: false,
        history: [],
        future: [],
        suspendHistory: false,
        draggingPath: '',
        draggingNewExtraType: '',
        draggingPanelRole: '',
        draggingDock: false,
        libraryPanelMode: 'modules',
        libraryModuleTab: 'new_module',
        stylePanelTab: 'theme',
        stylePanelPart: 'layout',
        settingsPanelTab: 'basic',
        settingsWidgetPreviewKey: '',
        currentColumnUrl: '',
        materialScope: 'scene',
        materialCategory: 'product_showcase',
        materialSearch: '',
        moduleSearch: '',
        recentModules: [],
        librarySearchTimer: null,
        materialPreviewKey: '',
        pageVisitCount: 0,
        publicMobilePreview: false,
        businessEditor: null,
        richTextEditorExtraId: '',
        hoveredPath: '',
        hoverLeaveTimer: null,
        hoverToolbarActive: false,
        resizingMeta: null,
        canvasGuides: {vertical: [], horizontal: []},
        pendingCanvasDrag: null,
        pendingBlockDrag: null,
        mouseDragMeta: null,
        liveEditToken: '',
        settingsPanelOpen: false,
        suppressClickUntil: 0,
        suppressDockClickUntil: 0,
        dockPosition: {left: 18, top: 118},
        materialModalPosition: null,
        businessModalPosition: null,
        libraryExpandedGroups: {
            common: true,
            basic: true,
            layout: false,
            product: false,
            interactive: false,
            advanced: false
        },
        panelPositions: {
            library: {left: 14, top: 82},
            settings: {left: 0, top: 82}
        },
        businessOptions: {
            article_categories: [],
            product_categories: [],
            votes: [],
            member_state: {},
            site_contact: {},
            loaded: false
        }
    };

    state.editorToken='edit_'+state.tabId.replace(/[^A-Za-z0-9._-]/g,'').slice(0,70);

    var alert=builderToast;
    var PAGE_STYLE_ID = 'cms_builder_page_style';

    function defaultLibraryExpandedGroups() {
        return {
            common: true,
            basic: true,
            layout: false,
            product: false,
            interactive: false,
            advanced: false
        };
    }

    function defaultAdvancedOptions() {
        return {
            production_info: 1,
            icp: 1,
            public_security: 0,
            admin_login: 0,
            mobile_site: 0
        };
    }

    function normalizeAdvancedOptions(value) {
        var defaults = defaultAdvancedOptions();
        if (!value || typeof value !== 'object' || Array.isArray(value)) {
            return defaults;
        }
        Object.keys(defaults).forEach(function (key) {
            defaults[key] = value[key] ? 1 : 0;
        });
        return defaults;
    }

    function defaultSiteWidgets() {
        return {
            floating_service: 0,
            site_sidebar: 0,
            top_ad: 0,
            bottom_float: 0,
            member_bar: 0,
            home_popup: 0,
            elevator_nav: 0,
            welcome_screen: 0,
            counter: 0,
            background_music: 0
        };
    }

    function normalizeSiteWidgets(value) {
        var defaults = defaultSiteWidgets();
        if (!value || typeof value !== 'object' || Array.isArray(value)) {
            return defaults;
        }
        Object.keys(defaults).forEach(function (key) {
            defaults[key] = value[key] ? 1 : 0;
        });
        return defaults;
    }

    function defaultSiteWidgetConfigs() {
        return {
            top_ad: {
                text: '网站顶部广告：立即领取专属咨询通道',
                link_url: ''
            },
            member_bar: {
                login_text: '会员登录',
                register_text: '立即注册'
            },
            floating_service: {
                guestbook_url: '',
                phone: '',
                qq: '',
                wechat: '',
                email: ''
            },
            site_sidebar: {
                product_url: '',
                contact_label: '联系'
            },
            elevator_nav: {
                product_url: '',
                contact_label: '联系'
            },
            bottom_float: {
                text: '立即获取专属方案',
                button_text: '立即咨询',
                button_url: ''
            },
            counter: {
                label: '本页访问'
            },
            background_music: {
                audio_url: '',
                button_text: '播放音乐',
                autoplay: 0
            },
            home_popup: {
                title: '首页弹窗',
                content: '这里可以展示活动信息、优惠券或者首页欢迎文案。',
                button_text: '立即查看',
                button_url: ''
            },
            welcome_screen: {
                title: 'WELCOME',
                content: '欢迎页已开启，适合活动场景或品牌开场。',
                button_text: '进入网站'
            }
        };
    }

    function normalizeSiteWidgetConfigs(value) {
        var defaults = defaultSiteWidgetConfigs();
        if (!value || typeof value !== 'object' || Array.isArray(value)) {
            return defaults;
        }
        Object.keys(defaults).forEach(function (widgetKey) {
            var source = value[widgetKey];
            if (!source || typeof source !== 'object' || Array.isArray(source)) {
                return;
            }
            Object.keys(defaults[widgetKey]).forEach(function (fieldKey) {
                if (typeof defaults[widgetKey][fieldKey] === 'number') {
                    defaults[widgetKey][fieldKey] = source[fieldKey] ? 1 : 0;
                } else if (typeof source[fieldKey] !== 'undefined') {
                    defaults[widgetKey][fieldKey] = String(source[fieldKey] == null ? '' : source[fieldKey]);
                }
            });
        });
        return defaults;
    }

    function defaultFrontendEditorState() {
        return {
            library_panel_mode: 'modules',
            library_module_tab: 'new_module',
            style_panel_tab: 'theme',
            style_panel_part: 'layout',
            settings_panel_tab: 'basic',
            free_layout_mode: 0,
            material_scope: 'scene',
            material_category: 'product_showcase',
            material_search: '',
            module_search: '',
            recent_modules: [],
            library_expanded_groups: defaultLibraryExpandedGroups(),
            dock_position: {left: 18, top: 118},
            panel_positions: {
                library: {left: 14, top: 82},
                settings: {left: 0, top: 82}
            },
            material_modal_position: null,
            business_modal_position: null
        };
    }

    function cloneJsonValue(value) {
        return JSON.parse(JSON.stringify(value));
    }

    function normalizePointValue(value, fallback, allowNull) {
        if (allowNull && (!value || typeof value !== 'object')) {
            return null;
        }
        if (!value || typeof value !== 'object') {
            return cloneJsonValue(fallback);
        }
        return {
            left: typeof value.left === 'number' && isFinite(value.left) ? value.left : fallback.left,
            top: typeof value.top === 'number' && isFinite(value.top) ? value.top : fallback.top
        };
    }

    function normalizeLibraryExpandedGroups(value) {
        var defaults = defaultLibraryExpandedGroups();
        if (!value || typeof value !== 'object') {
            return defaults;
        }
        Object.keys(defaults).forEach(function (key) {
            if (typeof value[key] === 'boolean') {
                defaults[key] = value[key];
            }
        });
        return defaults;
    }

    function normalizeFrontendEditorState(value) {
        var defaults = defaultFrontendEditorState();
        if (!value || typeof value !== 'object' || Array.isArray(value)) {
            return defaults;
        }

        var next = cloneJsonValue(defaults);
        next.library_panel_mode = ['modules', 'style', 'settings', 'tools'].indexOf(value.library_panel_mode) !== -1
            ? value.library_panel_mode
            : defaults.library_panel_mode;
        next.library_module_tab = ['new_module', 'new_material', 'section_module', 'current_columns'].indexOf(value.library_module_tab) !== -1
            ? value.library_module_tab
            : defaults.library_module_tab;
        next.style_panel_tab = ['theme', 'basic', 'layout', 'footer'].indexOf(value.style_panel_tab) !== -1
            ? value.style_panel_tab
            : defaults.style_panel_tab;
        next.style_panel_part = ['header', 'layout', 'footer'].indexOf(value.style_panel_part) !== -1
            ? value.style_panel_part
            : defaults.style_panel_part;
        next.settings_panel_tab = ['basic', 'page', 'advanced'].indexOf(value.settings_panel_tab) !== -1
            ? value.settings_panel_tab
            : defaults.settings_panel_tab;
        next.free_layout_mode = value.free_layout_mode ? 1 : 0;
        next.material_scope = value.material_scope === 'mine' ? 'mine' : defaults.material_scope;
        next.material_category = typeof value.material_category === 'string' && value.material_category !== ''
            ? value.material_category
            : defaults.material_category;
        next.material_search = typeof value.material_search === 'string' ? value.material_search : defaults.material_search;
        next.module_search = typeof value.module_search === 'string' ? value.module_search : defaults.module_search;
        next.recent_modules=Array.isArray(value.recent_modules)?value.recent_modules.map(function(item){return String(item||'');}).filter(function(item){return /^[a-z0-9_]{1,60}$/.test(item);}).slice(0,6):[];
        next.library_expanded_groups = normalizeLibraryExpandedGroups(value.library_expanded_groups);
        next.dock_position = normalizePointValue(value.dock_position, defaults.dock_position, false);
        next.panel_positions = {
            library: normalizePointValue(value.panel_positions && value.panel_positions.library, defaults.panel_positions.library, false),
            settings: normalizePointValue(value.panel_positions && value.panel_positions.settings, defaults.panel_positions.settings, false)
        };
        next.material_modal_position = normalizePointValue(value.material_modal_position, {left: 0, top: 0}, true);
        next.business_modal_position = normalizePointValue(value.business_modal_position, {left: 0, top: 0}, true);
        return next;
    }

    function loadEditorStateFromPage() {
        var defaults = defaultFrontendEditorState();
        var editorState = normalizeFrontendEditorState(state.page.frontend_editor_state);
        state.page.frontend_editor_state = editorState;
        state.libraryPanelMode = editorState.library_panel_mode;
        state.libraryModuleTab = editorState.library_module_tab;
        state.stylePanelTab = editorState.style_panel_tab;
        state.stylePanelPart = editorState.style_panel_part;
        state.settingsPanelTab = editorState.settings_panel_tab;
        state.page.free_layout_mode = editorState.free_layout_mode ? 1 : 0;
        state.materialScope = editorState.material_scope;
        state.materialCategory = editorState.material_category;
        state.materialSearch = editorState.material_search;
        state.moduleSearch = editorState.module_search;
        state.recentModules = editorState.recent_modules.slice();
        state.libraryExpandedGroups = normalizeLibraryExpandedGroups(editorState.library_expanded_groups);
        state.dockPosition = normalizePointValue(editorState.dock_position, defaults.dock_position, false);
        state.panelPositions = {
            library: normalizePointValue(editorState.panel_positions && editorState.panel_positions.library, defaults.panel_positions.library, false),
            settings: normalizePointValue(editorState.panel_positions && editorState.panel_positions.settings, defaults.panel_positions.settings, false)
        };
        state.materialModalPosition = normalizePointValue(editorState.material_modal_position, {left: 0, top: 0}, true);
        state.businessModalPosition = normalizePointValue(editorState.business_modal_position, {left: 0, top: 0}, true);
    }

    function syncEditorStateToPage() {
        state.page.frontend_editor_state = {
            library_panel_mode: state.libraryPanelMode,
            library_module_tab: state.libraryModuleTab,
            style_panel_tab: state.stylePanelTab,
            style_panel_part: state.stylePanelPart,
            settings_panel_tab: state.settingsPanelTab,
            free_layout_mode: state.page.free_layout_mode ? 1 : 0,
            material_scope: state.materialScope,
            material_category: state.materialCategory,
            material_search: state.materialSearch,
            module_search: state.moduleSearch,
            recent_modules: state.recentModules.slice(0,6),
            library_expanded_groups: cloneJsonValue(state.libraryExpandedGroups),
            dock_position: cloneJsonValue(state.dockPosition),
            panel_positions: cloneJsonValue(state.panelPositions),
            material_modal_position: state.materialModalPosition ? cloneJsonValue(state.materialModalPosition) : null,
            business_modal_position: state.businessModalPosition ? cloneJsonValue(state.businessModalPosition) : null
        };
    }

    function ensureDefaults() {
        if (!state.page.frontend_overrides || typeof state.page.frontend_overrides !== 'object' || Array.isArray(state.page.frontend_overrides)) {
            state.page.frontend_overrides = {};
        }
        if (!Array.isArray(state.page.frontend_extra_sections)) {
            state.page.frontend_extra_sections = [];
        }
        if (!Array.isArray(state.page.frontend_canvas_blocks)) {
            state.page.frontend_canvas_blocks = [];
        }
        if (!Array.isArray(state.page.frontend_hidden_paths)) {
            state.page.frontend_hidden_paths = [];
        }
        if (!Array.isArray(state.page.frontend_free_layout_hidden_paths)) {
            state.page.frontend_free_layout_hidden_paths = [];
        }
        if (!Array.isArray(state.page.frontend_deleted_paths)) {
            state.page.frontend_deleted_paths = [];
        }
        if (!Array.isArray(state.page.frontend_locked_paths)) {
            state.page.frontend_locked_paths = [];
        }
        if (!Array.isArray(state.page.frontend_block_orders)) {
            state.page.frontend_block_orders = [];
        }
        if (!Array.isArray(state.page.frontend_mobile_hidden_paths)) {
            state.page.frontend_mobile_hidden_paths = [];
        }
        if (!Array.isArray(state.page.frontend_mobile_deleted_paths)) {
            state.page.frontend_mobile_deleted_paths = [];
        }
        if (!state.page.frontend_mobile_overrides || typeof state.page.frontend_mobile_overrides !== 'object' || Array.isArray(state.page.frontend_mobile_overrides)) {
            state.page.frontend_mobile_overrides = {};
        }
        if (!state.page.device_mode) {
            state.page.device_mode = 'pc';
        }
        if (!state.page.page_width) {
            state.page.page_width = 1200;
        }
        if (!state.page.page_padding && state.page.page_padding !== 0) {
            state.page.page_padding = 0;
        }
        if (!state.page.mobile_page_width) {
            state.page.mobile_page_width = 375;
        }
        if (!state.page.mobile_page_padding && state.page.mobile_page_padding !== 0) {
            state.page.mobile_page_padding = 12;
        }
        if (!state.page.background_color) {
            state.page.background_color = '';
        }
        if (!state.page.background_mode) {
            state.page.background_mode = 'default';
        }
        if (typeof state.page.browser_title_visible === 'undefined') {
            state.page.browser_title_visible = 1;
        }
        if (!state.page.favicon_url) {
            state.page.favicon_url = '';
        }
        if (!state.page.theme_color) {
            state.page.theme_color = '#5f7eeb';
        }
        if (!state.page.theme_surface) {
            state.page.theme_surface = 'clean';
        }
        if (!state.page.page_width_mode) {
            state.page.page_width_mode = String(state.page.page_width) === '960' ? '960' : (String(state.page.page_width) === '1200' ? '1200' : 'custom');
        }
        if (!state.page.layout_preset) {
            state.page.layout_preset = 'classic';
        }
        if (!state.page.footer_text_align) {
            state.page.footer_text_align = 'center';
        }
        if (!state.page.footer_background_color) {
            state.page.footer_background_color = '';
        }
        if (!state.page.footer_text_color) {
            state.page.footer_text_color = '';
        }
        state.page.advanced_options = normalizeAdvancedOptions(state.page.advanced_options);
        state.page.site_widgets = normalizeSiteWidgets(state.page.site_widgets);
        state.page.site_widget_configs = normalizeSiteWidgetConfigs(state.page.site_widget_configs);
        if (!state.page.footer_padding_top && state.page.footer_padding_top !== 0) {
            state.page.footer_padding_top = 20;
        }
        if (!state.page.footer_padding_bottom && state.page.footer_padding_bottom !== 0) {
            state.page.footer_padding_bottom = 20;
        }
        if (!state.page.font_family) {
            state.page.font_family = '';
        }
        loadEditorStateFromPage();
        if (!BUILDER_FREE_CANVAS_ENABLED) {
            disableFreeCanvasState();
            syncEditorStateToPage();
        }
    }

    ensureDefaults();
    if (!BUILDER_FREE_CANVAS_ENABLED) {
        removeCanvasLayer();
    }

    function qs(selector, scope) {
        return (scope || document).querySelector(selector);
    }

    function qsa(selector, scope) {
        return Array.prototype.slice.call((scope || document).querySelectorAll(selector));
    }

    function escapeHtml(value) {
        return String(value == null ? '' : value)
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#39;');
    }

    function normalizeColorPickerValue(value, fallback) {
        var candidate = String(value || '').trim();
        if (/^#([0-9a-fA-F]{6})$/.test(candidate)) {
            return candidate;
        }
        if (/^#([0-9a-fA-F]{3})$/.test(candidate)) {
            return '#' + candidate.charAt(1) + candidate.charAt(1)
                + candidate.charAt(2) + candidate.charAt(2)
                + candidate.charAt(3) + candidate.charAt(3);
        }
        var rgbMatch = candidate.match(/^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})(?:\s*,\s*([0-9.]+))?\s*\)$/i);
        if (rgbMatch) {
            var alpha = typeof rgbMatch[4] === 'undefined' ? 1 : parseFloat(rgbMatch[4]);
            if (!isNaN(alpha) && alpha <= 0) {
                return fallback || '#ffffff';
            }
            var red = Math.max(0, Math.min(255, parseInt(rgbMatch[1], 10) || 0)).toString(16).padStart(2, '0');
            var green = Math.max(0, Math.min(255, parseInt(rgbMatch[2], 10) || 0)).toString(16).padStart(2, '0');
            var blue = Math.max(0, Math.min(255, parseInt(rgbMatch[3], 10) || 0)).toString(16).padStart(2, '0');
            return '#' + red + green + blue;
        }
        return fallback || '#ffffff';
    }

    function clampNumber(value, fallback, minValue, maxValue) {
        var number = parseInt(value, 10);
        if (isNaN(number)) {
            number = fallback;
        }
        if (typeof minValue === 'number') {
            number = Math.max(minValue, number);
        }
        if (typeof maxValue === 'number') {
            number = Math.min(maxValue, number);
        }
        return number;
    }

    function normalizeToggleValue(value, fallback) {
        if (String(value) === '1' || String(value).toLowerCase() === 'true') {
            return '1';
        }
        if (String(value) === '0' || String(value).toLowerCase() === 'false') {
            return '0';
        }
        return fallback ? '1' : '0';
    }

    function parseDelimitedCollection(value, maxParts) {
        return String(value || '')
            .split(/\r?\n/)
            .map(function (line) {
                return line.trim();
            })
            .filter(function (line) {
                return line !== '';
            })
            .map(function (line) {
                var parts = line.split('|').map(function (part) {
                    return part.trim();
                });
                while (parts.length < maxParts) {
                    parts.push('');
                }
                return parts.slice(0, maxParts);
            });
    }

    function extraLineValue(parts, index, fallback) {
        if (!parts || !parts.length) {
            return fallback || '';
        }
        return parts[index] || fallback || '';
    }

    function syncPageColorInputs(key, value) {
        qsa('[data-page-key="' + key + '"]').forEach(function (input) {
            if (input && input.type !== 'color') {
                input.value = value;
            }
        });
        qsa('[data-page-color-key="' + key + '"]').forEach(function (input) {
            if (input) {
                input.value = normalizeColorPickerValue(value, '#ffffff');
            }
        });
    }

    function syncPageFieldInputs(key, value, source) {
        qsa('[data-page-key="' + key + '"]').forEach(function (input) {
            if (input && input !== source && input.type !== 'color') {
                input.value = value;
            }
        });
    }

    function closeGalleryLightbox() {
        var lightbox = qs('#cms_builder_gallery_lightbox');
        if (lightbox && lightbox.parentNode) {
            lightbox.parentNode.removeChild(lightbox);
        }
    }

    function openGalleryLightbox(imageUrl, title) {
        closeGalleryLightbox();
        var lightbox = document.createElement('div');
        lightbox.id = 'cms_builder_gallery_lightbox';
        lightbox.setAttribute('style', 'position:fixed;inset:0;z-index:9205;background:rgba(15,23,42,.72);display:flex;align-items:center;justify-content:center;padding:24px;');
        lightbox.innerHTML = '<div style="position:relative;max-width:min(1100px,calc(100vw - 48px));width:100%;"><button type="button" data-gallery-lightbox-close="1" style="position:absolute;right:0;top:-48px;width:40px;height:40px;border:0;border-radius:999px;background:#fff;color:#111827;font-size:20px;cursor:pointer;">×</button><div style="padding:18px;border-radius:22px;background:#fff;"><img src="' + escapeHtml(imageUrl || '') + '" alt="' + escapeHtml(title || '') + '" style="display:block;width:100%;max-height:78vh;object-fit:contain;border-radius:16px;background:#f8fafc;"><div style="margin-top:12px;font-size:16px;font-weight:700;color:#0f172a;">' + escapeHtml(title || '') + '</div></div></div>';
        document.body.appendChild(lightbox);
    }

    function updateSimpleCarousel(root, nextIndex) {
        if (!root) {
            return;
        }
        var slides = qsa('[data-carousel-slide]', root);
        var dots = qsa('[data-carousel-dot]', root);
        if (!slides.length) {
            return;
        }
        var total = slides.length;
        var normalized = ((nextIndex % total) + total) % total;
        root.setAttribute('data-carousel-current', String(normalized));
        slides.forEach(function (slide, index) {
            slide.style.display = index === normalized ? 'block' : 'none';
        });
        dots.forEach(function (dot, index) {
            dot.style.background = index === normalized ? '#2563eb' : '#cbd5e1';
        });
    }

    function initSimpleCarousels(scope) {
        qsa('[data-front-carousel]', scope || document).forEach(function (root) {
            if (root.__cmsCarouselReady) {
                return;
            }
            root.__cmsCarouselReady = true;
            updateSimpleCarousel(root, 0);
            var delay = clampNumber(root.getAttribute('data-carousel-delay') || 4000, 4000, 1000, 30000);
            if (root.__cmsCarouselTimer) {
                clearInterval(root.__cmsCarouselTimer);
            }
            if (qsa('[data-carousel-slide]', root).length > 1) {
                root.__cmsCarouselTimer = window.setInterval(function () {
                    if (boot.edit_mode) {
                        return;
                    }
                    var current = clampNumber(root.getAttribute('data-carousel-current') || 0, 0, 0, 999);
                    updateSimpleCarousel(root, current + 1);
                }, delay);
            }
        });
    }

    function buildStyleTextWithProperty(styleText, propertyName, value) {
        var probe = document.createElement('div');
        probe.style.cssText = styleText || '';
        if (value === '' || value == null) {
            probe.style.removeProperty(propertyName);
        } else {
            probe.style.setProperty(propertyName, value);
        }
        return probe.getAttribute('style') || '';
    }

    function selectedStylePropertyValue(propertyName) {
        if (!state.selectedNode) {
            return '';
        }
        var styleText = overrideValue('style', state.selectedNode.getAttribute('style') || '');
        var probe = document.createElement('div');
        probe.style.cssText = styleText || '';
        var direct = probe.style.getPropertyValue(propertyName) || '';
        if (direct) {
            return direct;
        }
        try {
            var computed = window.getComputedStyle(state.selectedNode).getPropertyValue(propertyName) || '';
            if (/rgba?\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/i.test(computed) || computed === 'transparent') {
                return '';
            }
            return computed.trim();
        } catch (error) {
            return '';
        }
    }

    function syncSelectedStylePropInputs(propertyName, value) {
        qsa('[data-front-style-prop="' + propertyName + '"]').forEach(function (input) {
            if (input && input.type !== 'color') {
                input.value = value;
            }
        });
        qsa('[data-front-style-color-prop="' + propertyName + '"]').forEach(function (input) {
            if (input) {
                input.value = normalizeColorPickerValue(value, '#ffffff');
            }
        });
    }

    function currentDevice() {
        if(!boot.edit_mode){return state.publicMobilePreview||window.innerWidth<=768?'mobile':'pc';}
        return state.page.device_mode === 'mobile' ? 'mobile' : (state.page.device_mode === 'tablet' ? 'tablet' : 'pc');
    }

    function currentPreviewWidth() {
        return currentDevice()==='mobile'?(parseInt(state.page.mobile_page_width,10)||375):(currentDevice()==='tablet'?768:(parseInt(state.page.page_width,10)||1200));
    }

    function syncDeviceButtons() {
        var device = currentDevice();
        var pcButtons = qsa('#cms_builder_top_device_pc, #cms_builder_device_pc');
        var tabletButtons = qsa('#cms_builder_top_device_tablet, #cms_builder_device_tablet');
        var mobileButtons = qsa('#cms_builder_top_device_mobile, #cms_builder_device_mobile');
        pcButtons.forEach(function (button) {
            button.classList.toggle('is-active', device === 'pc');
        });
        mobileButtons.forEach(function (button) {
            button.classList.toggle('is-active', device === 'mobile');
        });
        tabletButtons.forEach(function (button) { button.classList.toggle('is-active',device==='tablet'); });
    }

    function isShellFrameMode() {
        try {
            return window.parent && window.parent !== window;
        } catch (error) {
            return false;
        }
    }

    function selectedExtraStyleState(extra) {
        var config = extra && extra.config && typeof extra.config === 'object' ? extra.config : {};
        var style = config.style && typeof config.style === 'object' ? Object.assign({}, config.style) : {};
        if (currentDevice() === 'mobile' && config.mobile_style && typeof config.mobile_style === 'object') {
            style = Object.assign(style, config.mobile_style);
        }
        return style;
    }

    function extraStyleFromKey(key) {
        var parts = String(key || '').split(':');
        return {
            device: parts[0] === 'mobile' ? 'mobile' : 'pc',
            name: parts.slice(1).join(':')
        };
    }

    function ensureExtraStyleState(extra, device) {
        if (!extra) {
            return {};
        }
        if (!extra.config || typeof extra.config !== 'object') {
            extra.config = {};
        }
        var key = device === 'mobile' ? 'mobile_style' : 'style';
        if (!extra.config[key] || typeof extra.config[key] !== 'object') {
            extra.config[key] = {};
        }
        return extra.config[key];
    }

    function styleValueOrDefault(style, key, fallback) {
        if (!style || typeof style !== 'object') {
            return fallback;
        }
        return Object.prototype.hasOwnProperty.call(style, key) ? style[key] : fallback;
    }

    function renderStyleStateFields(style, deviceKey) {
        style = style || {};
        var backgroundType = styleValueOrDefault(style, 'background_type', 'solid');
        var gradientFrom = styleValueOrDefault(style, 'gradient_from', '#ffffff');
        var gradientTo = styleValueOrDefault(style, 'gradient_to', '#eef2ff');
        var backgroundRepeat = styleValueOrDefault(style, 'background_repeat', 'no-repeat');
        return '' +
            '<div class="cms-builder-group">' +
                '<div class="cms-builder-group-title">基础样式</div>' +
                '<div class="cms-builder-fields">' +
                    '<div class="cms-builder-field"><label>宽度</label><input type="text" data-extra-style-key="' + deviceKey + ':width" value="' + escapeHtml(styleValueOrDefault(style, 'width', '100%')) + '"></div>' +
                    '<div class="cms-builder-field"><label>最小高度</label><input type="text" data-extra-style-key="' + deviceKey + ':min-height" value="' + escapeHtml(styleValueOrDefault(style, 'min-height', 'auto')) + '"></div>' +
                    '<div class="cms-builder-field"><label>边距</label><input type="text" data-extra-style-key="' + deviceKey + ':margin" value="' + escapeHtml(styleValueOrDefault(style, 'margin', '0')) + '"></div>' +
                    '<div class="cms-builder-field"><label>填充</label><input type="text" data-extra-style-key="' + deviceKey + ':padding" value="' + escapeHtml(styleValueOrDefault(style, 'padding', '0')) + '"></div>' +
                    '<div class="cms-builder-field"><label>圆角</label><input type="text" data-extra-style-key="' + deviceKey + ':border-radius" value="' + escapeHtml(styleValueOrDefault(style, 'border-radius', '0')) + '"></div>' +
                    '<div class="cms-builder-field"><label>边框</label><input type="text" data-extra-style-key="' + deviceKey + ':border" value="' + escapeHtml(styleValueOrDefault(style, 'border', 'none')) + '"></div>' +
                    '<div class="cms-builder-field"><label>阴影</label><input type="text" data-extra-style-key="' + deviceKey + ':box-shadow" value="' + escapeHtml(styleValueOrDefault(style, 'box-shadow', 'none')) + '"></div>' +
                '</div>' +
            '</div>' +
            '<div class="cms-builder-group" style="margin-top:16px;">' +
                '<div class="cms-builder-group-title">文字样式</div>' +
                '<div class="cms-builder-fields">' +
                    '<div class="cms-builder-field"><label>字体</label><input type="text" data-extra-style-key="' + deviceKey + ':font-family" value="' + escapeHtml(styleValueOrDefault(style, 'font-family', 'Microsoft YaHei')) + '"></div>' +
                    '<div class="cms-builder-field"><label>字号</label><input type="text" data-extra-style-key="' + deviceKey + ':font-size" value="' + escapeHtml(styleValueOrDefault(style, 'font-size', '16px')) + '"></div>' +
                    '<div class="cms-builder-field"><label>颜色</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-extra-style-key="' + deviceKey + ':color" value="' + escapeHtml(styleValueOrDefault(style, 'color', '#0f172a')) + '"><input type="color" data-extra-style-color-key="' + deviceKey + ':color" value="' + escapeHtml(normalizeColorPickerValue(styleValueOrDefault(style, 'color', '#0f172a'), '#0f172a')) + '" style="width:52px;height:42px;padding:4px 6px;border-radius:10px;flex:0 0 52px;cursor:pointer;"></div></div>' +
                    '<div class="cms-builder-field"><label>字重</label><select data-extra-style-key="' + deviceKey + ':font-weight">' +
                        '<option value="400"' + (String(styleValueOrDefault(style, 'font-weight', '400')) === '400' ? ' selected' : '') + '>正常</option>' +
                        '<option value="600"' + (String(styleValueOrDefault(style, 'font-weight', '400')) === '600' ? ' selected' : '') + '>加粗</option>' +
                        '<option value="700"' + (String(styleValueOrDefault(style, 'font-weight', '400')) === '700' ? ' selected' : '') + '>更粗</option>' +
                    '</select></div>' +
                    '<div class="cms-builder-field"><label>斜体</label><select data-extra-style-key="' + deviceKey + ':font-style">' +
                        '<option value="normal"' + (String(styleValueOrDefault(style, 'font-style', 'normal')) === 'normal' ? ' selected' : '') + '>正常</option>' +
                        '<option value="italic"' + (String(styleValueOrDefault(style, 'font-style', 'normal')) === 'italic' ? ' selected' : '') + '>斜体</option>' +
                    '</select></div>' +
                    '<div class="cms-builder-field"><label>对齐</label><select data-extra-style-key="' + deviceKey + ':text-align">' +
                        '<option value="left"' + (String(styleValueOrDefault(style, 'text-align', 'left')) === 'left' ? ' selected' : '') + '>左对齐</option>' +
                        '<option value="center"' + (String(styleValueOrDefault(style, 'text-align', 'left')) === 'center' ? ' selected' : '') + '>居中</option>' +
                        '<option value="right"' + (String(styleValueOrDefault(style, 'text-align', 'left')) === 'right' ? ' selected' : '') + '>右对齐</option>' +
                    '</select></div>' +
                    '<div class="cms-builder-field"><label>行高</label><input type="text" data-extra-style-key="' + deviceKey + ':line-height" value="' + escapeHtml(styleValueOrDefault(style, 'line-height', '1.8')) + '"></div>' +
                '</div>' +
            '</div>' +
            '<div class="cms-builder-group" style="margin-top:16px;">' +
                '<div class="cms-builder-group-title">背景样式</div>' +
                '<div class="cms-builder-fields">' +
                    '<div class="cms-builder-field"><label>背景类型</label><select data-extra-style-key="' + deviceKey + ':background_type">' +
                        '<option value="solid"' + (backgroundType === 'solid' ? ' selected' : '') + '>纯色</option>' +
                        '<option value="gradient"' + (backgroundType === 'gradient' ? ' selected' : '') + '>渐变</option>' +
                        '<option value="image"' + (backgroundType === 'image' ? ' selected' : '') + '>背景图</option>' +
                    '</select></div>' +
                    '<div class="cms-builder-field"><label>背景色</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-extra-style-key="' + deviceKey + ':background_color" value="' + escapeHtml(styleValueOrDefault(style, 'background_color', '#ffffff')) + '"><input type="color" data-extra-style-color-key="' + deviceKey + ':background_color" value="' + escapeHtml(normalizeColorPickerValue(styleValueOrDefault(style, 'background_color', '#ffffff'), '#ffffff')) + '" style="width:52px;height:42px;padding:4px 6px;border-radius:10px;flex:0 0 52px;cursor:pointer;"></div></div>' +
                    '<div class="cms-builder-field"><label>渐变起点</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-extra-style-key="' + deviceKey + ':gradient_from" value="' + escapeHtml(gradientFrom) + '"><input type="color" data-extra-style-color-key="' + deviceKey + ':gradient_from" value="' + escapeHtml(normalizeColorPickerValue(gradientFrom, '#ffffff')) + '" style="width:52px;height:42px;padding:4px 6px;border-radius:10px;flex:0 0 52px;cursor:pointer;"></div></div>' +
                    '<div class="cms-builder-field"><label>渐变终点</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-extra-style-key="' + deviceKey + ':gradient_to" value="' + escapeHtml(gradientTo) + '"><input type="color" data-extra-style-color-key="' + deviceKey + ':gradient_to" value="' + escapeHtml(normalizeColorPickerValue(gradientTo, '#ffffff')) + '" style="width:52px;height:42px;padding:4px 6px;border-radius:10px;flex:0 0 52px;cursor:pointer;"></div></div>' +
                    '<div class="cms-builder-field"><label>背景图</label><input type="text" data-extra-style-key="' + deviceKey + ':background_image" value="' + escapeHtml(styleValueOrDefault(style, 'background_image', '')) + '"></div>' +
                    '<div class="cms-builder-field"><label>重复方式</label><select data-extra-style-key="' + deviceKey + ':background_repeat">' +
                        '<option value="no-repeat"' + (backgroundRepeat === 'no-repeat' ? ' selected' : '') + '>不平铺</option>' +
                        '<option value="repeat"' + (backgroundRepeat === 'repeat' ? ' selected' : '') + '>平铺</option>' +
                        '<option value="repeat-x"' + (backgroundRepeat === 'repeat-x' ? ' selected' : '') + '>横向平铺</option>' +
                        '<option value="repeat-y"' + (backgroundRepeat === 'repeat-y' ? ' selected' : '') + '>纵向平铺</option>' +
                    '</select></div>' +
                    '<div class="cms-builder-field"><label>背景定位</label><input type="text" data-extra-style-key="' + deviceKey + ':background_position" value="' + escapeHtml(styleValueOrDefault(style, 'background_position', 'center center')) + '"></div>' +
                    '<div class="cms-builder-field"><label>背景尺寸</label><input type="text" data-extra-style-key="' + deviceKey + ':background_size" value="' + escapeHtml(styleValueOrDefault(style, 'background_size', 'cover')) + '"></div>' +
                '</div>' +
            '</div>' +
            '<div class="cms-builder-group" style="margin-top:16px;">' +
                '<div class="cms-builder-group-title">交互样式</div>' +
                '<div class="cms-builder-fields">' +
                    '<div class="cms-builder-field"><label>Hover 变色</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-extra-style-key="' + deviceKey + ':hover_color" value="' + escapeHtml(styleValueOrDefault(style, 'hover_color', '')) + '" placeholder="#..."><input type="color" data-extra-style-color-key="' + deviceKey + ':hover_color" value="' + escapeHtml(normalizeColorPickerValue(styleValueOrDefault(style, 'hover_color', '#ffffff'), '#ffffff')) + '" style="width:52px;height:42px;padding:4px 6px;border-radius:10px;flex:0 0 52px;cursor:pointer;"></div></div>' +
                    '<div class="cms-builder-field"><label>点击效果</label><select data-extra-style-key="' + deviceKey + ':click_effect">' +
                        '<option value=""' + (String(styleValueOrDefault(style, 'click_effect', '')) === '' ? ' selected' : '') + '>无</option>' +
                        '<option value="press"' + (String(styleValueOrDefault(style, 'click_effect', '')) === 'press' ? ' selected' : '') + '>按压缩放</option>' +
                        '<option value="glow"' + (String(styleValueOrDefault(style, 'click_effect', '')) === 'glow' ? ' selected' : '') + '>发光</option>' +
                    '</select></div>' +
                    '<div class="cms-builder-field"><label>入场动画</label><select data-extra-style-key="' + deviceKey + ':enter_animation">' +
                        '<option value=""' + (String(styleValueOrDefault(style, 'enter_animation', '')) === '' ? ' selected' : '') + '>无</option>' +
                        '<option value="fade-up"' + (String(styleValueOrDefault(style, 'enter_animation', '')) === 'fade-up' ? ' selected' : '') + '>淡入上移</option>' +
                        '<option value="fade"' + (String(styleValueOrDefault(style, 'enter_animation', '')) === 'fade' ? ' selected' : '') + '>淡入</option>' +
                        '<option value="zoom-in"' + (String(styleValueOrDefault(style, 'enter_animation', '')) === 'zoom-in' ? ' selected' : '') + '>缩放进入</option>' +
                    '</select></div>' +
                    '<div class="cms-builder-field"><label>动画时长</label><input type="text" data-extra-style-key="' + deviceKey + ':animation_duration" value="' + escapeHtml(styleValueOrDefault(style, 'animation_duration', '0.3s')) + '"></div>' +
                '</div>' +
            '</div>';
    }

    function updateExtraStyleLive(extraId, key, value) {
        var parsed = extraStyleFromKey(key);
        var extra = null;
        state.page.frontend_extra_sections = state.page.frontend_extra_sections.map(function (item) {
            if (String(item.id) === String(extraId)) {
                extra = item;
            }
            return item;
        });
        if (!extra) {
            return;
        }
        var bucket = ensureExtraStyleState(extra, parsed.device);
        bucket[parsed.name] = value;
        if (parsed.name === 'background_type' && value === 'image' && !bucket.background_size) {
            bucket.background_size = 'cover';
        }
        rebuildBusinessExtra(extraId, {live: true});
        queueSaveDraft();
    }

    function currentOverrides() {
        return currentDevice() === 'mobile' ? state.page.frontend_mobile_overrides : state.page.frontend_overrides;
    }

    function currentOverrideBucket() {
        var bucket = currentOverrides();
        if (!bucket || typeof bucket !== 'object' || Array.isArray(bucket)) {
            bucket = {};
            if (currentDevice() === 'mobile') {
                state.page.frontend_mobile_overrides = bucket;
            } else {
                state.page.frontend_overrides = bucket;
            }
        }
        return bucket;
    }

    function overrideEntryForRef(ref) {
        var bucket = currentOverrideBucket();
        var stable = refKey(ref);
        if (stable && bucket[stable]) {
            return bucket[stable];
        }
        var legacyPath = ref && typeof ref === 'object' ? ref.path : (typeof ref === 'string' ? ref : '');
        if (legacyPath && bucket[legacyPath]) {
            if (stable) {
                bucket[stable] = bucket[legacyPath];
                delete bucket[legacyPath];
            }
            return bucket[stable || legacyPath];
        }
        return null;
    }

    function ensureOverrideEntry(ref) {
        var bucket = currentOverrideBucket();
        var stable = refKey(ref);
        if (stable) {
            if (!bucket[stable]) {
                var legacyPath = ref && typeof ref === 'object' ? ref.path : '';
                bucket[stable] = legacyPath && bucket[legacyPath] ? bucket[legacyPath] : {};
                if (legacyPath && bucket[legacyPath]) {
                    delete bucket[legacyPath];
                }
            }
            return bucket[stable];
        }
        var fallbackPath = ref && typeof ref === 'object' ? ref.path : (typeof ref === 'string' ? ref : '');
        if (!fallbackPath) {
            return null;
        }
        if (!bucket[fallbackPath]) {
            bucket[fallbackPath] = {};
        }
        return bucket[fallbackPath];
    }

    function listHasRef(list, ref) {
        return (list || []).some(function (item) {
            return refsEqual(normalizeStoredRef(item), ref);
        });
    }

    function toggleRefValue(list, ref) {
        var found = false;
        var next = (list || []).filter(function (item) {
            var normalized = normalizeStoredRef(item);
            if (refsEqual(normalized, ref)) {
                found = true;
                return false;
            }
            return true;
        });
        if (!found) {
            next.push({path: ref.path || '', key: ref.key || ''});
        }
        return next;
    }

    function removeRefValue(list, ref) {
        return (list || []).filter(function (item) {
            return !refsEqual(normalizeStoredRef(item), ref);
        });
    }

    function disableFreeCanvasState() {
        var changed = false;
        var freeHidden = Array.isArray(state.page.frontend_free_layout_hidden_paths)
            ? state.page.frontend_free_layout_hidden_paths.slice()
            : [];
        if (state.page.free_layout_mode) {
            state.page.free_layout_mode = 0;
            changed = true;
        }
        if (Array.isArray(state.page.frontend_canvas_blocks) && state.page.frontend_canvas_blocks.length) {
            state.page.frontend_canvas_blocks = [];
            changed = true;
        }
        if (freeHidden.length && Array.isArray(state.page.frontend_hidden_paths)) {
            var nextHidden = state.page.frontend_hidden_paths.filter(function (item) {
                return !listHasRef(freeHidden, normalizeStoredRef(item));
            });
            if (nextHidden.length !== state.page.frontend_hidden_paths.length) {
                state.page.frontend_hidden_paths = nextHidden;
                changed = true;
            }
        }
        if (freeHidden.length) {
            state.page.frontend_free_layout_hidden_paths = [];
            changed = true;
        }
        if (state.page.frontend_editor_state && state.page.frontend_editor_state.free_layout_mode) {
            state.page.frontend_editor_state.free_layout_mode = 0;
            changed = true;
        }
        state.selectedCanvasBlockId = '';
        state.selectedCanvasBlockIds = [];
        return changed;
    }

    function currentHiddenPaths() {
        return currentDevice() === 'mobile' ? state.page.frontend_mobile_hidden_paths : state.page.frontend_hidden_paths;
    }

    function currentDeletedPaths() {
        return currentDevice() === 'mobile' ? state.page.frontend_mobile_deleted_paths : state.page.frontend_deleted_paths;
    }

    function setCurrentHiddenPaths(paths) {
        if (currentDevice() === 'mobile') {
            state.page.frontend_mobile_hidden_paths = paths;
        } else {
            state.page.frontend_hidden_paths = paths;
        }
    }

    function setCurrentDeletedPaths(paths) {
        if (currentDevice() === 'mobile') {
            state.page.frontend_mobile_deleted_paths = paths;
        } else {
            state.page.frontend_deleted_paths = paths;
        }
    }

    function addPathRef(bucketName, ref) {
        if (!ref || (!ref.path && !ref.key)) {
            return;
        }
        var list = Array.isArray(state.page[bucketName]) ? state.page[bucketName] : [];
        if (!listHasRef(list, ref)) {
            list.push({path: ref.path || '', key: ref.key || ''});
        }
        state.page[bucketName] = list;
    }

    function postApi(action, payload) {
        var apiUrl = boot.api_url + '&action=' + encodeURIComponent(action) + '&type=' + encodeURIComponent(boot.builder_type) + '&id=' + encodeURIComponent(boot.builder_ref_id);
        if (boot.builder_token) {
            apiUrl += '&builder_token=' + encodeURIComponent(boot.builder_token);
        }
        return fetch(apiUrl, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Requested-With': 'XMLHttpRequest',
                'X-CSRF-Token': boot.csrf_token || ''
            },
            body: JSON.stringify(Object.assign({csrf_token: boot.csrf_token, builder_token: boot.builder_token || ''}, payload || {}))
        }).then(function (response) {
            return response.json();
        });
    }

    function renderCollaborators(editors) {
        editors=Array.isArray(editors)?editors:[];state.collaborators=editors;
        var others=editors.filter(function(item){return !item.is_current;}),pill=qs('#cms_builder_presence');
        if(!pill)return;
        var names=others.map(function(item){return item.admin_name||'协作用户';}),signature=names.join('|');
        pill.classList.toggle('has-others',others.length>0);
        pill.textContent=others.length?(others.length+' 人同时编辑'):'仅你在编辑';
        pill.title=others.length?('当前协作者：'+names.join('、')):'当前没有其他编辑者';
        if(signature&&signature!==state.collaboratorSignature)builderToast(names.join('、')+' 正在同时编辑此页面','warning');
        state.collaboratorSignature=signature;
    }

    function heartbeatCollaborators() {
        if(!boot.edit_mode||!boot.can_operate||document.visibilityState==='hidden')return Promise.resolve();
        return postApi('heartbeat',{editor_token:state.editorToken,version_no:state.serverVersion}).then(function(result){if(result&&result.code===200)renderCollaborators(result.editors);}).catch(function(){});
    }

    function leaveBuilderSession() {
        if(!boot.edit_mode||!boot.can_operate)return;
        var apiUrl=boot.api_url+'&action=leave_edit&type='+encodeURIComponent(boot.builder_type)+'&id='+encodeURIComponent(boot.builder_ref_id);
        if(boot.builder_token)apiUrl+='&builder_token='+encodeURIComponent(boot.builder_token);
        try{fetch(apiUrl,{method:'POST',headers:{'Content-Type':'application/json','X-Requested-With':'XMLHttpRequest','X-CSRF-Token':boot.csrf_token||''},body:JSON.stringify({csrf_token:boot.csrf_token,builder_token:boot.builder_token||'',editor_token:state.editorToken}),keepalive:true});}catch(error){}
    }

    function builderUploadUrl() {
        var uploadUrl = boot.upload_url || '/admin/main.php?act=upload';
        var joiner = uploadUrl.indexOf('?') === -1 ? '?' : '&';
        uploadUrl += joiner + 'builder_type=' + encodeURIComponent(boot.builder_type || 'home') + '&builder_ref_id=' + encodeURIComponent(boot.builder_ref_id || 0);
        if (boot.builder_token) {
            uploadUrl += '&builder_token=' + encodeURIComponent(boot.builder_token);
        }
        return uploadUrl;
    }

    function sanitizeOverrideStyleForEdit(node, styleText) {
        var nextStyle = styleText == null ? '' : String(styleText);
        if (!boot.edit_mode || nextStyle === '') {
            return nextStyle;
        }
        nextStyle = nextStyle
            .replace(/(^|;)\s*display\s*:\s*none(?:\s*!important)?\s*(?=;|$)/ig, '$1')
            .replace(/(^|;)\s*visibility\s*:\s*hidden(?:\s*!important)?\s*(?=;|$)/ig, '$1')
            .replace(/(^|;)\s*pointer-events\s*:\s*none(?:\s*!important)?\s*(?=;|$)/ig, '$1');
        nextStyle = nextStyle.replace(/;;+/g, ';').replace(/^\s*;|;\s*$/g, '').trim();
        if (node && /^(cms_builder_design_canvas|cms_frontend_builder_root|cms_builder_overlay_layer|cms_builder_page_widgets)$/.test(node.id || '')) {
            nextStyle += (nextStyle ? ';' : '') + 'display:block !important; visibility:visible !important; pointer-events:auto;';
        }
        return nextStyle;
    }

    function postShellMessage(type, data) {
        if (!boot.edit_mode || !window.parent || window.parent === window) {
            return;
        }
        window.parent.postMessage({
            source: 'cms_builder_frontend',
            type: type,
            data: data || {}
        }, '*');
    }

    function notifyShellDeviceState() {
        postShellMessage('device_changed', {
            device: currentDevice(),
            mobile_width: parseInt(state.page.mobile_page_width, 10) || 375,
            meta: {
                type: boot.builder_type || 'home',
                ref_id: parseInt(boot.builder_ref_id, 10) || 0,
                page_title: builderTitle()
            }
        });
    }

    function nodeMeta(node) {
        if (!node || !(node instanceof Element)) {
            return null;
        }
        var rect = null;
        try {
            rect = node.getBoundingClientRect();
        } catch (error) {
            rect = null;
        }
        return {
            tag: node.tagName ? node.tagName.toLowerCase() : '',
            path: getDomPath(node),
            key: nodeStableKey(node),
            text: (node.innerText || node.textContent || '').replace(/\s+/g, ' ').trim(),
            html: node.innerHTML || '',
            src: node.getAttribute('src') || '',
            href: node.getAttribute('href') || '',
            style: node.getAttribute('style') || '',
            id: node.id || '',
            class_name: typeof node.className === 'string' ? node.className : '',
            rect: rect ? {
                top: rect.top,
                left: rect.left,
                width: rect.width,
                height: rect.height
            } : {top: 0, left: 0, width: 0, height: 0}
        };
    }

    function detectGlobalContactField(node, meta) {
        if (!node || !(node instanceof Element)) {
            return null;
        }
        meta = meta || activeNodeMeta(node) || nodeMeta(node) || {};
        var text = String(meta.text || '').trim();
        var href = String(meta.href || '').trim();
        var src = String(meta.src || '').trim();
        var identity = [
            meta.id || '',
            meta.class_name || '',
            node.getAttribute('name') || '',
            node.getAttribute('data-field') || '',
            node.getAttribute('data-name') || '',
            node.getAttribute('alt') || '',
            node.getAttribute('title') || ''
        ].join(' ').toLowerCase();
        if (/^tel:/i.test(href) || /phone|tel|mobile|热线|电话/.test(identity) || /^(\+?\d[\d\-\s]{5,})$/.test(text)) {
            return {key: 'site_phone', label: '电话', value: text || href.replace(/^tel:/i, '')};
        }
        if (/^mailto:/i.test(href) || /email|mail|邮箱/.test(identity) || /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(text)) {
            return {key: 'site_email', label: '邮箱', value: text || href.replace(/^mailto:/i, '')};
        }
        if (/address|addr|地址|公司地址|联系地址/.test(identity)) {
            return {key: 'site_address', label: '地址', value: text};
        }
        if (/wechat|weixin|vx|微信/.test(identity)) {
            return {key: 'contact_wechat', label: '微信', value: text || src};
        }
        return null;
    }

    function fetchBusinessData(dataType, params) {
        var query = new URLSearchParams(Object.assign({
            act: 'builder_api',
            action: 'business_data',
            type: boot.builder_type,
            id: boot.builder_ref_id,
            data_type: dataType,
            redirect: window.location.pathname + window.location.search
        }, params || {}));
        return fetch('/admin/main.php?' + query.toString(), {
            credentials: 'same-origin'
        }).then(function (response) {
            return response.json();
        });
    }

    function ensureBusinessOptions() {
        if (state.businessOptions.loaded) {
            return Promise.resolve(state.businessOptions);
        }
        return fetchBusinessData('all').then(function (result) {
            var data = result && result.data ? result.data : {};
            state.businessOptions.article_categories = Array.isArray(data.article_categories) ? data.article_categories : [];
            state.businessOptions.product_categories = Array.isArray(data.product_categories) ? data.product_categories : [];
            state.businessOptions.votes = Array.isArray(data.votes) ? data.votes : [];
            state.businessOptions.member_state = data.member_state && typeof data.member_state === 'object' ? data.member_state : {};
            state.businessOptions.site_contact = data.site_contact && typeof data.site_contact === 'object' ? data.site_contact : {};
            state.businessOptions.loaded = true;
            return state.businessOptions;
        }).catch(function () {
            return state.businessOptions;
        });
    }

    function syncAllExtraSectionHtml() {
        qsa('[data-builder-extra-id]').forEach(function (wrapper) {
            var extraId = wrapper.getAttribute('data-builder-extra-id') || '';
            if (!extraId) {
                return;
            }
            state.page.frontend_extra_sections = state.page.frontend_extra_sections.map(function (item) {
                if (String(item.id) === String(extraId)) {
                    item.html = wrapper.innerHTML;
                }
                return item;
            });
        });
    }

    function buildLayout() {
        syncEditorStateToPage();
        syncAllExtraSectionHtml();
        state.page.render_mode = 'frontend_template';
        state.page.advanced_options = normalizeAdvancedOptions(state.page.advanced_options);
        state.page.site_widgets = normalizeSiteWidgets(state.page.site_widgets);
        state.page.site_widget_configs = normalizeSiteWidgetConfigs(state.page.site_widget_configs);
        return {
            page: cloneJsonValue(state.page),
            sections: cloneJsonValue(state.sections)
        };
    }

    function jsonEqual(left,right) {
        try{return JSON.stringify(left)==JSON.stringify(right);}catch(error){return false;}
    }

    function countChangedEntries(left,right) {
        left=left&&typeof left==='object'?left:{};right=right&&typeof right==='object'?right:{};
        var keys={};Object.keys(left).forEach(function(key){keys[key]=1;});Object.keys(right).forEach(function(key){keys[key]=1;});
        return Object.keys(keys).filter(function(key){return !jsonEqual(left[key],right[key]);}).length;
    }

    function moduleChangeCount(base,current) {
        function map(layout){var result={},page=layout&&layout.page?layout.page:{};(layout&&Array.isArray(layout.sections)?layout.sections:[]).forEach(function(item,index){result['section:'+(item.id||item.key||index)]=item;});(Array.isArray(page.frontend_extra_sections)?page.frontend_extra_sections:[]).forEach(function(item,index){result['extra:'+(item.id||index)]=item;});(Array.isArray(page.frontend_canvas_blocks)?page.frontend_canvas_blocks:[]).forEach(function(item,index){result['canvas:'+(item.id||index)]=item;});return result;}
        return countChangedEntries(map(base),map(current));
    }

    function publishChangeSummary() {
        var current=buildLayout(),base=boot.published_layout;
        if(!base)return '这是当前页面的首次发布。发布后装修内容会立即成为线上版本。';
        var basePage=base.page||{},currentPage=current.page||{},modules=moduleChangeCount(base,current);
        var styleKeys=['frontend_overrides','frontend_mobile_overrides','frontend_hidden_paths','frontend_mobile_hidden_paths','frontend_deleted_paths','frontend_mobile_deleted_paths','frontend_block_orders'];
        var widgetKeys=['site_widgets','site_widget_configs'],ignored=styleKeys.concat(widgetKeys,['frontend_extra_sections','frontend_canvas_blocks','frontend_editor_state','device_mode','library_module_tab','style_panel_tab','settings_panel_tab']);
        var styles=styleKeys.filter(function(key){return !jsonEqual(basePage[key],currentPage[key]);}).length;
        var widgets=widgetKeys.filter(function(key){return !jsonEqual(basePage[key],currentPage[key]);}).length;
        var settings=Object.keys(Object.assign({},basePage,currentPage)).filter(function(key){return ignored.indexOf(key)===-1&&!jsonEqual(basePage[key],currentPage[key]);}).length;
        var parts=[];if(modules)parts.push('模块 '+modules+' 项');if(settings)parts.push('页面设置 '+settings+' 项');if(styles)parts.push('样式与显隐 '+styles+' 类');if(widgets)parts.push('全站挂件 '+widgets+' 类');
        var others=state.collaborators.filter(function(item){return !item.is_current;}).length;
        return (parts.length?('本次将发布：'+parts.join('、')+'。'):'当前草稿与线上版本没有检测到内容差异。')+(others?(' 当前还有 '+others+' 名协作者在线，发布前请确认其修改已保存。'):'')+' 发布后将立即替换线上版本。';
    }

    function builderTitle() {
        return state.page.title || boot.page_title || document.title || '网站装修';
    }

    function setStatus(text) {
        var status = qs('#cms_builder_status');
        if (status) {
            status.textContent = text;
            status.setAttribute('data-state',/其他标签页|冲突/.test(text)?'conflict':(/离线|断网/.test(text)?'offline':(/失败|异常/.test(text)?'error':(/正在|保存中|发布中|检测中/.test(text)?'saving':(/已保存|自动保存|发布成功|已恢复/.test(text)?'saved':(/未保存|等待继续保存|新修改/.test(text)?'dirty':'info'))))));
        }
    }

    function builderToast(message,type) {
        type=type||(/失败|错误|不能为空|无法|无效|禁止/.test(String(message||''))?'error':'success');var stack=qs('#cms_builder_toast_stack');if(!stack){stack=document.createElement('div');stack.id='cms_builder_toast_stack';stack.className='cms-builder-toast-stack';stack.setAttribute('aria-live','polite');document.body.appendChild(stack);}var item=document.createElement('div');item.className='cms-builder-toast is-'+type;var dot=document.createElement('i'),text=document.createElement('span'),close=document.createElement('button');text.textContent=String(message||'');close.type='button';close.textContent='×';close.setAttribute('aria-label','关闭');item.appendChild(dot);item.appendChild(text);item.appendChild(close);stack.appendChild(item);var timer=setTimeout(remove,3600);function remove(){clearTimeout(timer);if(item.parentNode)item.parentNode.removeChild(item);}close.onclick=remove;
    }

    function closeBuilderConfirm(answer) {
        var modal=qs('#cms_builder_confirm_modal'),resolve=state.confirmResolve,focus=state.confirmFocus;
        state.confirmResolve=null;state.confirmFocus=null;
        if(modal)modal.classList.add('is-hidden');
        if(focus&&focus.focus)focus.focus();
        if(resolve)resolve(!!answer);
    }

    function builderConfirm(options) {
        options=typeof options==='string'?{message:options}:(options||{});
        var modal=qs('#cms_builder_confirm_modal');if(!modal)return Promise.resolve(false);
        if(state.confirmResolve)closeBuilderConfirm(false);
        state.confirmFocus=document.activeElement;
        qs('#cms_builder_confirm_title').textContent=options.title||'确认操作';
        qs('#cms_builder_confirm_message').textContent=options.message||'确定继续吗？';
        var submit=qs('#cms_builder_confirm_submit'),cancel=qs('#cms_builder_confirm_cancel'),card=modal.querySelector('.cms-builder-confirm-card');
        submit.textContent=options.confirmText||'确认';cancel.textContent=options.cancelText||'取消';card.classList.toggle('is-danger',options.tone==='danger');
        modal.classList.remove('is-hidden');setTimeout(function(){submit.focus();},0);
        return new Promise(function(resolve){state.confirmResolve=resolve;});
    }

    function savedTimeLabel(timestamp) {
        return new Date(timestamp||Date.now()).toLocaleTimeString('zh-CN',{hour12:false,hour:'2-digit',minute:'2-digit',second:'2-digit'});
    }

    function builderStorageKey() {
        return 'cms_builder_saved:'+String(boot.builder_type||'home')+':'+String(boot.builder_ref_id||0);
    }

    function recoveryStoragePrefix() {
        return 'cms_builder_recovery:'+String(boot.builder_type||'home')+':'+String(boot.builder_ref_id||0)+':';
    }

    function localRecoveryKey() {
        return recoveryStoragePrefix()+state.tabId;
    }

    function clearLocalRecovery(key) {
        clearTimeout(state.recoveryTimer);state.recoveryTimer=null;
        try{localStorage.removeItem(key||localRecoveryKey());}catch(error){}
    }

    function persistLocalRecoveryNow() {
        clearTimeout(state.recoveryTimer);state.recoveryTimer=null;
        if(!boot.edit_mode||!boot.can_operate||!state.dirty)return;
        try{localStorage.setItem(localRecoveryKey(),JSON.stringify({schema:1,tab_id:state.tabId,page_id:parseInt(boot.builder_page_id||0,10),server_version:state.serverVersion,saved_at:Date.now(),title:builderTitle(),layout:buildLayout()}));}catch(error){builderToast('浏览器本地灾备空间不足，请尽快手动保存草稿','error');}
    }

    function queueLocalRecovery() {
        clearTimeout(state.recoveryTimer);state.recoveryTimer=setTimeout(persistLocalRecoveryNow,350);
    }

    function newestLocalRecovery() {
        var prefix=recoveryStoragePrefix(),newest=null,now=Date.now(),maxAge=7*24*60*60*1000;
        try{for(var i=localStorage.length-1;i>=0;i--){var key=localStorage.key(i);if(!key||key.indexOf(prefix)!==0)continue;try{var data=JSON.parse(localStorage.getItem(key)||'null');if(!data||data.schema!==1||!data.layout||!data.saved_at||now-data.saved_at>maxAge){localStorage.removeItem(key);continue;}data.storage_key=key;if(!newest||data.saved_at>newest.saved_at)newest=data;}catch(error){localStorage.removeItem(key);}}}catch(error){}
        return newest;
    }

    function restoreLocalRecovery(data) {
        if(!data||!data.layout)return;
        state.page=cloneJsonValue(data.layout.page||{});state.sections=cloneJsonValue(Array.isArray(data.layout.sections)?data.layout.sections:[]);
        state.changeRevision++;state.dirty=true;state.history=[];state.future=[];applyPageState();syncHistoryButtons();
        try{if(data.storage_key&&data.storage_key!==localRecoveryKey())localStorage.removeItem(data.storage_key);}catch(error){}
        persistLocalRecoveryNow();
        if(parseInt(data.server_version||0,10)!==state.serverVersion){state.conflict=true;setStatus('已打开旧版本地灾备 · 为防覆盖已停止保存');builderToast('本地灾备基于旧版本，仅供核对和复制内容','error');return;}
        setStatus('本地灾备已恢复 · 正在保存');queueSaveDraft();
    }

    function offerLocalRecovery() {
        if(state.recoveryPrompted||!boot.edit_mode||!boot.can_operate)return;state.recoveryPrompted=true;
        var data=newestLocalRecovery();if(!data)return;
        var current='';try{current=JSON.stringify(buildLayout());}catch(error){}
        if(current&&current===JSON.stringify(data.layout)){clearLocalRecovery(data.storage_key);return;}
        var stale=parseInt(data.server_version||0,10)!==state.serverVersion,time=new Date(data.saved_at).toLocaleString();
        builderConfirm({title:stale?'发现旧版本本地灾备':'发现未保存的本地草稿',message:(stale?'该灾备基于服务器版本 '+String(data.server_version||0)+'，当前已是版本 '+state.serverVersion+'。打开后仅供核对和复制，不会覆盖服务器。':'检测到 '+time+' 保存的浏览器灾备草稿，可以恢复并继续自动保存。'),confirmText:stale?'打开核对':'恢复草稿',cancelText:'暂不恢复',tone:stale?'danger':''}).then(function(ok){if(ok)restoreLocalRecovery(data);});
    }

    function announceSavedVersion() {
        try{localStorage.setItem(builderStorageKey(),JSON.stringify({tab_id:state.tabId,version_no:state.serverVersion,time:Date.now()}));}catch(error){}
    }

    function showVersionConflict(result) {
        state.conflict=true;state.dirty=true;clearTimeout(state.retryTimer);state.retryTimer=null;
        persistLocalRecoveryNow();
        setStatus('检测到其他标签页更新 · 当前修改尚未覆盖');
        if(state.conflictPromptOpen)return;
        state.conflictPromptOpen=true;
        builderConfirm({title:'检测到编辑冲突',message:'同一页面已在其他标签页保存。当前标签页的修改仍保留在页面中，系统已停止自动保存，重新加载后可继续编辑最新版本。',confirmText:'重新加载',tone:'danger'}).then(function(reload){state.conflictPromptOpen=false;if(reload){state.allowExit=true;window.location.reload();}});
    }

    function scheduleDraftRetry() {
        if(state.conflict||!state.dirty||state.retryTimer)return;
        var delays=[2000,5000,15000,30000],delay=delays[Math.min(state.retryAttempt,delays.length-1)];state.retryAttempt++;
        setStatus((state.online?'网络异常':'当前离线')+' · '+Math.round(delay/1000)+' 秒后重试');
        state.retryTimer=setTimeout(function(){state.retryTimer=null;if(state.online)startDraftSave(false).catch(function(){});else scheduleDraftRetry();},delay);
    }

    function centerNodeInViewport(node) {
        if(!node)return;try{var rect=node.getBoundingClientRect();window.scrollTo({top:Math.max(0,window.pageYOffset+rect.top-window.innerHeight/2+rect.height/2),behavior:'smooth'});}catch(error){}
    }

    function refreshModeStatus() {
        if (BUILDER_FREE_CANVAS_ENABLED && isFreeLayoutMode()) {
            setStatus(state.selectedCanvasBlockId ? '自由布局模式 · 正在编辑自由模块 · Alt 网格吸附 / Shift 10px 微调 / 方向键微移 / Delete 删除' : '自由布局模式 · 可拖拽/缩放 · Alt 网格吸附 / Shift 10px 微调');
            return;
        }
        setStatus('模板区块模式 · 点击编辑后打开属性');
    }

    function clamp(value, min, max) {
        return Math.max(min, Math.min(max, value));
    }

    function clearCanvasGuides() {
        state.canvasGuides = {vertical: [], horizontal: []};
    }

    function canvasSnapThreshold() {
        return 8;
    }

    function canvasGridSize() {
        return 20;
    }

    function buildCanvasGuideHtml() {
        var guides = state.canvasGuides || {vertical: [], horizontal: []};
        var parts = [];
        (guides.vertical || []).forEach(function (value) {
            parts.push('<span class="cms-builder-canvas-guide is-vertical" style="left:' + escapeHtml(Math.round(value)) + 'px;"></span>');
        });
        (guides.horizontal || []).forEach(function (value) {
            parts.push('<span class="cms-builder-canvas-guide is-horizontal" style="top:' + escapeHtml(Math.round(value)) + 'px;"></span>');
        });
        return parts.length ? '<div class="cms-builder-canvas-guides">' + parts.join('') + '</div>' : '';
    }

    function snapCanvasValue(rawValue, candidates, threshold, guideStore) {
        var best = rawValue;
        var bestDistance = threshold + 1;
        (candidates || []).forEach(function (candidate) {
            var distance = Math.abs(rawValue - candidate);
            if (distance <= threshold && distance < bestDistance) {
                best = candidate;
                bestDistance = distance;
            }
        });
        if (bestDistance <= threshold && typeof guideStore === 'object' && guideStore) {
            guideStore.push(best);
        }
        return best;
    }

    function panelNode(role) {
        return qs('[data-panel-role="' + role + '"]');
    }

    function dockNode() {
        return qs('#cms_builder_dock');
    }

    function overlayLayerNode() {
        return qs('#cms_builder_overlay_layer');
    }

    function modalCardNode() {
        return qs('.cms-builder-business-modal-card') || qs('.cms-builder-material-modal-card');
    }

    function applyDockPosition() {
        var dock = dockNode();
        if (!dock || window.innerWidth <= 980) {
            return;
        }
        state.dockPosition.left = clamp(state.dockPosition.left, 14, Math.max(14, window.innerWidth - (dock.offsetWidth || 120) - 14));
        state.dockPosition.top = clamp(state.dockPosition.top, 82, Math.max(82, window.innerHeight - (dock.offsetHeight || 220) - 14));
        dock.style.left = state.dockPosition.left + 'px';
        dock.style.top = state.dockPosition.top + 'px';
        dock.style.right = 'auto';
        dock.style.bottom = 'auto';
    }

    function resetDockPosition() {
        state.dockPosition.left = 18;
        state.dockPosition.top = 118;
        applyDockPosition();
    }

    function modalCardStyle(role) {
        var position = role === 'business-editor' ? state.businessModalPosition : state.materialModalPosition;
        if (!position) {
            return '';
        }
        return ' style="left:' + escapeHtml(position.left) + 'px;top:' + escapeHtml(position.top) + 'px;"';
    }

    function applyPanelPosition(role) {
        var panel = panelNode(role);
        var pos = state.panelPositions[role];
        if (!panel || !pos) {
            return;
        }
        var width = panel.offsetWidth || (role === 'settings' ? 560 : 338);
        var height = panel.offsetHeight || 420;
        pos.left = clamp(pos.left, 14, Math.max(14, window.innerWidth - width - 14));
        pos.top = clamp(pos.top, 82, Math.max(82, window.innerHeight - height - 14));
        panel.style.left = pos.left + 'px';
        panel.style.top = pos.top + 'px';
        panel.style.right = 'auto';
        panel.style.bottom = 'auto';
    }

    function ensurePanelDefaults() {
        applyDockPosition();
        applyPanelPosition('library');
        if (state.panelPositions.settings && !(state.panelPositions.settings.left === 0 && state.panelPositions.settings.top === 82)) {
            applyPanelPosition('settings');
        }
    }

    function resetPanelPosition(role) {
        var panel = panelNode(role);
        if (!panel) {
            return;
        }
        if (role === 'library') {
            state.panelPositions.library.left = 14;
            state.panelPositions.library.top = 82;
        } else if (role === 'settings') {
            var width = panel.offsetWidth || 560;
            var height = panel.offsetHeight || 640;
            state.panelPositions.settings.left = clamp(Math.round((window.innerWidth - width) / 2), 14, Math.max(14, window.innerWidth - width - 14));
            state.panelPositions.settings.top = clamp(Math.round((window.innerHeight - height) / 2), 82, Math.max(82, window.innerHeight - height - 14));
        }
        applyPanelPosition(role);
    }

    function settingsBackdropNode() {
        return qs('#cms_builder_settings_backdrop');
    }

    function syncSettingsBackdrop() {
        var backdrop = settingsBackdropNode();
        if (!backdrop) {
            return;
        }
        backdrop.classList.toggle('is-hidden', !state.settingsPanelOpen);
    }

    function hideSettingsPanel() {
        var panel = panelNode('settings');
        state.settingsPanelOpen = false;
        if (panel) {
            panel.classList.add('is-hidden');
        }
        syncSettingsBackdrop();
    }

    function libraryModes() {
        return [
            {key: 'modules', label: '模块', icon: 'modules'},
            {key: 'style', label: '样式', icon: 'style'},
            {key: 'settings', label: '设置', icon: 'settings'},
            {key: 'tools', label: '工具', icon: 'tools'}
        ];
    }

    function libraryIcon(name) {
        var icons = {
            modules: '<svg viewBox="0 0 24 24" fill="none" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1.5"></rect><rect x="14" y="3" width="7" height="7" rx="1.5"></rect><rect x="3" y="14" width="7" height="7" rx="1.5"></rect><rect x="14" y="14" width="7" height="7" rx="1.5"></rect></svg>',
            style: '<svg viewBox="0 0 24 24" fill="none" stroke-width="2"><path d="M6 4l4 4-6 6a3 3 0 0 0 4 4l6-6 4 4 2-10-10 2z"></path></svg>',
            settings: '<svg viewBox="0 0 24 24" fill="none" stroke-width="2"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-1.8-.3 1.6 1.6 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.2a1.6 1.6 0 0 0-1-1.5 1.6 1.6 0 0 0-1.8.3l-.1.1a2 2 0 0 1-2.8-2.8l.1-.1a1.6 1.6 0 0 0 .3-1.8 1.6 1.6 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.2a1.6 1.6 0 0 0 1.5-1 1.6 1.6 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.6 1.6 0 0 0 1.8.3h.1A1.6 1.6 0 0 0 10 3.2V3a2 2 0 1 1 4 0v.2a1.6 1.6 0 0 0 1 1.5 1.6 1.6 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.6 1.6 0 0 0-.3 1.8v.1a1.6 1.6 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.2a1.6 1.6 0 0 0-1.4 1z"></path></svg>',
            seo: '<svg viewBox="0 0 24 24" fill="none" stroke-width="2"><circle cx="8" cy="12" r="3"></circle><circle cx="16" cy="7" r="2.5"></circle><circle cx="18" cy="16" r="2.5"></circle><path d="M10.5 11l3-2"></path><path d="M10.7 13.3l4.8 1.2"></path></svg>',
            tools: '<svg viewBox="0 0 24 24" fill="none" stroke-width="2"><path d="M4 21h16"></path><path d="M7 21v-4"></path><path d="M12 21v-8"></path><path d="M17 21v-6"></path><path d="M5 9l4-4 4 3 6-5"></path></svg>',
            close: '<svg viewBox="0 0 24 24" fill="none" stroke-width="2"><path d="M6 6l12 12"></path><path d="M18 6L6 18"></path></svg>',
            chevron: '<svg viewBox="0 0 24 24" fill="none" stroke-width="2"><path d="M8 10l4 4 4-4"></path></svg>',
            search: '<svg viewBox="0 0 24 24" fill="none" stroke-width="2"><circle cx="11" cy="11" r="6"></circle><path d="M20 20l-3.5-3.5"></path></svg>',
            text: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><rect x="3" y="4" width="18" height="16" rx="3"></rect><path d="M8 9h8"></path><path d="M12 9v7"></path></svg>',
            image: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><rect x="3" y="5" width="18" height="14" rx="2"></rect><circle cx="9" cy="10" r="1.5"></circle><path d="M21 16l-5-5-5 5-2-2-6 5"></path></svg>',
            button: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><rect x="4" y="8" width="16" height="8" rx="3"></rect><path d="M9 12h6"></path></svg>',
            cards: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><rect x="4" y="5" width="7" height="6" rx="1.5"></rect><rect x="13" y="5" width="7" height="6" rx="1.5"></rect><rect x="4" y="13" width="16" height="6" rx="1.5"></rect></svg>',
            gallery: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M4 7h16"></path><rect x="4" y="4" width="16" height="16" rx="2"></rect><path d="M8 12h8"></path><path d="M8 16h5"></path></svg>',
            article: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><rect x="5" y="3" width="14" height="18" rx="2"></rect><path d="M8 8h8"></path><path d="M8 12h8"></path><path d="M8 16h5"></path></svg>',
            carousel: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M4 12h16"></path><path d="M7 8l-3 4 3 4"></path><path d="M17 8l3 4-3 4"></path><rect x="8" y="7" width="8" height="10" rx="1.5"></rect></svg>',
            video: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><circle cx="12" cy="12" r="8"></circle><path d="M10 9l5 3-5 3z"></path></svg>',
            nav: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M8 7h12"></path><path d="M8 12h12"></path><path d="M8 17h12"></path><circle cx="5" cy="7" r="1"></circle><circle cx="5" cy="12" r="1"></circle><circle cx="5" cy="17" r="1"></circle></svg>',
            container: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><rect x="4" y="5" width="16" height="14" rx="2"></rect><path d="M8 9h8"></path><path d="M8 13h4"></path></svg>',
            hover: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><rect x="4" y="5" width="14" height="14" rx="2"></rect><path d="M18 9l3-3"></path><path d="M15 6h6v6"></path><path d="M8 9h6"></path><path d="M8 13h4"></path></svg>',
            tabs: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><rect x="4" y="5" width="16" height="14" rx="2"></rect><path d="M8 9h3"></path><path d="M13 9h3"></path><path d="M8 13h8"></path></svg>',
            accordion: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><rect x="5" y="4" width="14" height="16" rx="2"></rect><path d="M8 9h8"></path><path d="M8 14h6"></path></svg>',
            columns: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><rect x="4" y="5" width="5" height="14" rx="1"></rect><rect x="10" y="5" width="5" height="14" rx="1"></rect><rect x="16" y="5" width="4" height="14" rx="1"></rect></svg>',
            fullwidth: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M4 8h16"></path><path d="M4 16h16"></path><path d="M7 6l-3 2 3 2"></path><path d="M17 6l3 2-3 2"></path><path d="M7 14l-3 2 3 2"></path><path d="M17 14l3 2-3 2"></path></svg>',
            service: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M5 11a7 7 0 0 1 14 0v4a2 2 0 0 1-2 2h-2"></path><path d="M9 18h6"></path><rect x="3" y="11" width="4" height="7" rx="2"></rect><rect x="17" y="11" width="4" height="7" rx="2"></rect></svg>',
            form: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><rect x="6" y="4" width="12" height="16" rx="2"></rect><path d="M9 8h6"></path><path d="M9 12h6"></path><path d="M9 16h4"></path></svg>',
            message: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M6 7h12a2 2 0 0 1 2 2v7l-4-3H6a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2z"></path></svg>',
            qrcode: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><rect x="4" y="4" width="6" height="6"></rect><rect x="14" y="4" width="6" height="6"></rect><rect x="4" y="14" width="6" height="6"></rect><path d="M14 14h3v3h-3z"></path><path d="M17 17h3v3h-3z"></path><path d="M17 14h3"></path><path d="M14 20v-3"></path></svg>',
            searchplus: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><circle cx="11" cy="11" r="6"></circle><path d="M11 8v6"></path><path d="M8 11h6"></path><path d="M20 20l-3.5-3.5"></path></svg>',
            share: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><circle cx="18" cy="5" r="2"></circle><circle cx="6" cy="12" r="2"></circle><circle cx="18" cy="19" r="2"></circle><path d="M8 12l8-6"></path><path d="M8 12l8 6"></path></svg>',
            member: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><circle cx="12" cy="8" r="4"></circle><path d="M5 20a7 7 0 0 1 14 0"></path></svg>',
            vote: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M7 20V10"></path><path d="M12 20V4"></path><path d="M17 20v-7"></path></svg>',
            query: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><rect x="4" y="4" width="16" height="16" rx="2"></rect><path d="M8 10h5"></path><path d="M8 14h8"></path><path d="M16.5 8.5l2 2"></path><path d="M15 10a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"></path></svg>',
            product: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M6 7l6-3 6 3"></path><path d="M6 7v10l6 3 6-3V7"></path><path d="M6 7l6 3 6-3"></path></svg>',
            folder: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M4 7h6l2 2h8v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z"></path></svg>',
            links: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M10 13a4 4 0 0 1 0-6l2-2a4 4 0 1 1 6 6l-1 1"></path><path d="M14 11a4 4 0 0 1 0 6l-2 2a4 4 0 1 1-6-6l1-1"></path></svg>',
            magic: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M4 20l7-7"></path><path d="M14 4l6 6"></path><path d="M12 6l6 6"></path><path d="M10 8l6 6"></path></svg>',
            eye: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6z"></path><circle cx="12" cy="12" r="3"></circle></svg>',
            eyeoff: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M3 3l18 18"></path><path d="M10.6 10.7A3 3 0 0 0 13.4 13.5"></path><path d="M9.9 5.2A10.8 10.8 0 0 1 12 5c6.5 0 10 7 10 7a18.8 18.8 0 0 1-4 4.8"></path><path d="M6.2 6.3C3.7 8 2 12 2 12s3.5 7 10 7c1.5 0 2.8-.3 4-.8"></path></svg>',
            trash: '<svg viewBox="0 0 24 24" fill="none" stroke-width="1.8"><path d="M4 7h16"></path><path d="M9 7V5h6v2"></path><path d="M8 7l1 12h6l1-12"></path></svg>',
            chevronRight: '<svg viewBox="0 0 24 24" fill="none" stroke-width="2"><path d="M10 8l4 4-4 4"></path></svg>'
        };
        return icons[name] || icons.modules;
    }

    function normalizeColumnUrl(url) {
        try {
            var next = new URL(url, window.location.href);
            if (next.origin !== window.location.origin) {
                return '';
            }
            next.hash = '';
            next.searchParams.delete('builder_edit');
            next.searchParams.delete('builder_window');
            next.searchParams.delete('builder_type');
            next.searchParams.delete('builder_ref_id');
            next.searchParams.delete('builder_token');
            return next.pathname + next.search;
        } catch (error) {
            return '';
        }
    }

    function builderEditUrl(url) {
        try {
            var next = new URL(url, window.location.href);
            if (next.origin !== window.location.origin) {
                return '';
            }
            next.searchParams.set('builder_edit', '1');
            return next.pathname + next.search + next.hash;
        } catch (error) {
            return '';
        }
    }

    function currentColumnOptions() {
        var options = [];
        var seen = {};
        var currentLabel = builderTitle() || document.title || '当前页面';
        var currentUrl = normalizeColumnUrl(window.location.href);
        var configured = Array.isArray(boot.column_links) ? boot.column_links : [];

        configured.forEach(function (item) {
            var normalized = normalizeColumnUrl(item && item.url ? item.url : '');
            var editUrl = item && item.edit_url ? String(item.edit_url) : '';
            if (!normalized || !editUrl || seen[normalized]) {
                return;
            }
            seen[normalized] = true;
            options.push({
                label: item && item.label ? String(item.label) : normalized,
                value: normalized,
                edit_url: editUrl
            });
        });

        if (currentUrl && !seen[currentUrl]) {
            options.unshift({label: currentLabel, value: currentUrl, edit_url: builderEditUrl(window.location.href)});
            seen[currentUrl] = true;
        } else if (currentUrl) {
            options = options.map(function (item) {
                if (item.value === currentUrl) {
                    return Object.assign({}, item, {label: currentLabel || item.label});
                }
                return item;
            });
        }

        qsa('header a, nav a, .nav a, .menu a, .navbar a, .topBar a').forEach(function (link) {
            var href = link.getAttribute('href') || '';
            var text = (link.textContent || '').replace(/\s+/g, ' ').trim();
            var normalized = normalizeColumnUrl(href);
            if (!text || !normalized || seen[normalized]) {
                return;
            }
            if (/^(javascript:|#)/i.test(href)) {
                return;
            }
            seen[normalized] = true;
            options.push({
                label: text,
                value: normalized,
                edit_url: builderEditUrl(href)
            });
        });
        return options;
    }

    function resolveColumnEditUrlFromHref(href) {
        var normalized = normalizeColumnUrl(href || '');
        if (!normalized) {
            return '';
        }
        var matched = currentColumnOptions().find(function (item) {
            return item.value === normalized && item.edit_url;
        });
        return matched ? String(matched.edit_url || '') : '';
    }

    function moduleTypeLabel(type) {
        var map = {
            text: '文本模块',
            image: '图片模块',
            button: '按钮模块',
            article_list: '文章列表',
            product_list: '产品展示',
            carousel: '轮播多图',
            image_text: '图文展示',
            multi_image: '列表多图',
            gallery: '图册目录',
            video_embed: '在线视频',
            channel_nav: '栏目导航',
            guestbook_form: '留言提交',
            qrcode: '二维码',
            friend_links: '友情链接',
            service_panel: '在线客服',
            member_portal: '会员登录',
            vote_form: '在线投票',
            html: '自由容器'
        };
        return map[type] || '通栏模块';
    }

    function blockModuleLabel(item) {
        if (!item) {
            return '通栏模块';
        }
        if (item.extra_type) {
            return moduleTypeLabel(item.extra_type);
        }
        var lowerLabel = String(item.label || '').toLowerCase();
        if (item.tag === 'img' || /img|banner|slide|swiper|carousel/.test(lowerLabel)) {
            return '图片';
        }
        if (item.tag === 'a' || item.tag === 'button') {
            return '按钮模块';
        }
        if (/header|footer|wrap|container|section|module/.test(lowerLabel) || item.tag === 'header' || item.tag === 'footer') {
            return '通栏模块';
        }
        if (item.tag === 'h1' || item.tag === 'h2' || item.tag === 'h3' || item.tag === 'h4' || item.tag === 'h5' || item.tag === 'h6' || item.tag === 'p' || item.tag === 'span' || item.tag === 'li') {
            return '文本模块';
        }
        if (item.tag === 'section' || item.tag === 'article') {
            return '通栏模块';
        }
        return '自由容器';
    }

    function libraryModeMeta(mode) {
        var map = {
            modules: {
                title: '模块',
                desc: '拖入页面新增模块，或点击区块导航定位现有内容。'
            },
            style: {
                title: '样式',
                desc: '设置页面宽度、背景、字体和当前设备视图。'
            },
            settings: {
                title: '设置',
                desc: '查看当前编辑状态，并通过点击页面模块触发右侧属性面板。'
            },
            seo: {
                title: '百度优化',
                desc: '管理当前页面标题与基础优化说明。'
            },
            tools: {
                title: '辅助工具',
                desc: '快速使用撤销、重做、保存、发布与区块导航。'
            }
        };
        return map[mode] || map.modules;
    }

    function syncLibraryDockVisibility() {
        var dock = dockNode();
        var panel = panelNode('library');
        if (!dock || !panel) {
            return;
        }
        dock.classList.toggle('is-hidden', !panel.classList.contains('is-hidden'));
    }

    function hideLibraryPanel() {
        var panel = panelNode('library');
        if (panel) {
            panel.classList.add('is-hidden');
        }
        syncLibraryDockVisibility();
        qsa('[data-library-mode]').forEach(function (button) {
            button.classList.remove('is-active');
        });
    }

    function showLibraryPanel() {
        var panel = panelNode('library');
        if (panel) {
            panel.classList.remove('is-hidden');
        }
        syncLibraryDockVisibility();
    }

    function moduleLibraryGroups() {
        return [
            {
                key: 'common',
                title: '常用',
                searchable: true,
                items: [
                    {label: '文本', icon: 'text', addType: 'text'},
                    {label: '图片', icon: 'image', addType: 'image'},
                    {label: '按钮', icon: 'button', addType: 'button'}
                ]
            },
            {
                key: 'basic',
                title: '基础',
                items: [
                    {label: '图文展示', icon: 'image', addType: 'image_text'},
                    {label: '列表多图', icon: 'cards', addType: 'multi_image'},
                    {label: '文章列表', icon: 'article', addType: 'article_list'},
                    {label: '轮播多图', icon: 'carousel', addType: 'carousel'},
                    {label: '图册目录', icon: 'gallery', addType: 'gallery'},
                    {label: '在线视频', icon: 'video', addType: 'video_embed'},
                    {label: '栏目导航', icon: 'nav', addType: 'channel_nav'}
                ]
            },
            {
                key: 'layout',
                title: '排版',
                items: [
                    {label: '自由容器', icon: 'container', addType: 'free_container'},
                    {label: '悬停容器', icon: 'hover', addType: 'hover_card'},
                    {label: '横向标签', icon: 'tabs', addType: 'tabs_horizontal'},
                    {label: '纵向标签', icon: 'tabs', addType: 'tabs_vertical'},
                    {label: '折叠标签', icon: 'accordion', addType: 'collapse_panel'},
                    {label: '图片标签', icon: 'image', addType: 'image_tabs'},
                    {label: '手风琴', icon: 'accordion', addType: 'accordion'},
                    {label: '多列排版', icon: 'columns', addType: 'columns'},
                    {label: '通栏排版', icon: 'fullwidth', addType: 'fullwidth'}
                ]
            },
            {
                key: 'product',
                title: '产品',
                items: [
                    {label: '产品展示', icon: 'product', addType: 'product_list'},
                    {label: '产品分类', icon: 'folder', addType: 'product_category'},
                    {label: '产品橱窗', icon: 'cards', addType: 'product_showcase'},
                    {label: '产品筛选', icon: 'searchplus', addType: 'product_filter'}
                ]
            },
            {
                key: 'interactive',
                title: '互动',
                items: [
                    {label: '在线客服', icon: 'service', addType: 'service_panel'},
                    {label: '在线表单', icon: 'form', addType: 'guestbook_form'},
                    {label: '留言提交', icon: 'message', addType: 'guestbook_form'},
                    {label: '二维码', icon: 'qrcode', addType: 'qrcode'},
                    {label: '全站搜索', icon: 'searchplus', addType: 'site_search'},
                    {label: '分享网站', icon: 'share', addType: 'site_share'},
                    {label: '会员登录', icon: 'member', addType: 'member_portal'},
                    {label: '在线投票', icon: 'vote', addType: 'vote_form'},
                    {label: '在线查询', icon: 'query', addType: 'query_form'}
                ]
            },
            {
                key: 'advanced',
                title: '高级',
                items: [
                    {label: '友情链接', icon: 'links', addType: 'friend_links'},
                    {label: '高级模块', icon: 'magic', addType: 'advanced_block'}
                ]
            }
        ];
    }

    function renderLibraryModeBar() {
        return '<div class="cms-builder-library-topbar" data-panel-handle="library">' +
            libraryModes().map(function (item) {
                return '<button type="button" class="cms-builder-library-mode' + (state.libraryPanelMode === item.key ? ' is-active' : '') + '" data-library-mode="' + item.key + '" title="' + item.label + '">' +
                    libraryIcon(item.icon) +
                    '<span>' + item.label + '</span>' +
                '</button>';
            }).join('') +
            '<button type="button" class="cms-builder-library-close" id="cms_builder_close_library" title="关闭">' + libraryIcon('close') + '</button>' +
        '</div>';
    }

    function renderModuleCard(item) {
        var attrs = item.addType ? ' data-add-extra="' + item.addType + '"' : ' data-module-disabled="1" disabled';
        return '<button type="button" class="cms-builder-library-item' + (item.addType ? '' : ' is-disabled') + '"' + attrs + ' title="' + escapeHtml(item.addType ? item.label : (item.label + '，暂未接入')) + '">' +
            '<span class="cms-builder-library-item-icon">' + libraryIcon(item.icon) + '</span>' +
            '<span class="cms-builder-library-item-label">' + item.label + '</span>' +
        '</button>';
    }

    function renderModuleGroup(group,forceExpanded) {
        var expanded = forceExpanded||!!state.libraryExpandedGroups[group.key];
        return '<section class="cms-builder-library-section cms-builder-library-group">' +
            '<button type="button" class="cms-builder-library-group-toggle' + (expanded ? '' : ' is-collapsed') + '" data-library-group-toggle="' + group.key + '">' +
                libraryIcon('chevron') +
                '<span>' + group.title + '</span>' +
                '<span class="cms-builder-library-group-line"></span>' +
                (group.searchable ? '<span class="cms-builder-library-group-search" data-library-search="1">' + libraryIcon('search') + '</span>' : '') +
            '</button>' +
            (expanded ? '<div class="cms-builder-library-grid">' + group.items.map(renderModuleCard).join('') + '</div>' : '') +
        '</section>';
    }

    function renderModuleLibraryContent() {
        var tab = state.libraryModuleTab || 'new_module';
        var body = '';
        if (tab === 'new_material') {
            body = renderMaterialLibraryContent();
        } else if (tab === 'section_module') {
            body = renderSectionModuleContent();
        } else {
            var allModuleGroups=moduleLibraryGroups(),moduleNeedle=String(state.moduleSearch||'').trim().toLowerCase(),moduleGroups=allModuleGroups.map(function(group){var copy=Object.assign({},group);copy.items=group.items.filter(function(item){return !moduleNeedle||(String(item.label||'')+' '+String(item.addType||'')).toLowerCase().indexOf(moduleNeedle)!==-1;});return copy;}).filter(function(group){return group.items.length;});
            if(!moduleNeedle&&state.recentModules.length){var allItems=[];allModuleGroups.forEach(function(group){allItems=allItems.concat(group.items);});var recentItems=state.recentModules.map(function(type){return allItems.find(function(item){return item.addType===type;});}).filter(Boolean);if(recentItems.length)moduleGroups.unshift({key:'recent',title:'最近使用',items:recentItems,force_expanded:true});}
            body='<div class="cms-builder-material-searchbar"><div class="cms-builder-material-search"><input type="search" placeholder="搜索模块名称" value="'+escapeHtml(state.moduleSearch||'')+'" data-module-search="1"><span class="cms-builder-material-search-icon">'+libraryIcon('search')+'</span></div></div><div class="cms-builder-library-scroll">'+(moduleGroups.length?moduleGroups.map(function(group){return renderModuleGroup(group,!!moduleNeedle||group.force_expanded);}).join(''):'<div class="cms-builder-material-result-empty">没有找到匹配模块，请更换关键词。</div>')+'</div>';
        }
        return '' +
            '<div class="cms-builder-library-content">' +
                '<div class="cms-builder-library-tabs">' +
                    '<button type="button" class="cms-builder-library-tab' + (tab === 'new_module' ? ' is-active' : '') + '" data-library-module-tab="new_module">新增模块</button>' +
                    '<button type="button" class="cms-builder-library-tab' + (tab === 'new_material' ? ' is-active' : '') + '" data-library-module-tab="new_material">新增素材</button>' +
                    '<button type="button" class="cms-builder-library-tab' + (tab === 'section_module' ? ' is-active' : '') + '" data-library-module-tab="section_module">栏目模块</button>' +
                '</div>' +
                body +
            '</div>';
    }

    function renderSectionModuleContent() {
        collectBlocks();
        var options = currentColumnOptions();
        var currentUrl = normalizeColumnUrl(window.location.href);
        if (!state.currentColumnUrl || !options.some(function (item) { return item.value === state.currentColumnUrl; })) {
            state.currentColumnUrl = currentUrl || (options[0] ? options[0].value : '');
        }
        var optionHtml = options.map(function (item) {
            return '<option value="' + escapeHtml(item.value) + '" data-edit-url="' + escapeHtml(item.edit_url || '') + '"' + (item.value === state.currentColumnUrl ? ' selected' : '') + '>' + escapeHtml(item.label) + '</option>';
        }).join('');
        var listHtml = state.blocks.map(function (item) {
            var active = item.path === state.selectedPath ? ' is-active' : '';
            var hidden = item.hidden ? ' is-hidden' : '';
            var blockNode = queryByPath(item.path);
            var isProtected = isProtectedBlockNode(blockNode);
            return '<div class="cms-builder-column-card' + active + hidden + '">' +
                '<button type="button" class="cms-builder-column-card-main" data-select-path="' + escapeHtml(item.path) + '" title="' + escapeHtml(item.path) + '">' +
                    '<span class="cms-builder-column-card-caret">' + libraryIcon('chevronRight') + '</span>' +
                    '<span><span class="cms-builder-column-card-label">' + escapeHtml(blockModuleLabel(item)) + '</span><span class="cms-builder-column-card-meta">' + escapeHtml(item.label || item.path) + '</span></span>' +
                '</button>' +
                '<div class="cms-builder-column-card-actions">' +
                    '<button type="button" class="cms-builder-column-card-action" data-node-toggle-hidden-path="' + escapeHtml(item.path) + '" title="' + escapeHtml(isProtected ? protectedBlockMessage() : (item.hidden ? '显示模块' : '隐藏模块')) + '"' + (isProtected ? ' disabled' : '') + '>' + libraryIcon(item.hidden ? 'eye' : 'eyeoff') + '</button>' +
                    '<button type="button" class="cms-builder-column-card-action is-danger" data-node-delete-path="' + escapeHtml(item.path) + '" title="' + escapeHtml(isProtected ? protectedBlockMessage() : '删除模块') + '"' + (isProtected ? ' disabled' : '') + '>' + libraryIcon('trash') + '</button>' +
                '</div>' +
            '</div>';
        }).join('');
        if (!listHtml) {
            listHtml = '<div class="cms-builder-empty">当前栏目还没有识别到可编辑模块，先在页面中添加模块或切换到其它栏目。</div>';
        }
        return '<div class="cms-builder-library-note">栏目模块会列出当前页面所有已识别模块。点击列表项定位模块，右侧按钮可直接隐藏、显示或删除当前模块。</div>' +
            '<div class="cms-builder-column-toolbar"><label for="cms_builder_column_switch">选择栏目：</label><select id="cms_builder_column_switch" class="cms-builder-column-select" data-column-switch="1">' + optionHtml + '</select></div>' +
            '<div class="cms-builder-column-list">' + listHtml + '</div>';
    }

    function materialCategories() {
        return [
            {key: 'product_showcase', label: '产品展示'},
            {key: 'article_showcase', label: '文章展示'},
            {key: 'image_text_showcase', label: '图文化展示'},
            {key: 'hover_effect', label: '悬停特效'},
            {key: 'company_advantage', label: '公司优势'},
            {key: 'video_showcase', label: '视频展示'},
            {key: 'contact_us', label: '联系我们'},
            {key: 'service_consult', label: '服务咨询'},
            {key: 'friend_links_showcase', label: '友情链接'},
            {key: 'enterprise_hiring', label: '企业招聘'}
        ];
    }

    function materialCategoryLabel(key) {
        var found = '';
        materialCategories().some(function (item) {
            if (item.key === key) {
                found = item.label;
                return true;
            }
            return false;
        });
        return found || key;
    }

    function materialScopeLabel(scope) {
        return scope === 'mine' ? '我的' : '场景';
    }

    function buildMaterialPreset(categoryKey, index) {
        var categoryLabel = materialCategoryLabel(categoryKey);
        var preset = {
            key: categoryKey + '_' + index,
            category: categoryKey,
            categoryLabel: categoryLabel,
            index: index,
            scope: index <= 2 ? 'mine' : 'scene',
            title: categoryLabel + '方案 ' + index,
            desc: '适合在企业官网中快速落版，可一键插入后继续编辑。',
            type: 'html',
            options: { html: '<div class="builder-extra-text"><h3>新素材</h3></div>' },
            previewKind: 'generic'
        };
        if (categoryKey === 'product_showcase') {
            var productVariants = ['default', 'showcase', 'compact', 'cards', 'minimal'];
            var productNames = ['基础商品卡片', '橱窗精选布局', '紧凑双列列表', '图文网格陈列', '极简产品清单'];
            preset.type = 'product_list';
            preset.title = '产品展示 · ' + productNames[index - 1];
            preset.desc = '自动调用产品数据，适合首页推荐、栏目列表和产品专题。';
            preset.options = { config: { title: productNames[index - 1], limit: index === 3 ? 8 : 6, variant: productVariants[index - 1], show_price: index === 5 ? 0 : 1 } };
            preset.previewKind = 'product';
        } else if (categoryKey === 'article_showcase') {
            var articleVariants = ['default', 'timeline', 'magazine', 'compact', 'highlight'];
            var articleNames = ['资讯列表', '时间轴动态', '杂志封面流', '简讯摘要栏', '首条高亮版'];
            preset.type = 'article_list';
            preset.title = '文章展示 · ' + articleNames[index - 1];
            preset.desc = '自动调用文章数据，适合新闻中心、公告发布和专题资讯区。';
            preset.options = { config: { title: articleNames[index - 1], limit: index === 4 ? 8 : 6, variant: articleVariants[index - 1] } };
            preset.previewKind = 'article';
        } else if (categoryKey === 'image_text_showcase') {
            preset.title = '图文化展示 · 版式 ' + index;
            preset.desc = '适合品牌介绍、产品卖点和服务场景说明。';
            preset.options = { html: materialHtmlTemplate(categoryKey, index) };
            preset.previewKind = 'image_text';
        } else if (categoryKey === 'hover_effect') {
            preset.title = '悬停特效 · 卡片 ' + index;
            preset.desc = '适合卖点卡片、案例入口和图片悬停说明。';
            preset.options = { html: materialHtmlTemplate(categoryKey, index) };
            preset.previewKind = 'hover';
        } else if (categoryKey === 'company_advantage') {
            preset.title = '公司优势 · 布局 ' + index;
            preset.desc = '适合企业实力、核心优势、数字成果与服务承诺展示。';
            preset.options = { html: materialHtmlTemplate(categoryKey, index) };
            preset.previewKind = 'advantage';
        } else if (categoryKey === 'video_showcase') {
            preset.title = '视频展示 · 方案 ' + index;
            preset.desc = '适合企业宣传片、案例视频、工厂实拍和项目讲解。';
            preset.options = { html: materialHtmlTemplate(categoryKey, index) };
            preset.previewKind = 'video';
        } else if (categoryKey === 'contact_us') {
            preset.title = '联系我们 · 布局 ' + index;
            preset.desc = '适合联系页、底部转化区和地图联络信息展示。';
            preset.options = { html: materialHtmlTemplate(categoryKey, index) };
            preset.previewKind = 'contact';
        } else if (categoryKey === 'service_consult') {
            var serviceNames = ['客服卡片矩阵', '悬浮咨询条', '紧凑联系方式', '咨询表单 CTA', '分栏顾问区'];
            if (index <= 3) {
                preset.type = 'service_panel';
                preset.options = { config: { title: serviceNames[index - 1], subtitle: '支持电话、QQ、微信和邮箱等顾问联系方式。', variant: ['cards', 'floating', 'compact'][index - 1] } };
            } else {
                preset.type = 'guestbook_form';
                preset.options = { config: { title: serviceNames[index - 1], subtitle: '填写需求后，我们将在 10 分钟内安排顾问联系您。', button_text: index === 4 ? '立即咨询' : '预约顾问', variant: index === 4 ? 'banner' : 'split' } };
            }
            preset.title = '服务咨询 · ' + serviceNames[index - 1];
            preset.desc = '适合在线客服、销售咨询、预约回访和商机收集。';
            preset.previewKind = 'service';
        } else if (categoryKey === 'friend_links_showcase') {
            var linkNames = ['文字友链', 'Logo 合作墙', '品牌云集', '合作名录', '推荐链接区'];
            if (index <= 2) {
                preset.type = 'friend_links';
                preset.options = { config: { title: linkNames[index - 1], variant: index === 1 ? 'text' : 'logo' } };
            } else {
                preset.options = { html: materialHtmlTemplate(categoryKey, index) };
            }
            preset.title = '友情链接 · ' + linkNames[index - 1];
            preset.desc = '适合合作伙伴、推荐品牌、平台入口和生态联盟展示。';
            preset.previewKind = 'links';
        } else if (categoryKey === 'enterprise_hiring') {
            preset.title = '企业招聘 · 版式 ' + index;
            preset.desc = '适合招聘页、岗位列表、人才招募和团队介绍。';
            preset.options = { html: materialHtmlTemplate(categoryKey, index) };
            preset.previewKind = 'recruit';
        }
        return preset;
    }

    function materialPresets() {
        var presets = [];
        materialCategories().forEach(function (category) {
            for (var i = 1; i <= 5; i++) {
                presets.push(buildMaterialPreset(category.key, i));
            }
        });
        return presets;
    }

    function materialHtmlTemplate(categoryKey, index) {
        var title = materialCategoryLabel(categoryKey);
        if (categoryKey === 'image_text_showcase') {
            if (index === 1) {
                return '<section style="display:grid;grid-template-columns:1.1fr .9fr;gap:28px;align-items:center;padding:32px;border-radius:24px;background:linear-gradient(135deg,#f8fbff,#eef4ff);"><div><div style="font-size:13px;font-weight:700;color:#5b74e8;">品牌推荐</div><h2 style="margin:14px 0 0;font-size:34px;line-height:1.3;color:#0f172a;">用一组大图和卖点说明产品价值</h2><p style="margin:14px 0 0;font-size:14px;line-height:1.9;color:#64748b;">适合放品牌故事、产品主推、服务亮点。支持后续继续改文字和图片。</p><a href=\"javascript:void(0)\" style=\"display:inline-flex;margin-top:18px;padding:12px 18px;border-radius:999px;background:#111827;color:#fff;text-decoration:none;\">立即了解</a></div><div style=\"height:300px;border-radius:22px;background:linear-gradient(135deg,#bfd4ff,#eff5ff);\"></div></section>';
            }
            if (index === 2) {
                return '<section style="padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="height:220px;border-radius:18px;background:linear-gradient(135deg,#d8e5ff,#eef4ff);"></div><div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;margin-top:18px;"><div><h3 style=\"margin:0;font-size:18px;\">快速部署</h3><p style=\"margin:10px 0 0;color:#64748b;line-height:1.8;\">图上文下的说明布局，适合品牌故事卡片。</p></div><div><h3 style=\"margin:0;font-size:18px;\">视觉统一</h3><p style=\"margin:10px 0 0;color:#64748b;line-height:1.8;\">上方氛围图，下方三列说明。</p></div><div><h3 style=\"margin:0;font-size:18px;\">可继续改图</h3><p style=\"margin:10px 0 0;color:#64748b;line-height:1.8;\">插入后仍可替换图片与文案。</p></div></div></section>';
            }
            if (index === 3) {
                return '<section style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;"><article style="padding:24px;border:1px solid #e7edf7;border-radius:22px;background:#fff;"><div style="height:140px;border-radius:16px;background:linear-gradient(135deg,#dbeafe,#eff6ff);"></div><h3 style="margin:16px 0 0;font-size:20px;">图文卡片 A</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.8;">适合产品卖点或服务说明。</p></article><article style="padding:24px;border:1px solid #e7edf7;border-radius:22px;background:#fff;"><div style="height:140px;border-radius:16px;background:linear-gradient(135deg,#c7d2fe,#eef2ff);"></div><h3 style="margin:16px 0 0;font-size:20px;">图文卡片 B</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.8;">三列内容整齐落版。</p></article><article style="padding:24px;border:1px solid #e7edf7;border-radius:22px;background:#fff;"><div style="height:140px;border-radius:16px;background:linear-gradient(135deg,#bfdbfe,#e0f2fe);"></div><h3 style="margin:16px 0 0;font-size:20px;">图文卡片 C</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.8;">适合案例、服务、优势。</p></article></section>';
            }
            if (index === 4) {
                return '<section style="display:grid;grid-template-columns:.92fr 1.08fr;gap:24px;padding:24px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:grid;gap:14px;"><div style="height:148px;border-radius:18px;background:linear-gradient(135deg,#dbeafe,#eff6ff);"></div><div style="height:148px;border-radius:18px;background:linear-gradient(135deg,#fde68a,#fff7ed);"></div></div><div><div style="font-size:12px;font-weight:700;color:#5b74e8;">主打方案</div><h2 style="margin:14px 0 0;font-size:32px;line-height:1.3;">左右混排让内容更有节奏感</h2><p style="margin:14px 0 0;color:#64748b;line-height:1.9;">左侧双图，右侧主文案和按钮，更适合首页主推广和企业核心能力介绍。</p><div style="display:flex;gap:12px;margin-top:18px;"><span style="padding:8px 14px;border-radius:999px;background:#eef2ff;color:#4664df;">图文混排</span><span style="padding:8px 14px;border-radius:999px;background:#f8fafc;color:#475569;">可改内容</span></div></div></section>';
            }
            return '<section style="padding:30px;border-radius:24px;background:linear-gradient(135deg,#0f172a,#1e293b);color:#fff;"><div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;align-items:center;"><div><div style="font-size:12px;font-weight:700;color:#bfdbfe;">图文专题</div><h2 style="margin:14px 0 0;font-size:34px;line-height:1.35;">沉浸式暗色图文化展示</h2><p style="margin:14px 0 0;color:rgba(255,255,255,.76);line-height:1.9;">适合工厂实拍、项目案例、技术实力等高质感专题内容。</p></div><div style="height:260px;border-radius:20px;background:linear-gradient(135deg,#1d4ed8,#38bdf8);"></div></div></section>';
        }
        if (categoryKey === 'hover_effect') {
            if (index === 1) {
                return '<section style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;">' + [1,2,3].map(function (i) {
                    return '<article style="padding:24px;border:1px solid #e7edf7;border-radius:22px;background:#fff;transition:all .25s ease;box-shadow:' + (i === 2 ? '0 16px 28px rgba(67,97,238,.14);transform:translateY(-4px);' : 'none;') + '"><div style="height:132px;border-radius:16px;background:linear-gradient(135deg,#dbeafe,#eff6ff);"></div><h3 style="margin:16px 0 0;font-size:20px;">悬停卡片 ' + i + '</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.8;">强调鼠标经过后的视觉反馈，可用于服务入口与案例列表。</p></article>';
                }).join('') + '</section>';
            }
            if (index === 2) {
                return '<section style="display:grid;grid-template-columns:1.1fr .9fr;gap:20px;"><article style="position:relative;overflow:hidden;padding:28px;border-radius:24px;background:linear-gradient(135deg,#0f172a,#1d4ed8);color:#fff;"><div style="font-size:12px;font-weight:700;color:#93c5fd;">悬停主视觉</div><h2 style="margin:14px 0 0;font-size:34px;line-height:1.35;">鼠标经过后显示深色遮罩与行动按钮</h2><p style="margin:14px 0 0;line-height:1.9;color:rgba(255,255,255,.78);">适合主打产品、案例专题或服务入口。</p><a href="javascript:void(0)" style="display:inline-flex;margin-top:18px;padding:12px 18px;border-radius:999px;background:#fff;color:#0f172a;text-decoration:none;">查看详情</a></article><div style="display:grid;gap:16px;"><article style="padding:22px;border-radius:20px;background:#fff;border:1px solid #e7edf7;"><h3 style="margin:0;font-size:20px;">悬停浮层</h3><p style="margin:12px 0 0;color:#64748b;line-height:1.8;">右侧配合两张辅助卡片，增强层次。</p></article><article style="padding:22px;border-radius:20px;background:#fff;border:1px solid #e7edf7;"><h3 style="margin:0;font-size:20px;">行动入口</h3><p style="margin:12px 0 0;color:#64748b;line-height:1.8;">适合把 CTA 和悬停动画放在同一区域。</p></article></div></section>';
            }
            if (index === 3) {
                return '<section style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px;">' + ['设计服务','品牌策划','产品定制','项目落地'].map(function (text, idx) {
                    return '<article style="position:relative;padding:22px;border-radius:22px;background:' + (idx % 2 === 0 ? '#fff' : 'linear-gradient(180deg,#f8fbff,#eef4ff)') + ';border:1px solid #e7edf7;"><div style="width:48px;height:48px;border-radius:14px;background:#eef2ff;"></div><h3 style="margin:18px 0 0;font-size:20px;">' + text + '</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.8;">四宫格悬停卡片，适合业务入口展示。</p></article>';
                }).join('') + '</section>';
            }
            if (index === 4) {
                return '<section style="padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:grid;grid-template-columns:.9fr 1.1fr;gap:20px;align-items:center;"><div style="display:grid;gap:14px;"><div style="padding:18px;border-radius:18px;background:#f8fbff;">左侧菜单悬停</div><div style="padding:18px;border-radius:18px;background:#f8fbff;">内容切换动画</div><div style="padding:18px;border-radius:18px;background:#f8fbff;">按钮反馈明显</div></div><article style="padding:28px;border-radius:22px;background:linear-gradient(135deg,#eff6ff,#fff7ed);"><div style="font-size:12px;font-weight:700;color:#5b74e8;">交互动效</div><h2 style="margin:14px 0 0;font-size:32px;line-height:1.35;">侧边导航联动主内容的悬停布局</h2><p style="margin:14px 0 0;color:#64748b;line-height:1.9;">适合服务导航、常见问题、产品参数等内容切换区。</p></article></div></section>';
            }
            return '<section style="padding:30px;border-radius:24px;background:linear-gradient(135deg,#111827,#374151);color:#fff;"><div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;">' + ['案例封面','服务方案','品牌故事'].map(function (text, idx) { return '<article style="padding:22px;border-radius:20px;background:rgba(255,255,255,' + (idx === 1 ? '.18' : '.08') + ');border:1px solid rgba(255,255,255,.12);"><div style="height:128px;border-radius:14px;background:linear-gradient(135deg,#1d4ed8,#38bdf8);"></div><h3 style="margin:16px 0 0;font-size:20px;color:#fff;">' + text + '</h3><p style="margin:10px 0 0;color:rgba(255,255,255,.72);line-height:1.8;">暗色沉浸式悬停展示，更适合案例与品牌专题。</p></article>'; }).join('') + '</div></section>';
        }
        if (categoryKey === 'company_advantage') {
            if (index === 1) {
                return '<section style="padding:30px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:flex;align-items:end;justify-content:space-between;gap:20px;"><div><div style="font-size:12px;font-weight:700;color:#5b74e8;">' + title + '</div><h2 style="margin:12px 0 0;font-size:34px;line-height:1.35;">四个数字直接说明企业实力</h2></div><a href="javascript:void(0)" style="padding:10px 16px;border-radius:999px;background:#111827;color:#fff;text-decoration:none;">查看详情</a></div><div style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px;margin-top:22px;"><div style="padding:22px;border-radius:18px;background:#f8fbff;"><strong style="font-size:30px;">15+</strong><div style="margin-top:8px;color:#64748b;">行业经验</div></div><div style="padding:22px;border-radius:18px;background:#f8fbff;"><strong style="font-size:30px;">3000+</strong><div style="margin-top:8px;color:#64748b;">服务客户</div></div><div style="padding:22px;border-radius:18px;background:#f8fbff;"><strong style="font-size:30px;">60+</strong><div style="margin-top:8px;color:#64748b;">专业团队</div></div><div style="padding:22px;border-radius:18px;background:#f8fbff;"><strong style="font-size:30px;">24H</strong><div style="margin-top:8px;color:#64748b;">响应机制</div></div></div></section>';
            }
            if (index === 2) {
                return '<section style="display:grid;grid-template-columns:1fr 1fr;gap:20px;">' + [1,2,3,4].map(function (i) {
                    return '<div style="padding:24px;border-radius:22px;background:linear-gradient(180deg,#fff,#f8fbff);border:1px solid #e7edf7;"><div style="width:42px;height:42px;border-radius:12px;background:#eef2ff;"></div><h3 style="margin:16px 0 0;font-size:20px;">核心优势 ' + i + '</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.8;">用图标、标题和简述说明企业优势、服务承诺和交付标准。</p></div>';
                }).join('') + '</section>';
            }
            if (index === 3) {
                return '<section style="padding:28px;border-radius:24px;background:linear-gradient(135deg,#eff6ff,#fff);"><h2 style="margin:0;font-size:34px;">服务流程清晰，让客户更放心</h2><div style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px;margin-top:24px;">' + ['需求沟通','方案设计','执行交付','长期维护'].map(function (text, idx) { return '<div style="padding:20px;border-radius:18px;background:#fff;border:1px solid #e7edf7;"><div style="font-size:12px;color:#94a3b8;">0' + (idx + 1) + '</div><h3 style="margin:10px 0 0;font-size:18px;">' + text + '</h3></div>'; }).join('') + '</div></section>';
            }
            if (index === 4) {
                return '<section style="display:grid;grid-template-columns:.85fr 1.15fr;gap:24px;align-items:center;padding:30px;border-radius:24px;background:#0f172a;color:#fff;"><div><div style="height:280px;border-radius:22px;background:linear-gradient(135deg,#2563eb,#38bdf8);"></div></div><div><div style="font-size:12px;font-weight:700;color:#93c5fd;">品质保障</div><h2 style="margin:14px 0 0;font-size:34px;line-height:1.35;">把资质、案例和标准放在一个强展示区</h2><p style="margin:14px 0 0;color:rgba(255,255,255,.78);line-height:1.9;">适合制造企业、工程企业、技术服务企业展示实力和标准化能力。</p></div></section>';
            }
            return '<section style="padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;">' + ['硬件设备','技术研发','交付体系','质量管控','售后保障','品牌口碑'].map(function (text) { return '<div style="padding:18px;border-radius:18px;background:#f8fbff;"><h3 style="margin:0;font-size:18px;">' + text + '</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.8;">简洁说明优势亮点。</p></div>'; }).join('') + '</div></section>';
        }
        if (categoryKey === 'video_showcase') {
            if (index === 1) {
                return '<section style="display:grid;grid-template-columns:1.2fr .8fr;gap:22px;align-items:center;padding:26px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="position:relative;height:290px;border-radius:22px;background:linear-gradient(135deg,#0f172a,#1d4ed8);"><span style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:68px;height:68px;border-radius:999px;background:rgba(255,255,255,.9);"></span><span style="position:absolute;left:calc(50% + 7px);top:50%;transform:translate(-50%,-50%);border-left:20px solid #4664df;border-top:12px solid transparent;border-bottom:12px solid transparent;"></span></div><div><div style="font-size:12px;font-weight:700;color:#5b74e8;">视频内容</div><h2 style="margin:14px 0 0;font-size:32px;line-height:1.35;">宣传片、案例讲解或工厂实拍都可直接套用</h2><p style="margin:14px 0 0;color:#64748b;line-height:1.9;">插入后可以继续替换为真实封面图、视频地址或文案说明。</p><div style="display:flex;gap:12px;margin-top:18px;"><span style="padding:8px 14px;border-radius:999px;background:#eef2ff;color:#4664df;">高清封面</span><span style="padding:8px 14px;border-radius:999px;background:#f8fafc;color:#475569;">可编辑</span></div></div></section>';
            }
            if (index === 2) {
                return '<section style="padding:26px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;"><div style="position:relative;height:180px;border-radius:18px;background:linear-gradient(135deg,#0f172a,#1d4ed8);"><span style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:52px;height:52px;border-radius:999px;background:rgba(255,255,255,.9);"></span></div><div style="position:relative;height:180px;border-radius:18px;background:linear-gradient(135deg,#334155,#38bdf8);"><span style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:52px;height:52px;border-radius:999px;background:rgba(255,255,255,.9);"></span></div><div style="padding:18px;border-radius:18px;background:#f8fbff;"><h3 style="margin:0;font-size:20px;">多视频矩阵</h3><p style="margin:12px 0 0;color:#64748b;line-height:1.8;">适合案例集、教程集或工厂多场景展示。</p></div></div></section>';
            }
            if (index === 3) {
                return '<section style="padding:30px;border-radius:24px;background:linear-gradient(135deg,#111827,#1e293b);color:#fff;"><div style="display:grid;grid-template-columns:.95fr 1.05fr;gap:22px;align-items:center;"><div><div style="font-size:12px;font-weight:700;color:#93c5fd;">VIDEO STORY</div><h2 style="margin:14px 0 0;font-size:34px;line-height:1.35;">暗色品牌影片专题区</h2><p style="margin:14px 0 0;color:rgba(255,255,255,.78);line-height:1.9;">更适合品牌故事、工厂纪录片、工艺流程讲解。</p></div><div style="position:relative;height:300px;border-radius:22px;background:linear-gradient(135deg,#1d4ed8,#38bdf8);"><span style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:72px;height:72px;border-radius:999px;background:rgba(255,255,255,.9);"></span></div></div></section>';
            }
            if (index === 4) {
                return '<section style="padding:26px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:grid;grid-template-columns:1fr 1fr;gap:18px;"><div style="position:relative;height:220px;border-radius:20px;background:linear-gradient(135deg,#0f172a,#1d4ed8);"><span style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:58px;height:58px;border-radius:999px;background:rgba(255,255,255,.9);"></span></div><div style="display:grid;gap:12px;"><article style="padding:18px;border-radius:18px;background:#f8fbff;"><h3 style="margin:0;font-size:20px;">视频标题一</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.8;">适合左视频右目录的内容结构。</p></article><article style="padding:18px;border-radius:18px;background:#f8fbff;"><h3 style="margin:0;font-size:20px;">视频标题二</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.8;">支持系列视频与内容导航。</p></article></div></div></section>';
            }
            return '<section style="padding:28px;border-radius:24px;background:linear-gradient(135deg,#eff6ff,#fff);"><div style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;"><div style="position:relative;height:148px;border-radius:18px;background:linear-gradient(135deg,#0f172a,#1d4ed8);"><span style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:46px;height:46px;border-radius:999px;background:rgba(255,255,255,.9);"></span></div><div style="position:relative;height:148px;border-radius:18px;background:linear-gradient(135deg,#334155,#38bdf8);"><span style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:46px;height:46px;border-radius:999px;background:rgba(255,255,255,.9);"></span></div><div style="position:relative;height:148px;border-radius:18px;background:linear-gradient(135deg,#1d4ed8,#60a5fa);"><span style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:46px;height:46px;border-radius:999px;background:rgba(255,255,255,.9);"></span></div><div style="padding:18px;border-radius:18px;background:#fff;"><h3 style="margin:0;font-size:18px;">短视频合集</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.8;">适合短内容、工艺片段和客户见证集合。</p></div></div></section>';
        }
        if (categoryKey === 'contact_us') {
            if (index === 1) {
                return '<section style="display:grid;grid-template-columns:1fr 1fr;gap:22px;padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div><h2 style="margin:0;font-size:32px;">联系我们</h2><p style="margin:14px 0 0;color:#64748b;line-height:1.9;">适合联系页主视觉，左侧联系方式，右侧地图或门店图。</p><div style="display:grid;gap:10px;margin-top:18px;"><div style="padding:14px 16px;border-radius:14px;background:#f8fbff;">电话：400-888-1234</div><div style="padding:14px 16px;border-radius:14px;background:#f8fbff;">邮箱：service@example.com</div><div style="padding:14px 16px;border-radius:14px;background:#f8fbff;">地址：上海市浦东新区示例大道 88 号</div></div></div><div style="height:280px;border-radius:20px;background:linear-gradient(135deg,#dbeafe,#eff6ff);"></div></section>';
            }
            if (index === 2) {
                return '<section style="padding:28px;border-radius:24px;background:linear-gradient(135deg,#eff6ff,#fff);"><div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;"><div style="padding:24px;border-radius:20px;background:#fff;"><h3 style="margin:0;font-size:18px;">电话咨询</h3><p style="margin:12px 0 0;color:#64748b;">400-888-1234</p></div><div style="padding:24px;border-radius:20px;background:#fff;"><h3 style="margin:0;font-size:18px;">商务邮箱</h3><p style="margin:12px 0 0;color:#64748b;">hello@example.com</p></div><div style="padding:24px;border-radius:20px;background:#fff;"><h3 style="margin:0;font-size:18px;">办公地址</h3><p style="margin:12px 0 0;color:#64748b;">上海浦东新区示例大道 88 号</p></div></div></section>';
            }
            if (index === 3) {
                return '<section style="display:grid;grid-template-columns:.95fr 1.05fr;gap:20px;padding:28px;border-radius:24px;background:#0f172a;color:#fff;"><div><div style="font-size:12px;font-weight:700;color:#93c5fd;">服务网络</div><h2 style="margin:14px 0 0;font-size:34px;line-height:1.35;">让联系信息更正式、更具品牌感</h2><p style="margin:14px 0 0;color:rgba(255,255,255,.78);line-height:1.9;">适合高端品牌或科技企业使用。</p></div><div style="height:260px;border-radius:20px;background:linear-gradient(135deg,#1d4ed8,#38bdf8);"></div></section>';
            }
            if (index === 4) {
                return '<section style="padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:flex;align-items:center;justify-content:space-between;gap:16px;"><div><h2 style="margin:0;font-size:30px;">联系我们 / CONTACT</h2><p style="margin:12px 0 0;color:#64748b;">顶部标题 + 底部联系条，适合网站底部强转化区。</p></div><a href="javascript:void(0)" style="padding:12px 18px;border-radius:999px;background:#111827;color:#fff;text-decoration:none;">预约来访</a></div><div style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;margin-top:22px;"><div style="padding:18px;border-radius:18px;background:#f8fbff;">服务热线</div><div style="padding:18px;border-radius:18px;background:#f8fbff;">商务邮箱</div><div style="padding:18px;border-radius:18px;background:#f8fbff;">公司地址</div><div style="padding:18px;border-radius:18px;background:#f8fbff;">在线地图</div></div></section>';
            }
            return '<section style="padding:28px;border-radius:24px;background:linear-gradient(135deg,#fff7ed,#fff);"><div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;"><div style="height:280px;border-radius:20px;background:linear-gradient(135deg,#fb923c,#fdba74);"></div><div><h2 style="margin:0;font-size:32px;">门店 / 工厂 / 办公室地址说明区</h2><p style="margin:14px 0 0;color:#64748b;line-height:1.9;">更适合线下企业、展厅、体验中心或工厂联系页。</p></div></div></section>';
        }
        if (categoryKey === 'friend_links_showcase') {
            if (index === 3) {
                return '<section style="padding:26px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:flex;align-items:center;justify-content:space-between;gap:12px;"><h2 style="margin:0;font-size:28px;">合作品牌云集</h2><span style="padding:8px 14px;border-radius:999px;background:#eef2ff;color:#4664df;">友情链接</span></div><div style="display:flex;flex-wrap:wrap;gap:12px;margin-top:22px;">' + ['华为云','阿里云','京东工业','三棵树','百安居','居然之家','红星美凯龙','腾讯云'].map(function (name, idx) { return '<span style="padding:12px 16px;border-radius:999px;background:' + (idx % 2 === 0 ? '#f8fbff' : '#eef4ff') + ';border:1px solid #e7edf7;color:#475569;font-weight:700;">' + name + '</span>'; }).join('') + '</div></section>';
            }
            if (index === 4) {
                return '<section style="padding:26px;border-radius:24px;background:linear-gradient(135deg,#eff6ff,#fff);"><div style="display:grid;grid-template-columns:.9fr 1.1fr;gap:18px;align-items:center;"><div><div style="font-size:12px;font-weight:700;color:#5b74e8;">合作生态</div><h2 style="margin:14px 0 0;font-size:32px;line-height:1.35;">主标题 + 品牌矩阵的友情链接布局</h2><p style="margin:14px 0 0;color:#64748b;line-height:1.9;">更适合合作伙伴页、品牌联盟页或资质合作展示区。</p></div><div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;">' + ['居然之家','红星美凯龙','阿里云','京东工业'].map(function (name) { return '<div style="height:84px;border-radius:18px;background:#fff;border:1px solid #e7edf7;display:flex;align-items:center;justify-content:center;color:#475569;font-weight:700;">' + name + '</div>'; }).join('') + '</div></div></section>';
            }
            if (index === 5) {
                return '<section style="padding:28px;border-radius:24px;background:#0f172a;color:#fff;"><div style="display:flex;align-items:center;justify-content:space-between;gap:16px;"><div><div style="font-size:12px;font-weight:700;color:#93c5fd;">PARTNERS</div><h2 style="margin:14px 0 0;font-size:32px;line-height:1.35;">暗色合作品牌墙</h2></div><a href="javascript:void(0)" style="padding:12px 18px;border-radius:999px;background:#fff;color:#0f172a;text-decoration:none;">查看更多</a></div><div style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;margin-top:22px;">' + ['华为云','腾讯云','阿里云','京东工业','圣象地板','欧派家居','索菲亚','三棵树'].map(function (name, idx) { return '<div style="height:78px;border-radius:16px;background:rgba(255,255,255,' + (idx % 2 === 0 ? '.08' : '.14') + ');border:1px solid rgba(255,255,255,.12);display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;">' + name + '</div>'; }).join('') + '</div></section>';
            }
            return '<section style="padding:26px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:flex;align-items:center;justify-content:space-between;gap:12px;"><h2 style="margin:0;font-size:28px;">合作品牌 / 推荐平台</h2><span style="padding:8px 14px;border-radius:999px;background:#eef2ff;color:#4664df;">友情链接</span></div><div style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;margin-top:22px;">' + [1,2,3,4,5,6,7,8].map(function (i) { return '<div style="height:72px;border-radius:16px;background:#f8fbff;display:flex;align-items:center;justify-content:center;color:#64748b;font-weight:700;">品牌 ' + i + '</div>'; }).join('') + '</div></section>';
        }
        if (categoryKey === 'enterprise_hiring') {
            if (index === 1) {
                return '<section style="padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:flex;align-items:end;justify-content:space-between;gap:16px;"><div><div style="font-size:12px;font-weight:700;color:#5b74e8;">加入我们</div><h2 style="margin:12px 0 0;font-size:34px;">企业招聘岗位列表</h2></div><a href="javascript:void(0)" style="padding:12px 18px;border-radius:999px;background:#111827;color:#fff;text-decoration:none;">投递简历</a></div><div style="display:grid;gap:14px;margin-top:22px;"><div style="padding:18px;border-radius:18px;background:#f8fbff;"><strong>招商主管</strong><p style="margin:8px 0 0;color:#64748b;">上海 / 3-5 年 / 全职</p></div><div style="padding:18px;border-radius:18px;background:#f8fbff;"><strong>UI 设计师</strong><p style="margin:8px 0 0;color:#64748b;">上海 / 2-4 年 / 全职</p></div><div style="padding:18px;border-radius:18px;background:#f8fbff;"><strong>销售顾问</strong><p style="margin:8px 0 0;color:#64748b;">杭州 / 经验不限 / 全职</p></div></div></section>';
            }
            if (index === 2) {
                return '<section style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;"><article style="padding:24px;border-radius:22px;background:#fff;border:1px solid #e7edf7;"><div style="font-size:12px;color:#94a3b8;">职位 01</div><h3 style="margin:12px 0 0;font-size:20px;">市场策划</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.8;">双休 / 五险一金 / 年终奖</p></article><article style="padding:24px;border-radius:22px;background:#fff;border:1px solid #e7edf7;"><div style="font-size:12px;color:#94a3b8;">职位 02</div><h3 style="margin:12px 0 0;font-size:20px;">外贸专员</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.8;">英语口语 / 跨境经验 / 高提成</p></article><article style="padding:24px;border-radius:22px;background:#fff;border:1px solid #e7edf7;"><div style="font-size:12px;color:#94a3b8;">职位 03</div><h3 style="margin:12px 0 0;font-size:20px;">前端开发</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.8;">Vue / React / 企业官网项目</p></article></section>';
            }
            if (index === 3) {
                return '<section style="padding:28px;border-radius:24px;background:linear-gradient(135deg,#eff6ff,#fff);"><h2 style="margin:0;font-size:34px;">我们正在寻找更优秀的伙伴</h2><div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-top:22px;"><div style="padding:22px;border-radius:20px;background:#fff;"><strong>销售经理</strong><p style="margin:10px 0 0;color:#64748b;">华东区 / 年薪面议 / 管理岗</p></div><div style="padding:22px;border-radius:20px;background:#fff;"><strong>项目经理</strong><p style="margin:10px 0 0;color:#64748b;">制造业数字化项目 / 交付管理</p></div></div></section>';
            }
            if (index === 4) {
                return '<section style="display:grid;grid-template-columns:.9fr 1.1fr;gap:22px;padding:28px;border-radius:24px;background:#0f172a;color:#fff;"><div><div style="height:280px;border-radius:20px;background:linear-gradient(135deg,#1d4ed8,#38bdf8);"></div></div><div><div style="font-size:12px;font-weight:700;color:#93c5fd;">JOIN US</div><h2 style="margin:14px 0 0;font-size:34px;line-height:1.35;">更有品牌感的企业招聘专题版式</h2><p style="margin:14px 0 0;color:rgba(255,255,255,.78);line-height:1.9;">适合校招专区、品牌招聘页或专题招聘会活动页。</p></div></section>';
            }
            return '<section style="padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;">' + ['成长路径','福利待遇','办公环境','团队文化'].map(function (text) { return '<div style="padding:18px;border-radius:18px;background:#f8fbff;"><h3 style="margin:0;font-size:18px;">' + text + '</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.8;">招聘页配套介绍模块。</p></div>'; }).join('') + '</div></section>';
        }
        return '<section style="padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><h2 style="margin:0;font-size:30px;">' + escapeHtml(title) + '</h2></section>';
    }

    function materialSvgDataUri(markup) {
        return 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(markup);
    }

    function materialMockImage(title, subtitle, startColor, endColor, darkMode) {
        var titleColor = darkMode ? '#ffffff' : '#0f172a';
        var subtitleColor = darkMode ? 'rgba(255,255,255,0.78)' : '#475569';
        return materialSvgDataUri(
            '<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="760" viewBox="0 0 1200 760">' +
                '<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1"><stop offset="0%" stop-color="' + startColor + '"/><stop offset="100%" stop-color="' + endColor + '"/></linearGradient></defs>' +
                '<rect width="1200" height="760" rx="48" fill="url(#g)"/>' +
                '<circle cx="1036" cy="124" r="144" fill="rgba(255,255,255,0.18)"/><circle cx="172" cy="634" r="116" fill="rgba(255,255,255,0.14)"/>' +
                '<rect x="84" y="92" width="470" height="28" rx="14" fill="rgba(255,255,255,0.38)"/><rect x="84" y="140" width="360" height="16" rx="8" fill="rgba(255,255,255,0.28)"/>' +
                '<text x="84" y="292" font-size="78" font-family="Microsoft YaHei,Arial,sans-serif" font-weight="700" fill="' + titleColor + '">' + escapeHtml(title) + '</text>' +
                '<text x="84" y="356" font-size="30" font-family="Microsoft YaHei,Arial,sans-serif" fill="' + subtitleColor + '">' + escapeHtml(subtitle) + '</text>' +
                '<rect x="84" y="438" width="232" height="70" rx="35" fill="rgba(15,23,42,0.84)"/><text x="144" y="484" font-size="28" font-family="Microsoft YaHei,Arial,sans-serif" fill="#ffffff">查看详情</text>' +
                '<rect x="720" y="150" width="338" height="420" rx="34" fill="rgba(255,255,255,0.2)"/><rect x="760" y="192" width="256" height="260" rx="26" fill="rgba(255,255,255,0.82)"/>' +
                '<rect x="780" y="474" width="214" height="20" rx="10" fill="rgba(255,255,255,0.72)"/><rect x="780" y="514" width="166" height="16" rx="8" fill="rgba(255,255,255,0.54)"/>' +
            '</svg>'
        );
    }

    function renderMaterialPreviewFrame(preset, subtitle, pill, innerHtml) {
        return '<div class="cms-builder-material-preview-surface">' +
            '<div class="cms-builder-material-preview-header"><div><div class="cms-builder-material-preview-title">' + escapeHtml(preset.title) + '</div><div class="cms-builder-material-preview-sub">' + escapeHtml(subtitle) + '</div></div><span class="cms-builder-material-pill">' + escapeHtml(pill) + '</span></div>' +
            innerHtml +
        '</div>';
    }

    function renderMaterialHtmlSnapshot(preset, large) {
        var html = preset && preset.options && typeof preset.options.html === 'string' ? preset.options.html : '';
        var scale = large ? 0.82 : 0.28;
        var canvasHeight = large ? 620 : 230;
        return renderMaterialPreviewFrame(
            preset,
            '真实版式缩略预览',
            preset.categoryLabel || '素材',
            '<div style="height:' + canvasHeight + 'px;overflow:hidden;border-radius:16px;background:#fff;border:1px solid #e7edf7;"><div style="width:1120px;transform-origin:top left;transform:scale(' + scale + ');">' + html + '</div></div>'
        );
    }

    function renderProductMaterialPreview(preset, large) {
        var cards = [
            {title: '生态板系列', price: '￥168/张', img: materialMockImage('生态板系列', '高密度基材 · 环保饰面', '#c7d2fe', '#e0f2fe', false)},
            {title: '实木门板', price: '￥899/套', img: materialMockImage('实木门板', '木纹质感 · 多色可选', '#f5d0fe', '#dbeafe', false)},
            {title: '户外景观木', price: '￥268/平', img: materialMockImage('景观木', '耐候处理 · 室外可用', '#bbf7d0', '#dbeafe', false)}
        ];
        return renderMaterialPreviewFrame(
            preset,
            '真实产品卡片效果 / ' + escapeHtml(preset.options && preset.options.config ? preset.options.config.variant : 'default'),
            '产品',
            '<div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:' + (large ? 18 : 12) + 'px;">' + cards.map(function (item) {
                return '<article style="padding:' + (large ? 14 : 10) + 'px;border:1px solid #e7edf7;border-radius:16px;background:#fff;"><img src="' + item.img + '" alt="' + escapeHtml(item.title) + '" style="display:block;width:100%;height:' + (large ? 168 : 96) + 'px;object-fit:cover;border-radius:12px;"><h4 style="margin:12px 0 0;font-size:' + (large ? 18 : 13) + 'px;line-height:1.4;color:#0f172a;">' + escapeHtml(item.title) + '</h4><p style="margin:8px 0 0;font-size:' + (large ? 16 : 12) + 'px;font-weight:700;color:#2563eb;">' + escapeHtml(item.price) + '</p></article>';
            }).join('') + '</div>'
        );
    }

    function renderArticleMaterialPreview(preset, large) {
        var rows = [
            {title: '实木门墙一体化设计趋势解析', summary: '从材质、色彩到空间氛围，拆解今年流行的木作一体化方案。'},
            {title: '工厂数字化升级后，交付周期缩短了多少？', summary: '结合真实项目案例，说明生产协同、排产和交付效率的提升路径。'},
            {title: '高端定制客户更关注哪些细节', summary: '从工艺、五金、环保与售后服务几个维度总结客户决策点。'}
        ];
        return renderMaterialPreviewFrame(
            preset,
            '真实文章标题与摘要排版',
            '文章',
            '<div style="display:grid;gap:' + (large ? 14 : 10) + 'px;">' + rows.map(function (row, index) {
                return '<article style="display:grid;grid-template-columns:' + (index === 0 && large ? '180px 1fr' : '1fr') + ';gap:14px;padding:' + (large ? 16 : 12) + 'px;border:1px solid #e7edf7;border-radius:16px;background:#fff;">' + (index === 0 && large ? '<img src="' + materialMockImage('新闻资讯', '企业动态 / 行业洞察', '#bae6fd', '#c7d2fe', false) + '" alt="新闻配图" style="width:180px;height:112px;object-fit:cover;border-radius:12px;">' : '') + '<div><h4 style="margin:0;font-size:' + (large ? 18 : 14) + 'px;line-height:1.5;color:#0f172a;">' + escapeHtml(row.title) + '</h4><p style="margin:8px 0 0;font-size:' + (large ? 13 : 12) + 'px;line-height:1.8;color:#64748b;">' + escapeHtml(row.summary) + '</p></div></article>';
            }).join('') + '</div>'
        );
    }

    function renderServiceMaterialPreview(preset, large) {
        if (preset.type === 'guestbook_form') {
            return renderMaterialPreviewFrame(
                preset,
                '真实咨询表单版式',
                '咨询',
                '<div style="display:grid;grid-template-columns:' + (large ? '1fr 1fr' : '1fr') + ';gap:14px;"><div style="padding:' + (large ? 20 : 14) + 'px;border-radius:16px;background:#f8fbff;border:1px solid #e7edf7;"><h4 style="margin:0;font-size:' + (large ? 22 : 16) + 'px;">' + escapeHtml((preset.options.config && preset.options.config.title) || '在线咨询') + '</h4><p style="margin:10px 0 0;font-size:13px;line-height:1.8;color:#64748b;">' + escapeHtml((preset.options.config && preset.options.config.subtitle) || '填写需求后，我们会尽快联系您。') + '</p></div><form style="display:grid;gap:10px;padding:' + (large ? 20 : 14) + 'px;border-radius:16px;background:#fff;border:1px solid #e7edf7;"><input value="张先生" style="padding:10px 12px;border:1px solid #dbe3f4;border-radius:10px;"><input value="138****1234" style="padding:10px 12px;border:1px solid #dbe3f4;border-radius:10px;"><textarea style="min-height:' + (large ? 100 : 72) + 'px;padding:10px 12px;border:1px solid #dbe3f4;border-radius:10px;">想了解板材定制与报价流程</textarea><button type="button" style="padding:12px 16px;border:0;border-radius:10px;background:#111827;color:#fff;cursor:pointer;">' + escapeHtml((preset.options.config && preset.options.config.button_text) || '立即咨询') + '</button></form></div>'
            );
        }
        return renderMaterialPreviewFrame(
            preset,
            '真实联系方式展示',
            '咨询',
            '<div style="display:grid;grid-template-columns:repeat(' + (large ? 3 : 1) + ',minmax(0,1fr));gap:12px;"><div style="padding:' + (large ? 18 : 14) + 'px;border:1px solid #e7edf7;border-radius:16px;background:#fff;"><div style="font-size:12px;color:#94a3b8;">电话咨询</div><div style="margin-top:8px;font-size:' + (large ? 18 : 14) + 'px;font-weight:700;color:#0f172a;">400-888-1234</div></div><div style="padding:' + (large ? 18 : 14) + 'px;border:1px solid #e7edf7;border-radius:16px;background:#fff;"><div style="font-size:12px;color:#94a3b8;">微信顾问</div><div style="margin-top:8px;font-size:' + (large ? 18 : 14) + 'px;font-weight:700;color:#0f172a;">wood-service</div></div><div style="padding:' + (large ? 18 : 14) + 'px;border:1px solid #e7edf7;border-radius:16px;background:#fff;"><div style="font-size:12px;color:#94a3b8;">商务邮箱</div><div style="margin-top:8px;font-size:' + (large ? 18 : 14) + 'px;font-weight:700;color:#0f172a;">service@example.com</div></div></div>'
        );
    }

    function renderLinksMaterialPreview(preset, large) {
        var brands = ['居然之家', '红星美凯龙', '阿里云', '华为云', '京东工业', '三棵树'];
        return renderMaterialPreviewFrame(
            preset,
            '真实品牌名与合作墙布局',
            '链接',
            '<div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;">' + brands.map(function (name, index) {
                var bg = index % 2 === 0 ? '#f8fbff' : '#eef4ff';
                return '<div style="height:' + (large ? 84 : 64) + 'px;border-radius:14px;background:' + bg + ';display:flex;align-items:center;justify-content:center;border:1px solid #e7edf7;font-size:' + (large ? 18 : 13) + 'px;font-weight:700;color:#475569;">' + escapeHtml(name) + '</div>';
            }).join('') + '</div>'
        );
    }

    function renderVideoMaterialPreview(preset, large) {
        return renderMaterialPreviewFrame(
            preset,
            '真实视频封面与标题文案',
            '视频',
            '<div style="display:grid;grid-template-columns:' + (large ? '1.1fr .9fr' : '1fr') + ';gap:16px;align-items:center;"><div style="position:relative;"><img src="' + materialMockImage('品牌宣传片', '空间实景 · 工艺展示 · 客户案例', '#0f172a', '#2563eb', true) + '" alt="视频封面" style="display:block;width:100%;height:' + (large ? 280 : 180) + 'px;object-fit:cover;border-radius:18px;"><span class="cms-builder-material-play"></span></div>' + (large ? '<div><h4 style="margin:0;font-size:24px;color:#0f172a;">品牌宣传片：从原材到交付的全流程展示</h4><p style="margin:12px 0 0;font-size:14px;line-height:1.9;color:#64748b;">适合企业介绍、工厂实拍、项目案例和服务讲解。插入后可以继续替换真实封面与视频地址。</p></div>' : '') + '</div>'
        );
    }

    function renderMaterialPreviewContent(preset, large) {
        if (!preset) {
            return '';
        }
        if (preset.type === 'html') {
            return renderMaterialHtmlSnapshot(preset, large);
        }
        if (preset.previewKind === 'product') {
            return renderProductMaterialPreview(preset, large);
        }
        if (preset.previewKind === 'article') {
            return renderArticleMaterialPreview(preset, large);
        }
        if (preset.previewKind === 'service') {
            return renderServiceMaterialPreview(preset, large);
        }
        if (preset.previewKind === 'links') {
            return renderLinksMaterialPreview(preset, large);
        }
        if (preset.previewKind === 'video') {
            return renderVideoMaterialPreview(preset, large);
        }
        return renderMaterialPreviewFrame(
            preset,
            '真实示意图与文案预览',
            preset.categoryLabel || '素材',
            '<img src="' + materialMockImage(preset.title, preset.desc, '#dbeafe', '#eef4ff', false) + '" alt="' + escapeHtml(preset.title) + '" style="display:block;width:100%;height:' + (large ? 360 : 190) + 'px;object-fit:cover;border-radius:16px;">'
        );
    }

    function filteredMaterialPresets() {
        var search = (state.materialSearch || '').trim().toLowerCase();
        return materialPresets().filter(function (preset) {
            if (state.materialScope === 'mine' && preset.scope !== 'mine') {
                return false;
            }
            if (state.materialCategory && state.materialCategory !== preset.category) {
                return false;
            }
            if (!search) {
                return true;
            }
            var haystack = [preset.title, preset.desc, preset.categoryLabel].join(' ').toLowerCase();
            return haystack.indexOf(search) !== -1;
        });
    }

    function findMaterialPreset(key) {
        var found = null;
        materialPresets().some(function (preset) {
            if (preset.key === key) {
                found = preset;
                return true;
            }
            return false;
        });
        return found;
    }

    function renderMaterialCardPreviewCompact(preset) {
        if (!preset) {
            return '<div class="cms-builder-material-preview-compact"></div>';
        }
        if (preset.type === 'html') {
            var compactHtml = preset.options && typeof preset.options.html === 'string' ? preset.options.html : '';
            return '<div class="cms-builder-material-preview-compact scaled">' +
                '<div style="width:1120px;transform-origin:top left;transform:scale(0.16);">' + compactHtml + '</div>' +
            '</div>';
        }
        if (preset.previewKind === 'product') {
            return '<div class="cms-builder-material-preview-compact"><div class="cms-builder-material-preview-compact-grid"><img src="' + materialMockImage('生态板系列', '环保饰面 · 快速交付', '#c7d2fe', '#e0f2fe', false) + '" alt="' + escapeHtml(preset.title) + '" class="cms-builder-material-preview-compact-thumb"><div class="cms-builder-material-preview-compact-stack"><div class="cms-builder-material-preview-compact-title">生态板系列</div><div class="cms-builder-material-preview-compact-text">高密度基材，适合柜体、门墙与整装空间。</div><div class="cms-builder-material-preview-compact-pills"><span class="cms-builder-material-preview-compact-pill">￥168/张</span><span class="cms-builder-material-preview-compact-pill">热销</span></div></div></div></div>';
        }
        if (preset.previewKind === 'article') {
            return '<div class="cms-builder-material-preview-compact"><div class="cms-builder-material-preview-compact-grid"><img src="' + materialMockImage('行业资讯', '木作趋势 / 项目案例', '#bae6fd', '#c7d2fe', false) + '" alt="' + escapeHtml(preset.title) + '" class="cms-builder-material-preview-compact-thumb"><div class="cms-builder-material-preview-compact-stack"><div class="cms-builder-material-preview-compact-title">高端木作一体化趋势解析</div><div class="cms-builder-material-preview-compact-text">真实标题和摘要排版，适合新闻中心与资讯列表。</div><div class="cms-builder-material-preview-compact-lines"><span class="cms-builder-material-preview-compact-line lg"></span><span class="cms-builder-material-preview-compact-line md"></span></div></div></div></div>';
        }
        if (preset.previewKind === 'service') {
            return '<div class="cms-builder-material-preview-compact"><div class="cms-builder-material-preview-compact-boxes"><div class="cms-builder-material-preview-compact-box">电话咨询</div><div class="cms-builder-material-preview-compact-box">微信顾问</div><div class="cms-builder-material-preview-compact-box">在线表单</div></div></div>';
        }
        if (preset.previewKind === 'links') {
            return '<div class="cms-builder-material-preview-compact"><div class="cms-builder-material-preview-compact-boxes"><div class="cms-builder-material-preview-compact-box">居然之家</div><div class="cms-builder-material-preview-compact-box">红星美凯龙</div><div class="cms-builder-material-preview-compact-box">阿里云</div></div></div>';
        }
        if (preset.previewKind === 'video') {
            return '<div class="cms-builder-material-preview-compact"><div class="cms-builder-material-preview-compact-grid"><img src="' + materialMockImage('品牌宣传片', '空间实景 · 客户案例', '#0f172a', '#2563eb', true) + '" alt="' + escapeHtml(preset.title) + '" class="cms-builder-material-preview-compact-thumb"><div class="cms-builder-material-preview-compact-stack"><div class="cms-builder-material-preview-compact-title">宣传片 / 案例视频</div><div class="cms-builder-material-preview-compact-text">卡片内仅保留缩略封面，完整预览在弹层里打开。</div><div class="cms-builder-material-preview-compact-pills"><span class="cms-builder-material-preview-compact-pill">视频展示</span></div></div></div></div>';
        }
        return '<div class="cms-builder-material-preview-compact"><img src="' + materialMockImage(preset.title, preset.categoryLabel || '素材预览', '#dbeafe', '#eef4ff', false) + '" alt="' + escapeHtml(preset.title) + '" class="cms-builder-material-preview-compact-thumb"></div>';
    }

    function renderMaterialCard(preset) {
        return '<article class="cms-builder-material-card">' +
            '<div class="cms-builder-material-card-head"><div><div class="cms-builder-material-card-title">' + escapeHtml(preset.title) + '</div></div><span class="cms-builder-material-chip">' + escapeHtml(preset.categoryLabel) + '</span></div>' +
            '<div class="cms-builder-material-preview"><div class="cms-builder-material-preview-canvas">' + renderMaterialCardPreviewCompact(preset) + '</div></div>' +
            '<div class="cms-builder-material-actions"><button type="button" class="cms-builder-btn cms-builder-btn-light" data-material-preview="' + escapeHtml(preset.key) + '">预览素材</button><button type="button" class="cms-builder-btn cms-builder-btn-primary" data-material-apply="' + escapeHtml(preset.key) + '">使用素材</button></div>' +
        '</article>';
    }

    function renderMaterialPreviewModal() {
        var preset = state.materialPreviewKey ? findMaterialPreset(state.materialPreviewKey) : null;
        if (!preset) {
            return '';
        }
        return '<div class="cms-builder-material-modal" id="cms_builder_material_modal">' +
            '<div class="cms-builder-material-modal-card cms-builder-no-copy' + (state.materialModalPosition ? ' is-detached' : '') + '"' + modalCardStyle('material-preview') + '>' +
                '<div class="cms-builder-material-modal-head" data-modal-handle="material-preview"><div><div class="cms-builder-material-modal-title">' + escapeHtml(preset.title) + '</div></div><button type="button" class="cms-builder-panel-tool" data-material-preview-close="1">关闭</button></div>' +
                '<div class="cms-builder-material-modal-body"><div class="cms-builder-material-modal-preview">' + renderMaterialPreviewContent(preset, true) + '</div></div>' +
                '<div class="cms-builder-material-modal-actions"><button type="button" class="cms-builder-btn cms-builder-btn-light" data-material-preview-close="1">关闭预览</button><button type="button" class="cms-builder-btn cms-builder-btn-primary" data-material-apply="' + escapeHtml(preset.key) + '">使用这套素材</button></div>' +
            '</div>' +
        '</div>';
    }

    function renderBusinessEditorModal() {
        var editor = state.businessEditor;
        if (!editor) {
            return '';
        }
        var typeLabel = businessTypeLabel(editor.type);
        var fields = '<div class="cms-builder-fields">' +
            '<div class="cms-builder-field"><label>' + typeLabel + '标题</label><input type="text" data-business-edit-field="title" value="' + escapeHtml(editor.title || '') + '"></div>' +
            '<div class="cms-builder-field"><label>' + typeLabel + '简介</label><textarea data-business-edit-field="summary">' + escapeHtml(editor.summary || '') + '</textarea></div>';
        if (editor.type === 'product') {
            fields += '<div class="cms-builder-field"><label>产品价格</label><input type="text" data-business-edit-field="price" value="' + escapeHtml(editor.price || '') + '"></div>' +
                '<div class="cms-builder-field"><label>产品图片</label><input type="text" data-business-edit-field="litpic" value="' + escapeHtml(editor.litpic || '') + '"></div>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_business_upload_image">上传产品图片</button>';
        }
        fields += '</div>';
        return '<div class="cms-builder-material-modal" id="cms_builder_business_modal">' +
            '<div class="cms-builder-material-modal-card cms-builder-business-modal-card cms-builder-no-copy' + (state.businessModalPosition ? ' is-detached' : '') + '"' + modalCardStyle('business-editor') + '>' +
                '<div class="cms-builder-material-modal-head" data-modal-handle="business-editor"><div><div class="cms-builder-material-modal-title">编辑' + escapeHtml(typeLabel) + '数据</div></div><button type="button" class="cms-builder-panel-tool" id="cms_builder_close_business_editor">关闭</button></div>' +
                '<div class="cms-builder-material-modal-body">' +
                    '<div class="cms-builder-business-editor-note">当前弹窗直接修改后台' + escapeHtml(typeLabel) + '数据表。保存成功后，当前页面中的业务模块会立即刷新。</div>' +
                    '<div class="cms-builder-business-editor-meta">' +
                        '<div class="cms-builder-business-editor-chip"><strong>数据类型</strong>' + escapeHtml(typeLabel) + '</div>' +
                        '<div class="cms-builder-business-editor-chip"><strong>数据编号</strong>#' + escapeHtml(editor.id || '') + '</div>' +
                    '</div>' +
                    '<div class="cms-builder-material-modal-preview">' + fields + '</div>' +
                '</div>' +
                '<div class="cms-builder-material-modal-actions"><button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_close_business_editor_footer">取消</button><button type="button" class="cms-builder-btn cms-builder-btn-primary" id="cms_builder_save_business_editor">保存同步</button></div>' +
            '</div>' +
        '</div>';
    }

    function blockActionIcon(name) {
        var icons = {
            star: '<svg viewBox="0 0 24 24"><path d="M12 4.5l2.3 4.7 5.2.8-3.8 3.7.9 5.3-4.6-2.4-4.6 2.4.9-5.3-3.8-3.7 5.2-.8L12 4.5z"></path></svg>',
            link: '<svg viewBox="0 0 24 24"><path d="M10 13a5 5 0 0 1 0-7l1.6-1.6a5 5 0 0 1 7 7L17 13"></path><path d="M14 11a5 5 0 0 1 0 7l-1.6 1.6a5 5 0 0 1-7-7L7 11"></path></svg>',
            eye: '<svg viewBox="0 0 24 24"><path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6S2 12 2 12z"></path><circle cx="12" cy="12" r="2.5"></circle></svg>'
        };
        return icons[name] || '';
    }

    function currentOverlayPath() {
        if (isFreeLayoutMode()) {
            return '';
        }
        if (state.selectedCanvasBlockId) {
            return '';
        }
        if (state.draggingPath || state.draggingNewExtraType || state.mouseDragMeta) {
            return '';
        }
        if (state.materialPreviewKey || state.businessEditor) {
            return '';
        }
        if (state.resizingMeta && state.resizingMeta.path) {
            return state.resizingMeta.path;
        }
        if (state.selectedPath) {
            return state.selectedPath;
        }
        if (state.hoverToolbarActive && state.hoveredPath) {
            return state.hoveredPath;
        }
        return state.hoveredPath || '';
    }

    function currentOverlayNode() {
        var path = currentOverlayPath();
        var node = path ? queryByPath(path) : null;
        if (!node) {
            return null;
        }
        if (listHasRef(currentHiddenPaths(), nodeRef(node)) || isProtectedEditableNode(node)) {
            return null;
        }
        return node;
    }

    function isStructuralWrapperNode(node) {
        if (!node || !(node instanceof Element)) {
            return false;
        }
        if (node.matches('header,footer,main,.topBar,.w1200,.wrap,body')) {
            return true;
        }
        var identity = [
            node.id || '',
            typeof node.className === 'string' ? node.className : '',
            node.getAttribute('data-builder-block-path') || ''
        ].join(' ').toLowerCase();
        return /(^|[\s_-])(wrap|container|layout|main|page)([\s_-]|$)/.test(identity);
    }

    function isBuilderUnsafeRootNode(node) {
        if (!node || !(node instanceof Element)) {
            return false;
        }
        if (node.matches('html,body,header,main,footer')) {
            return true;
        }
        if (node.id && /^(cms_builder_design_canvas|cms_builder_extra_zone|cms_frontend_builder_root|cms_builder_overlay_layer|cms_builder_page_widgets)$/i.test(node.id)) {
            return true;
        }
        var path = getDomPath(node).toLowerCase();
        if (path === '') {
            return false;
        }
        if (/^(header|main|footer)(?:$|\s*>)/.test(path)) {
            return true;
        }
        if (/^div#cms_builder_design_canvas(?:$|\s*>)/.test(path) || /^div#cms_builder_extra_zone(?:$|\s*>)/.test(path)) {
            return true;
        }
        if (/^main:nth-of-type\(1\)\s*>\s*div:nth-of-type\(\d+\)(?:$|\s*>)/.test(path)) {
            return true;
        }
        return false;
    }

    function currentResizeNode() {
        if (isFreeLayoutMode()) {
            return null;
        }
        if (state.selectedCanvasBlockId) {
            return null;
        }
        if (state.materialPreviewKey || state.businessEditor) {
            return null;
        }
        var node = currentBlockNode();
        if (!node) {
            return null;
        }
        if (listHasRef(currentHiddenPaths(), nodeRef(node))) {
            return null;
        }
        var rect = node.getBoundingClientRect();
        if (!rect || rect.width < 4 || rect.height < 4) {
            return null;
        }
        if (isStructuralWrapperNode(node) && rect.width >= window.innerWidth * 0.78) {
            return null;
        }
        if (rect.width >= window.innerWidth * 0.94 && rect.height >= window.innerHeight * 0.68) {
            return null;
        }
        return node;
    }

    function renderHoverToolbar() {
        var node = currentOverlayNode();
        if (!node) {
            return '';
        }
        var path = getDomPath(node);
        var rect = node.getBoundingClientRect();
        if (!rect || rect.width < 4 || rect.height < 4) {
            return '';
        }
        var toolbarWidth = 208;
        var left = clamp(rect.left, 12, Math.max(12, window.innerWidth - toolbarWidth - 12));
        var top = clamp(rect.top + 8, 70, Math.max(70, window.innerHeight - 52));
        var locked = listHasRef((state.page.frontend_locked_paths || []), nodeRef(node));
        var isProtected = isProtectedBlockNode(node);
        var primaryAction = blockPrimaryActionMeta(node);
        return '<div class="cms-builder-hover-toolbar" data-hover-toolbar="1" style="left:' + escapeHtml(left) + 'px;top:' + escapeHtml(top) + 'px;">' +
            '<button type="button" class="cms-builder-hover-edit" data-block-action="edit" data-block-path="' + escapeHtml(path) + '" data-block-edit-kind="' + escapeHtml(primaryAction.kind) + '">' + escapeHtml(primaryAction.label) + '</button>' +
            '<span class="cms-builder-hover-sep"></span>' +
            '<button type="button" class="cms-builder-hover-icon' + (locked ? ' is-active' : '') + '" data-block-action="lock" data-block-path="' + escapeHtml(path) + '" title="' + escapeHtml(locked ? '解除锁定' : '锁定区块') + '">' + blockActionIcon('star') + '</button>' +
            '<button type="button" class="cms-builder-hover-icon" data-block-action="locate" data-block-path="' + escapeHtml(path) + '" title="定位区块">' + blockActionIcon('link') + '</button>' +
            '<button type="button" class="cms-builder-hover-icon" data-block-action="delete" data-block-path="' + escapeHtml(path) + '" title="' + escapeHtml(isProtected ? protectedBlockMessage() : '删除区块') + '"' + (isProtected ? ' disabled' : '') + '>' + libraryIcon('trash') + '</button>' +
        '</div>';
    }

    function renderResizeOverlay() {
        var node = currentResizeNode();
        if (!node) {
            return '';
        }
        if (isFreeLayoutMode()) {
            return '';
        }
        var path = getDomPath(node);
        var rect = node.getBoundingClientRect();
        if (!rect || rect.width < 4 || rect.height < 4) {
            return '';
        }
        var locked = listHasRef((state.page.frontend_locked_paths || []), nodeRef(node));
        return '<div class="cms-builder-resize-frame' + (path === state.selectedPath ? ' is-selected' : '') + '" data-resize-frame="1" style="left:' + escapeHtml(rect.left) + 'px;top:' + escapeHtml(rect.top) + 'px;width:' + escapeHtml(Math.max(rect.width, 12)) + 'px;height:' + escapeHtml(Math.max(rect.height, 12)) + 'px;">' +
            (locked ? '' : '<button type="button" class="cms-builder-move-handle" data-transform-handle="move" data-block-path="' + escapeHtml(path) + '" aria-label="移动模块">移动</button><button type="button" class="cms-builder-resize-handle scale" data-transform-handle="scale" data-block-path="' + escapeHtml(path) + '" aria-label="缩放模块"></button><button type="button" class="cms-builder-resize-handle east" data-transform-handle="e" data-block-path="' + escapeHtml(path) + '" aria-label="调整宽度"></button><button type="button" class="cms-builder-resize-handle south" data-transform-handle="s" data-block-path="' + escapeHtml(path) + '" aria-label="调整高度"></button><button type="button" class="cms-builder-resize-handle corner" data-transform-handle="se" data-block-path="' + escapeHtml(path) + '" aria-label="调整尺寸"></button>') +
        '</div>';
    }

    function renderFloatingOverlays() {
        var layer = overlayLayerNode();
        if (!layer) {
            return;
        }
        layer.innerHTML = renderHoverToolbar() + renderResizeOverlay() + renderMaterialPreviewModal() + renderBusinessEditorModal() + renderSettingsWidgetPreviewModal();
    }

    function canvasBlocks() {
        if (!Array.isArray(state.page.frontend_canvas_blocks)) {
            state.page.frontend_canvas_blocks = [];
        }
        return state.page.frontend_canvas_blocks;
    }

    function nextCanvasZIndex() {
        var maxZ = 0;
        canvasBlocks().forEach(function (block) {
            maxZ = Math.max(maxZ, parseInt(block.z || 0, 10) || 0);
        });
        return maxZ + 1;
    }

    function normalizeCanvasBlock(block) {
        block = block && typeof block === 'object' ? block : {};
        var type = block.type || 'text';
        return {
            id: block.id || ('canvas_' + Math.random().toString(16).slice(2, 10)),
            type: type,
            label: block.label || canvasTypeLabel(type),
            x: Math.max(0, parseInt(block.x, 10) || 60),
            y: Math.max(0, parseInt(block.y, 10) || 60),
            w: Math.max(48, parseInt(block.w, 10) || 320),
            h: Math.max(32, parseInt(block.h, 10) || 160),
            z: Math.max(1, parseInt(block.z, 10) || nextCanvasZIndex()),
            hidden: block.hidden ? 1 : 0,
            locked: block.locked ? 1 : 0,
            content: block.content && typeof block.content === 'object' ? block.content : {},
            style: block.style && typeof block.style === 'object' ? block.style : {}
        };
    }

    function normalizeCanvasBlocks() {
        state.page.frontend_canvas_blocks = canvasBlocks().map(normalizeCanvasBlock);
    }

    function canvasTypeLabel(type) {
        var map = {
            text: '文本模块',
            image: '图片模块',
            button: '按钮模块',
            html: '自由 HTML',
            material: '素材模块'
        };
        return map[type] || '自由模块';
    }

    function defaultCanvasBlock(type, options) {
        options = options && typeof options === 'object' ? options : {};
        type = type || 'text';
        var count = canvasBlocks().length;
        var block = normalizeCanvasBlock({
            id: 'canvas_' + Date.now().toString(36) + '_' + Math.random().toString(16).slice(2, 6),
            type: type,
            label: options.label || canvasTypeLabel(type),
            x: 70 + (count % 4) * 28,
            y: 70 + (count % 5) * 34,
            w: type === 'button' ? 220 : (type === 'image' ? 420 : 380),
            h: type === 'button' ? 56 : (type === 'text' ? 170 : 240),
            z: nextCanvasZIndex(),
            content: {},
            style: {}
        });
        if (type === 'text') {
            block.content = {title: options.title || '双击/右侧编辑标题', text: options.text || '这里是自由拖拽文本模块，可调整位置、尺寸、背景和文字。'};
            block.style = {background: '#ffffff', color: '#0f172a', radius: 18, padding: 20, opacity: 100, border_width: 0, border_style: 'solid', border_color: '#dbe3f4', shadow: ''};
        } else if (type === 'image') {
            block.content = {src: options.src || 'https://via.placeholder.com/900x520?text=Image', alt: options.alt || '图片'};
            block.style = {background: '#f8fafc', radius: 18, fit: 'cover', opacity: 100, border_width: 0, border_style: 'solid', border_color: '#dbe3f4', shadow: ''};
        } else if (type === 'button') {
            block.content = {text: options.text || '立即咨询', href: options.href || 'javascript:void(0)'};
            block.style = {background: '#111827', color: '#ffffff', radius: 999, padding: 0, opacity: 100, border_width: 0, border_style: 'solid', border_color: '#111827', shadow: ''};
        } else {
            block.type = 'html';
            block.content = {html: options.html || '<div style="padding:24px;border-radius:18px;background:#fff;border:1px solid #e7edf7;"><h3 style="margin:0;font-size:24px;color:#0f172a;">自由模块</h3><p style="margin:12px 0 0;color:#64748b;line-height:1.8;">这个模块使用独立画布定位，可自由拖拽和缩放。</p></div>'};
            block.style = {background: 'transparent', color: '#0f172a', radius: 18, padding: 0, opacity: 100, border_width: 0, border_style: 'solid', border_color: '#dbe3f4', shadow: ''};
        }
        return block;
    }

    function htmlToCanvasContent(node) {
        if (!node) {
            return '';
        }
        var clone = node.cloneNode(true);
        qsa('[data-builder-block-path],[data-builder-extra-id],[data-builder-key],[draggable]', clone).forEach(function (item) {
            item.removeAttribute('data-builder-block-path');
            item.removeAttribute('data-builder-extra-id');
            item.removeAttribute('data-builder-extra-type');
            item.removeAttribute('data-builder-extra-config');
            item.removeAttribute('data-builder-key');
            item.removeAttribute('draggable');
        });
        return clone.outerHTML || clone.innerHTML || '';
    }

    function convertNodeToCanvasBlock(node) {
        if (!node || !(node instanceof Element)) {
            return;
        }
        if (isProtectedEditableNode(node)) {
            setStatus(blockProtectedEditMessage());
            return;
        }
        var path = getDomPath(node);
        if (!path) {
            return;
        }
        var rect = node.getBoundingClientRect();
        var canvas = getCanvasLayer();
        var canvasRect = canvas.getBoundingClientRect();
        pushHistory();
        var block = defaultCanvasBlock('html', {
            label: (node.tagName ? node.tagName.toLowerCase() : 'block') + ' 模块',
            html: htmlToCanvasContent(node)
        });
        block.x = Math.max(0, Math.round(rect.left - canvasRect.left));
        block.y = Math.max(0, Math.round(rect.top - canvasRect.top));
        block.w = Math.max(120, Math.round(rect.width || 320));
        block.h = Math.max(80, Math.round(rect.height || 180));
        block.z = nextCanvasZIndex();
        canvasBlocks().push(block);
        addPathRef('frontend_hidden_paths', nodeRef(node));
        addPathRef('frontend_free_layout_hidden_paths', nodeRef(node));
        state.page.free_layout_mode = 1;
        syncEditorStateToPage();
        applyPageState();
        state.selectedNode = null;
        state.selectedPath = '';
        state.hoveredPath = '';
        state.hoverToolbarActive = false;
        selectCanvasBlock(block.id, false);
        queueSaveDraft();
        refreshModeStatus();
        setStatus('当前模块已转为自由模块，可直接拖拽、缩放，并在点“编辑”后修改属性。');
    }

    async function convertCurrentPageToFreeLayout() {
        collectBlocks();
        var candidates = [];
        state.blocks.forEach(function (item) {
            var node = queryByPath(item.path);
            if (!node || isProtectedEditableNode(node) || listHasRef(currentHiddenPaths(), nodeRef(node))) {
                return;
            }
            candidates.push(node);
        });
        if (!candidates.length) {
            setStatus('当前页面没有可转换的模板区块。');
            return;
        }
        if (!await builderConfirm({title:'转换为自由布局',message:'将把当前页面已识别区块批量转为自由模块，并隐藏原模板区块。',confirmText:'确认转换'})) {
            return;
        }
        pushHistory();
        candidates.forEach(function (node) {
            var path = getDomPath(node);
            if (!path) {
                return;
            }
            var rect = node.getBoundingClientRect();
            var canvas = getCanvasLayer();
            var canvasRect = canvas.getBoundingClientRect();
            var block = defaultCanvasBlock('html', {
                label: (node.tagName ? node.tagName.toLowerCase() : 'block') + ' 模块',
                html: htmlToCanvasContent(node)
            });
            block.x = Math.max(0, Math.round(rect.left - canvasRect.left));
            block.y = Math.max(0, Math.round(rect.top - canvasRect.top));
            block.w = Math.max(120, Math.round(rect.width || 320));
            block.h = Math.max(80, Math.round(rect.height || 180));
            block.z = nextCanvasZIndex();
            canvasBlocks().push(block);
            addPathRef('frontend_hidden_paths', nodeRef(node));
            addPathRef('frontend_free_layout_hidden_paths', nodeRef(node));
        });
        state.page.free_layout_mode = 1;
        syncEditorStateToPage();
        state.selectedNode = null;
        state.selectedPath = '';
        state.hoveredPath = '';
        state.hoverToolbarActive = false;
        applyPageState();
        var latest = canvasBlocks();
        if (latest.length) {
            selectCanvasBlock(latest[latest.length - 1].id, false);
        }
        queueSaveDraft();
        refreshModeStatus();
    }

    function isFreeLayoutMode() {
        return !!(state.page && state.page.free_layout_mode && canvasBlocks().length);
    }

    async function restoreTemplateLayoutModeInternal(options) {
        options = options && typeof options === 'object' ? options : {};
        var hiddenRefs = Array.isArray(state.page.frontend_free_layout_hidden_paths)
            ? state.page.frontend_free_layout_hidden_paths.slice()
            : [];
        if (!hiddenRefs.length && !canvasBlocks().length) {
            state.page.free_layout_mode = 0;
            syncEditorStateToPage();
            if (!options.silentIfEmpty) {
                queueSaveDraft();
                setStatus('当前页面已经是模板模式。');
            }
            return false;
        }
        if (!options.skipConfirm && !await builderConfirm({title:'恢复模板模式',message:options.confirmMessage || '将退出自由布局主模式，并恢复之前隐藏的模板区块。',confirmText:'确认恢复'})) {
            return false;
        }
        if (!options.skipHistory) {
            pushHistory();
        }
        state.page.free_layout_mode = 0;
        if (options.clearCanvas) {
            state.page.frontend_canvas_blocks = [];
        }
        if (hiddenRefs.length) {
            state.page.frontend_hidden_paths = (state.page.frontend_hidden_paths || []).filter(function (item) {
                return !listHasRef(hiddenRefs, normalizeStoredRef(item));
            });
        }
        state.page.frontend_free_layout_hidden_paths = [];
        state.selectedCanvasBlockId = '';
        state.selectedNode = null;
        state.selectedPath = '';
        state.hoveredPath = '';
        state.hoverToolbarActive = false;
        applyPageState();
        syncEditorStateToPage();
        queueSaveDraft();
        refreshModeStatus();
        setStatus(options.clearCanvas ? '已恢复模板模式，并清空当前自由模块。' : '已恢复模板模式，原模板区块重新显示。');
        return true;
    }

    function restoreTemplateLayoutMode() {
        restoreTemplateLayoutModeInternal();
    }

    function restoreTemplateAndClearCanvasBlocks() {
        if (!canvasBlocks().length && !isFreeLayoutMode()) {
            setStatus('当前没有需要清空的自由模块。');
            return;
        }
        restoreTemplateLayoutModeInternal({
            clearCanvas: true,
            confirmMessage: '将恢复模板模式，并清空当前自由模块。此操作会删除当前画布块，但原模板区块会恢复显示。继续吗？'
        });
    }

    async function restoreOriginalTemplateLayout() {
        if (!await builderConfirm({title:'恢复原始状态',message:'将清空当前页面所有前台装修修改并恢复到模板原始状态，已发布与未发布的装修数据都会被覆盖。',confirmText:'确认恢复',tone:'danger'})) {
            return;
        }
        pushHistory();
        state.page.frontend_overrides = {};
        state.page.frontend_mobile_overrides = {};
        state.page.frontend_extra_sections = [];
        state.page.frontend_canvas_blocks = [];
        state.page.frontend_hidden_paths = [];
        state.page.frontend_free_layout_hidden_paths = [];
        state.page.frontend_deleted_paths = [];
        state.page.frontend_locked_paths = [];
        state.page.frontend_block_orders = [];
        state.page.frontend_mobile_hidden_paths = [];
        state.page.frontend_mobile_deleted_paths = [];
        state.page.free_layout_mode = 0;
        state.page.render_mode = 'frontend_template';
        state.page.frontend_editor_state = normalizeFrontendEditorState({});
        state.sections = Array.isArray(state.sections) ? state.sections : [];
        state.selectedNode = null;
        state.selectedPath = '';
        state.selectedCanvasBlockId = '';
        state.selectedCanvasBlockIds = [];
        state.hoveredPath = '';
        state.hoverToolbarActive = false;
        state.stylePanelPart = 'layout';
        disableFreeCanvasState();
        loadEditorStateFromPage();
        applyPageState();
        syncEditorStateToPage();
        commitMutation('');
        clearTimeout(state.saveTimer);
        state.saveTimer = null;
        setStatus('正在恢复原始模板...');
        saveDraftNow().then(function (result) {
            if(!(result&&result.code===200)){throw new Error('save_failed');}
            setStatus('已恢复到原始模板。');
            state.allowExit=true;
            window.setTimeout(function () {
                window.location.reload();
            }, 120);
        }).catch(function () {
            setStatus('原始模板已恢复，但自动保存失败。');
        });
    }

    function findCanvasBlock(id) {
        var found = null;
        canvasBlocks().forEach(function (block) {
            if (String(block.id) === String(id)) {
                found = block;
            }
        });
        return found;
    }

    function updateCanvasBlock(id, patch, liveOnly) {
        state.page.frontend_canvas_blocks = canvasBlocks().map(function (block) {
            if (String(block.id) !== String(id)) {
                return block;
            }
            return Object.assign({}, block, patch || {});
        });
        renderCanvasLayer();
        renderNodeList();
        if (!liveOnly) {
            renderSettings();
        }
        if (liveOnly) {
            queueSaveDraft();
        }
    }

    function updateCanvasBlockNested(id, group, key, value, liveOnly) {
        var block = findCanvasBlock(id);
        if (!block) {
            return;
        }
        var next = Object.assign({}, block[group] || {});
        next[key] = value;
        updateCanvasBlock(id, group === 'content' ? {content: next} : {style: next}, liveOnly);
    }

    function selectedCanvasIds() {
        var ids = Array.isArray(state.selectedCanvasBlockIds) ? state.selectedCanvasBlockIds.filter(Boolean) : [];
        if (state.selectedCanvasBlockId && ids.indexOf(String(state.selectedCanvasBlockId)) === -1) {
            ids.unshift(String(state.selectedCanvasBlockId));
        }
        return ids;
    }

    function isCanvasBlockSelected(id) {
        return selectedCanvasIds().indexOf(String(id)) !== -1;
    }

    function setCanvasSelection(ids, primaryId, openSettings) {
        ids = Array.isArray(ids) ? ids.map(function (item) { return String(item); }).filter(Boolean) : [];
        primaryId = primaryId ? String(primaryId) : (ids[0] || '');
        if (primaryId && ids.indexOf(primaryId) === -1) {
            ids.unshift(primaryId);
        }
        state.selectedCanvasBlockIds = ids;
        state.selectedCanvasBlockId = primaryId;
        state.selectedNode = null;
        state.selectedPath = '';
        state.hoveredPath = '';
        state.hoverToolbarActive = false;
        renderCanvasLayer();
        renderLibraryPanel();
        renderNodeList();
        renderSettings();
        if (openSettings) {
            showSettingsPanel(null);
        } else {
            hideSettingsPanel();
        }
    }

    function toggleCanvasBlockSelection(id, openSettings) {
        id = String(id || '');
        var ids = selectedCanvasIds().slice();
        var index = ids.indexOf(id);
        if (index === -1) {
            ids.push(id);
        } else {
            ids.splice(index, 1);
        }
        setCanvasSelection(ids, ids.length ? ids[ids.length - 1] : '', openSettings);
    }

    function selectCanvasBlock(id, openSettings) {
        var block = findCanvasBlock(id);
        if (!block) {
            return;
        }
        if (typeof openSettings === 'undefined') {
            openSettings = false;
        }
        setCanvasSelection([String(id)], String(id), openSettings);
    }

    function clearCanvasSelection() {
        state.selectedCanvasBlockId = '';
        state.selectedCanvasBlockIds = [];
        renderCanvasLayer();
        renderSettings();
    }

    function currentSettingsAnchorNode() {
        if (state.selectedCanvasBlockId) {
            return qs('[data-canvas-block-id="' + String(state.selectedCanvasBlockId).replace(/"/g, '&quot;') + '"]');
        }
        return state.selectedNode || null;
    }

    function canvasStyleText(block) {
        var style = block.style || {};
        var css = [
            'left:' + Math.round(block.x || 0) + 'px',
            'top:' + Math.round(block.y || 0) + 'px',
            'width:' + Math.round(block.w || 0) + 'px',
            'height:' + Math.round(block.h || 0) + 'px',
            'z-index:' + (parseInt(block.z || 1, 10) || 1)
        ];
        if (block.hidden) {
            if (boot.edit_mode) {
                css.push('opacity:.38!important');
            } else {
                css.push('display:none');
            }
        }
        if (style.radius !== '' && style.radius != null) {
            css.push('border-radius:' + Math.max(0, parseInt(style.radius, 10) || 0) + 'px');
        }
        if (style.opacity !== '' && style.opacity != null) {
            css.push('opacity:' + Math.max(1, Math.min(100, parseInt(style.opacity, 10) || 100)) / 100);
        }
        return css.join(';') + ';';
    }

    function canvasInnerStyleText(block) {
        var style = block.style || {};
        var css = [];
        if (style.background) {
            css.push('background:' + style.background);
        }
        if (style.color) {
            css.push('color:' + style.color);
        }
        if (style.padding !== '' && style.padding != null) {
            css.push('padding:' + Math.max(0, parseInt(style.padding, 10) || 0) + 'px');
        }
        if (style.radius !== '' && style.radius != null) {
            css.push('border-radius:' + Math.max(0, parseInt(style.radius, 10) || 0) + 'px');
        }
        if (style.border_width !== '' && style.border_width != null && (parseInt(style.border_width, 10) || 0) > 0) {
            css.push('border:' + Math.max(0, parseInt(style.border_width, 10) || 0) + 'px ' + (style.border_style || 'solid') + ' ' + (style.border_color || '#dbe3f4'));
        }
        if (style.shadow) {
            css.push('box-shadow:' + style.shadow);
        }
        return css.join(';') + ';';
    }

    function renderCanvasBlockContent(block) {
        var content = block.content || {};
        var style = block.style || {};
        if (block.type === 'image') {
            return '<img src="' + escapeHtml(content.src || '') + '" alt="' + escapeHtml(content.alt || '') + '" style="width:100%;height:100%;object-fit:' + escapeHtml(style.fit || 'cover') + ';">';
        }
        if (block.type === 'button') {
            return '<a href="' + escapeHtml(content.href || 'javascript:void(0)') + '" style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;box-sizing:border-box;border-radius:' + escapeHtml(style.radius == null ? 999 : style.radius) + 'px;background:' + escapeHtml(style.background || '#111827') + ';color:' + escapeHtml(style.color || '#fff') + ';font-weight:800;text-decoration:none;">' + escapeHtml(content.text || '按钮') + '</a>';
        }
        if (block.type === 'html') {
            return content.html || '';
        }
        return '<h3 style="margin:0;font-size:24px;line-height:1.35;color:inherit;">' + escapeHtml(content.title || '标题文本') + '</h3><p style="margin:12px 0 0;color:inherit;opacity:.76;line-height:1.8;">' + escapeHtml(content.text || '') + '</p>';
    }

    function renderCanvasBlock(block) {
        block = normalizeCanvasBlock(block);
        var selected = isCanvasBlockSelected(block.id);
        var hiddenPreview = !!(block.hidden && boot.edit_mode);
        var locked = block.locked ? ' 已锁定' : '';
        return '<div class="cms-builder-design-block' + (selected ? ' is-selected' : '') + (hiddenPreview ? ' is-hidden-preview' : '') + '" data-canvas-block-id="' + escapeHtml(block.id) + '" data-canvas-type="' + escapeHtml(block.type) + '" style="' + escapeHtml(canvasStyleText(block)) + '" title="' + escapeHtml(block.label + locked) + '">' +
            '<div class="cms-builder-canvas-toolbar">' +
                '<button type="button" class="cms-builder-canvas-tool" data-canvas-action="edit" data-canvas-id="' + escapeHtml(block.id) + '">编辑</button>' +
                '<button type="button" class="cms-builder-canvas-tool" data-canvas-action="front" data-canvas-id="' + escapeHtml(block.id) + '">上移</button>' +
                '<button type="button" class="cms-builder-canvas-tool" data-canvas-action="duplicate" data-canvas-id="' + escapeHtml(block.id) + '">复制</button>' +
                '<button type="button" class="cms-builder-canvas-tool" data-canvas-action="lock" data-canvas-id="' + escapeHtml(block.id) + '">' + (block.locked ? '解锁' : '锁定') + '</button>' +
                '<button type="button" class="cms-builder-canvas-tool is-danger" data-canvas-action="delete" data-canvas-id="' + escapeHtml(block.id) + '">删除</button>' +
            '</div>' +
            '<div class="cms-builder-design-block-inner" style="' + escapeHtml(canvasInnerStyleText(block)) + '">' + renderCanvasBlockContent(block) + '</div>' +
            '<span class="cms-builder-canvas-handle n" data-canvas-resize="n" data-canvas-id="' + escapeHtml(block.id) + '"></span>' +
            '<span class="cms-builder-canvas-handle e" data-canvas-resize="e" data-canvas-id="' + escapeHtml(block.id) + '"></span>' +
            '<span class="cms-builder-canvas-handle s" data-canvas-resize="s" data-canvas-id="' + escapeHtml(block.id) + '"></span>' +
            '<span class="cms-builder-canvas-handle w" data-canvas-resize="w" data-canvas-id="' + escapeHtml(block.id) + '"></span>' +
            '<span class="cms-builder-canvas-handle ne" data-canvas-resize="ne" data-canvas-id="' + escapeHtml(block.id) + '"></span>' +
            '<span class="cms-builder-canvas-handle nw" data-canvas-resize="nw" data-canvas-id="' + escapeHtml(block.id) + '"></span>' +
            '<span class="cms-builder-canvas-handle se" data-canvas-resize="se" data-canvas-id="' + escapeHtml(block.id) + '"></span>' +
            '<span class="cms-builder-canvas-handle sw" data-canvas-resize="sw" data-canvas-id="' + escapeHtml(block.id) + '"></span>' +
            '<span class="cms-builder-canvas-meta">' + Math.round(block.x) + ',' + Math.round(block.y) + ' / ' + Math.round(block.w) + 'x' + Math.round(block.h) + '</span>' +
        '</div>';
    }

    function canvasHeight() {
        var height = 720;
        canvasBlocks().forEach(function (block) {
            if (!block.hidden) {
                height = Math.max(height, (parseInt(block.y, 10) || 0) + (parseInt(block.h, 10) || 0) + 80);
            }
        });
        return height;
    }

    function getCanvasLayer() {
        if (!BUILDER_FREE_CANVAS_ENABLED) {
            return null;
        }
        var canvas = qs('#cms_builder_design_canvas');
        if (!canvas) {
            canvas = document.createElement('div');
            canvas.id = 'cms_builder_design_canvas';
            canvas.className = 'cms-builder-design-canvas';
            var anchor = qs('#cms_builder_extra_zone') || qs('footer') || qs('main') || document.body;
            if (anchor && anchor.parentNode) {
                anchor.parentNode.insertBefore(canvas, anchor);
            } else {
                document.body.appendChild(canvas);
            }
        }
        return canvas;
    }

    function removeCanvasLayer() {
        var canvas = qs('#cms_builder_design_canvas');
        if (canvas && canvas.parentNode) {
            canvas.parentNode.removeChild(canvas);
        }
    }

    function renderCanvasLayer() {
        if (!BUILDER_FREE_CANVAS_ENABLED) {
            removeCanvasLayer();
            return;
        }
        normalizeCanvasBlocks();
        var canvas = getCanvasLayer();
        if (!canvas) {
            return;
        }
        var blocks = canvasBlocks();
        canvas.style.setProperty('--cms-builder-canvas-width', currentPreviewWidth() + 'px');
        canvas.style.setProperty('--cms-builder-canvas-height', canvasHeight() + 'px');
        canvas.classList.toggle('is-empty', blocks.length === 0);
        canvas.innerHTML = blocks.length ? (buildCanvasGuideHtml() + blocks.map(renderCanvasBlock).join('')) : '<div class="cms-builder-design-empty">从左侧“模块/素材”添加内容后，会进入这里。新画布支持自由拖拽、缩放、属性编辑，并保存到页面 JSON。</div>';
    }

    function addCanvasBlock(type, options) {
        pushHistory();
        var block = defaultCanvasBlock(type, options || {});
        if (options && typeof options.x !== 'undefined') {
            block.x = Math.max(0, parseInt(options.x, 10) || 0);
        }
        if (options && typeof options.y !== 'undefined') {
            block.y = Math.max(0, parseInt(options.y, 10) || 0);
        }
        canvasBlocks().push(block);
        state.page.free_layout_mode = 1;
        syncEditorStateToPage();
        selectCanvasBlock(block.id, false);
        queueSaveDraft();
        setStatus('已添加自由画布模块，可直接拖拽和缩放，点击“编辑”再打开属性。');
    }

    function addQuickCanvasBlock(type) {
        var point = canvasPointFromEvent();
        addCanvasBlockFromType(type, point);
    }

    function canvasPointFromEvent(event) {
        var canvas = getCanvasLayer();
        var rect = canvas.getBoundingClientRect();
        return {
            x: Math.max(0, Math.round((event ? event.clientX : rect.left + 80) - rect.left)),
            y: Math.max(0, Math.round((event ? event.clientY : rect.top + 80) - rect.top))
        };
    }

    function addCanvasBlockFromType(type, options) {
        options = options && typeof options === 'object' ? options : {};
        if (type === 'text') {
            addCanvasBlock('text', options);
            return;
        }
        if (type === 'image') {
            addCanvasBlock('image', options);
            return;
        }
        if (type === 'button') {
            addCanvasBlock('button', options);
            return;
        }
        var html = buildExtraHtml(type);
        if (!html && options.html) {
            html = options.html;
        }
        if (html) {
            addCanvasBlock('html', {
                label: canvasTypeLabel(type),
                html: html
            });
            return;
        }
        addCanvasBlock('text', {
            title: canvasTypeLabel(type),
            text: '该模块已加入自由画布，可在右侧继续编辑内容和样式。'
        });
    }

    function buildExtraHtml(type) {
        if (type === 'product_showcase' || type === 'product_list') {
            return '<section style="padding:26px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="font-size:12px;font-weight:800;color:#5b74e8;">产品展示</div><h2 style="margin:10px 0 0;font-size:28px;color:#0f172a;">精选产品橱窗</h2><div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;margin-top:18px;"><article style="padding:14px;border-radius:16px;background:#f8fbff;"><div style="height:92px;border-radius:12px;background:linear-gradient(135deg,#dbeafe,#f5d0fe);"></div><h3 style="margin:12px 0 0;font-size:16px;color:#0f172a;">产品一</h3></article><article style="padding:14px;border-radius:16px;background:#f8fbff;"><div style="height:92px;border-radius:12px;background:linear-gradient(135deg,#dcfce7,#dbeafe);"></div><h3 style="margin:12px 0 0;font-size:16px;color:#0f172a;">产品二</h3></article><article style="padding:14px;border-radius:16px;background:#f8fbff;"><div style="height:92px;border-radius:12px;background:linear-gradient(135deg,#fee2e2,#fef3c7);"></div><h3 style="margin:12px 0 0;font-size:16px;color:#0f172a;">产品三</h3></article></div></section>';
        }
        if (type === 'article_list') {
            return '<section style="padding:26px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="font-size:12px;font-weight:800;color:#5b74e8;">新闻动态</div><h2 style="margin:10px 0 0;font-size:28px;color:#0f172a;">文章资讯列表</h2><div style="display:grid;gap:12px;margin-top:18px;"><article style="padding:16px;border-radius:14px;background:#f8fbff;color:#334155;">行业资讯标题示例</article><article style="padding:16px;border-radius:14px;background:#f8fbff;color:#334155;">企业新闻标题示例</article><article style="padding:16px;border-radius:14px;background:#f8fbff;color:#334155;">解决方案标题示例</article></div></section>';
        }
        if (type === 'carousel') {
            return '<section style="height:100%;min-height:220px;border-radius:24px;background:linear-gradient(135deg,#0f172a,#2563eb);color:#fff;padding:34px;box-sizing:border-box;display:flex;flex-direction:column;justify-content:center;"><div style="font-size:12px;font-weight:800;color:#bfdbfe;">轮播横幅</div><h2 style="margin:12px 0 0;font-size:34px;line-height:1.25;">拖拽式焦点图模块</h2><p style="margin:12px 0 0;color:rgba(255,255,255,.72);line-height:1.8;">可自由调整位置和尺寸。</p></section>';
        }
        if (type === 'guestbook_form') {
            return '<section style="display:grid;grid-template-columns:.9fr 1.1fr;gap:18px;padding:26px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div><div style="font-size:12px;font-weight:800;color:#5b74e8;">在线留言</div><h2 style="margin:10px 0 0;font-size:28px;color:#0f172a;">留下您的需求</h2><p style="margin:12px 0 0;color:#64748b;line-height:1.8;">我们会尽快联系您。</p></div><div style="display:grid;gap:10px;"><input value="姓名" style="height:42px;border:1px solid #dbe3f4;border-radius:12px;padding:0 12px;"><input value="电话" style="height:42px;border:1px solid #dbe3f4;border-radius:12px;padding:0 12px;"><button style="height:42px;border:0;border-radius:12px;background:#111827;color:#fff;font-weight:800;">提交留言</button></div></section>';
        }
        if (type === 'qrcode') {
            return '<section style="padding:24px;border-radius:24px;background:#fff;border:1px solid #e7edf7;text-align:center;"><div style="width:132px;height:132px;margin:0 auto;border-radius:18px;background:repeating-linear-gradient(45deg,#111827 0 8px,#fff 8px 16px);"></div><h3 style="margin:16px 0 0;font-size:22px;color:#0f172a;">扫码关注</h3><p style="margin:8px 0 0;color:#64748b;">微信扫码获取更多信息</p></section>';
        }
        if (type === 'service_panel') {
            return '<section style="padding:24px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><h2 style="margin:0;font-size:26px;color:#0f172a;">在线客服</h2><div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:16px;"><span style="padding:10px 14px;border-radius:999px;background:#111827;color:#fff;">电话咨询</span><span style="padding:10px 14px;border-radius:999px;background:#eef2ff;color:#3b5fe2;">QQ 咨询</span><span style="padding:10px 14px;border-radius:999px;background:#ecfdf5;color:#059669;">微信客服</span></div></section>';
        }
        if (type === 'member_portal') {
            return '<section style="padding:26px;border-radius:24px;background:linear-gradient(135deg,#fff7ed,#fff);border:1px solid #fed7aa;"><h2 style="margin:0;font-size:28px;color:#0f172a;">会员中心</h2><p style="margin:10px 0 0;color:#64748b;">登录后查看订单、收藏和专属服务。</p><div style="display:flex;gap:10px;margin-top:18px;"><span style="padding:10px 16px;border-radius:999px;background:#111827;color:#fff;font-weight:800;">会员登录</span><span style="padding:10px 16px;border-radius:999px;background:#fff;border:1px solid #fed7aa;color:#9a3412;font-weight:800;">立即注册</span></div></section>';
        }
        if (type === 'vote_form') {
            return '<section style="padding:24px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><h2 style="margin:0;font-size:26px;color:#0f172a;">在线投票</h2><div style="display:grid;gap:10px;margin-top:16px;"><label style="padding:12px;border-radius:12px;background:#f8fbff;color:#334155;">选项 A</label><label style="padding:12px;border-radius:12px;background:#f8fbff;color:#334155;">选项 B</label><button style="height:42px;border:0;border-radius:12px;background:#111827;color:#fff;font-weight:800;">立即投票</button></div></section>';
        }
        return buildStaticModuleHtml(type);
    }

    function beginCanvasInteraction(id, mode, event, options) {
        options = options && typeof options === 'object' ? options : {};
        var block = findCanvasBlock(id);
        var canvas = qs('#cms_builder_design_canvas');
        if (!block || !canvas) {
            return;
        }
        if (block.locked) {
            selectCanvasBlock(id, false);
            setStatus('当前画布模块已锁定，解锁后才能移动或缩放。');
            return;
        }
        event.preventDefault();
        event.stopPropagation();
        pushHistory();
        selectCanvasBlock(id, !!options.openSettings);
        clearCanvasGuides();
        var start = {
            x: parseInt(block.x, 10) || 0,
            y: parseInt(block.y, 10) || 0,
            w: parseInt(block.w, 10) || 120,
            h: parseInt(block.h, 10) || 80,
            mouseX: typeof options.startClientX === 'number' ? options.startClientX : event.clientX,
            mouseY: typeof options.startClientY === 'number' ? options.startClientY : event.clientY,
            canvasWidth: canvas.clientWidth || 1200,
            canvasHeight: canvas.clientHeight || canvasHeight(),
            threshold: canvasSnapThreshold(),
            grid: canvasGridSize()
        };
        state.resizingMeta = {canvasBlockId: id, mode: mode};
        document.body.classList.add('cms-builder-is-resizing');
        function applyMove(moveEvent) {
            var dx = moveEvent.clientX - start.mouseX;
            var dy = moveEvent.clientY - start.mouseY;
            var next = {x: start.x, y: start.y, w: start.w, h: start.h};
            var guides = {vertical: [], horizontal: []};
            if (mode === 'move') {
                next.x = start.x + dx;
                next.y = start.y + dy;
            } else {
                if (mode.indexOf('e') !== -1) {
                    next.w = start.w + dx;
                }
                if (mode.indexOf('s') !== -1) {
                    next.h = start.h + dy;
                }
                if (mode.indexOf('w') !== -1) {
                    next.x = start.x + dx;
                    next.w = start.w - dx;
                }
                if (mode.indexOf('n') !== -1) {
                    next.y = start.y + dy;
                    next.h = start.h - dy;
                }
            }
            next.w = Math.max(48, next.w);
            next.h = Math.max(32, next.h);
            next.x = Math.max(0, Math.min(next.x, Math.max(0, start.canvasWidth - 24)));
            next.y = Math.max(0, next.y);
            var otherBlocks = canvasBlocks().filter(function (item) {
                return String(item.id) !== String(id) && !item.hidden;
            });
            var verticalCandidates = [0, start.canvasWidth / 2, start.canvasWidth];
            var horizontalCandidates = [0, start.canvasHeight / 2];
            otherBlocks.forEach(function (item) {
                var itemX = parseInt(item.x, 10) || 0;
                var itemY = parseInt(item.y, 10) || 0;
                var itemW = parseInt(item.w, 10) || 0;
                var itemH = parseInt(item.h, 10) || 0;
                verticalCandidates.push(itemX, itemX + itemW / 2, itemX + itemW);
                horizontalCandidates.push(itemY, itemY + itemH / 2, itemY + itemH);
            });
            if (moveEvent.altKey) {
                next.x = Math.round(next.x / start.grid) * start.grid;
                next.y = Math.round(next.y / start.grid) * start.grid;
                next.w = Math.round(next.w / start.grid) * start.grid;
                next.h = Math.round(next.h / start.grid) * start.grid;
                guides.vertical.push(next.x, next.x + next.w / 2);
                guides.horizontal.push(next.y, next.y + next.h / 2);
            } else {
                if (mode === 'move') {
                    var snappedLeft = snapCanvasValue(next.x, verticalCandidates, start.threshold, guides.vertical);
                    var snappedTop = snapCanvasValue(next.y, horizontalCandidates, start.threshold, guides.horizontal);
                    var snappedCenterX = snapCanvasValue(next.x + next.w / 2, verticalCandidates, start.threshold, guides.vertical);
                    var snappedCenterY = snapCanvasValue(next.y + next.h / 2, horizontalCandidates, start.threshold, guides.horizontal);
                    if (Math.abs(snappedCenterX - (next.x + next.w / 2)) <= start.threshold) {
                        next.x = snappedCenterX - next.w / 2;
                    } else {
                        next.x = snappedLeft;
                    }
                    if (Math.abs(snappedCenterY - (next.y + next.h / 2)) <= start.threshold) {
                        next.y = snappedCenterY - next.h / 2;
                    } else {
                        next.y = snappedTop;
                    }
                } else {
                    if (mode.indexOf('e') !== -1) {
                        var snappedRight = snapCanvasValue(next.x + next.w, verticalCandidates, start.threshold, guides.vertical);
                        next.w = Math.max(48, snappedRight - next.x);
                    }
                    if (mode.indexOf('s') !== -1) {
                        var snappedBottom = snapCanvasValue(next.y + next.h, horizontalCandidates, start.threshold, guides.horizontal);
                        next.h = Math.max(32, snappedBottom - next.y);
                    }
                    if (mode.indexOf('w') !== -1) {
                        var snappedWest = snapCanvasValue(next.x, verticalCandidates, start.threshold, guides.vertical);
                        next.w = Math.max(48, (next.x + next.w) - snappedWest);
                        next.x = snappedWest;
                    }
                    if (mode.indexOf('n') !== -1) {
                        var snappedNorth = snapCanvasValue(next.y, horizontalCandidates, start.threshold, guides.horizontal);
                        next.h = Math.max(32, (next.y + next.h) - snappedNorth);
                        next.y = snappedNorth;
                    }
                }
            }
            if (moveEvent.shiftKey) {
                next.x = Math.round(next.x / 10) * 10;
                next.y = Math.round(next.y / 10) * 10;
                next.w = Math.round(next.w / 10) * 10;
                next.h = Math.round(next.h / 10) * 10;
            }
            state.canvasGuides = guides;
            updateCanvasBlock(id, {
                x: Math.round(next.x),
                y: Math.round(next.y),
                w: Math.round(next.w),
                h: Math.round(next.h)
            }, true);
        }
        function onMove(moveEvent) {
            applyMove(moveEvent);
        }
        function onUp() {
            document.body.classList.remove('cms-builder-is-resizing');
            document.removeEventListener('mousemove', onMove, true);
            document.removeEventListener('mouseup', onUp, true);
            state.resizingMeta = null;
            clearCanvasGuides();
            state.suppressClickUntil = Date.now() + 180;
            queueSaveDraft();
            renderCanvasLayer();
            setStatus(mode === 'move' ? '画布模块位置已更新。按住 Alt 可按网格吸附，按住 Shift 可 10px 微调。' : '画布模块尺寸已更新。按住 Alt 可按网格吸附，按住 Shift 可 10px 微调。');
        }
        document.addEventListener('mousemove', onMove, true);
        document.addEventListener('mouseup', onUp, true);
        if (options.immediateEvent) {
            applyMove(options.immediateEvent);
        }
    }

    function duplicateCanvasBlock(id) {
        var source = findCanvasBlock(id);
        if (!source) {
            return;
        }
        pushHistory();
        var copy = normalizeCanvasBlock(JSON.parse(JSON.stringify(source)));
        copy.id = 'canvas_' + Date.now().toString(36) + '_' + Math.random().toString(16).slice(2, 6);
        copy.x += 24;
        copy.y += 24;
        copy.z = nextCanvasZIndex();
        canvasBlocks().push(copy);
        selectCanvasBlock(copy.id, false);
        queueSaveDraft();
    }

    function triggerCanvasPrimaryEditAction(id) {
        var block = findCanvasBlock(id);
        if (!block) {
            return;
        }
        selectCanvasBlock(id, true);
        setStatus('已打开画布模块属性，可修改内容、样式、位置与尺寸。');
    }

    function nudgeCanvasBlock(id, dx, dy) {
        var block = findCanvasBlock(id);
        if (!block || block.locked) {
            return false;
        }
        updateCanvasBlock(id, {
            x: Math.max(0, (parseInt(block.x, 10) || 0) + dx),
            y: Math.max(0, (parseInt(block.y, 10) || 0) + dy)
        }, true);
        return true;
    }

    function alignCanvasBlock(id, mode) {
        var block = findCanvasBlock(id);
        var canvas = qs('#cms_builder_design_canvas');
        if (!block || !canvas || block.locked) {
            return false;
        }
        var canvasWidth = canvas.clientWidth || currentPreviewWidth();
        var canvasH = canvas.clientHeight || canvasHeight();
        var patch = {};
        var x = parseInt(block.x, 10) || 0;
        var y = parseInt(block.y, 10) || 0;
        var w = parseInt(block.w, 10) || 0;
        var h = parseInt(block.h, 10) || 0;
        if (mode === 'left') {
            patch.x = 0;
        } else if (mode === 'right') {
            patch.x = Math.max(0, canvasWidth - w);
        } else if (mode === 'hcenter') {
            patch.x = Math.max(0, Math.round((canvasWidth - w) / 2));
        } else if (mode === 'top') {
            patch.y = 0;
        } else if (mode === 'bottom') {
            patch.y = Math.max(0, canvasH - h);
        } else if (mode === 'vcenter') {
            patch.y = Math.max(0, Math.round((canvasH - h) / 2));
        } else {
            return false;
        }
        updateCanvasBlock(id, patch, true);
        queueSaveDraft();
        return true;
    }

    function alignCanvasSelection(mode) {
        var ids = selectedCanvasIds();
        if (!ids.length) {
            return false;
        }
        var changed = false;
        ids.forEach(function (id) {
            if (alignCanvasBlock(id, mode)) {
                changed = true;
            }
        });
        if (changed) {
            renderCanvasLayer();
            renderNodeList();
            renderSettings();
        }
        return changed;
    }

    function patchCanvasSelection(patch) {
        var ids = selectedCanvasIds();
        if (!ids.length) {
            return false;
        }
        ids.forEach(function (id) {
            updateCanvasBlock(id, patch, true);
        });
        renderCanvasLayer();
        renderNodeList();
        renderSettings();
        queueSaveDraft();
        return true;
    }

    function deleteCanvasSelection() {
        var ids = selectedCanvasIds();
        if (!ids.length) {
            return false;
        }
        pushHistory();
        state.page.frontend_canvas_blocks = canvasBlocks().filter(function (block) {
            return ids.indexOf(String(block.id)) === -1;
        });
        state.selectedCanvasBlockId = '';
        state.selectedCanvasBlockIds = [];
        renderCanvasLayer();
        renderLibraryPanel();
        renderNodeList();
        renderSettings();
        queueSaveDraft();
        setStatus(ids.length > 1 ? '已删除所选自由模块。' : '已删除画布模块。');
        return true;
    }

    function ensureSettingsPanelForCurrentSelection() {
        var anchorNode = currentSettingsAnchorNode();
        if (!anchorNode) {
            hideSettingsPanel();
            return;
        }
        showSettingsPanel(anchorNode);
    }

    function cancelPendingCanvasDrag() {
        if (!state.pendingCanvasDrag) {
            return;
        }
        if (state.pendingCanvasDrag.onMove) {
            document.removeEventListener('mousemove', state.pendingCanvasDrag.onMove, true);
        }
        if (state.pendingCanvasDrag.onUp) {
            document.removeEventListener('mouseup', state.pendingCanvasDrag.onUp, true);
        }
        state.pendingCanvasDrag = null;
    }

    function cancelPendingBlockDrag() {
        if (!state.pendingBlockDrag) {
            return;
        }
        if (state.pendingBlockDrag.onMove) {
            document.removeEventListener('mousemove', state.pendingBlockDrag.onMove, true);
        }
        if (state.pendingBlockDrag.onUp) {
            document.removeEventListener('mouseup', state.pendingBlockDrag.onUp, true);
        }
        state.pendingBlockDrag = null;
    }

    function deleteCanvasBlock(id) {
        pushHistory();
        state.page.frontend_canvas_blocks = canvasBlocks().filter(function (block) {
            return String(block.id) !== String(id);
        });
        state.selectedCanvasBlockId = '';
        renderCanvasLayer();
        renderLibraryPanel();
        renderNodeList();
        renderSettings();
        queueSaveDraft();
        setStatus('已删除画布模块。');
    }

    function moveCanvasZ(id, delta) {
        var block = findCanvasBlock(id);
        if (!block) {
            return;
        }
        pushHistory();
        if (delta === 999) {
            block.z = nextCanvasZIndex();
        } else {
            block.z = Math.max(1, (parseInt(block.z, 10) || 1) + delta);
        }
        updateCanvasBlock(id, {z: block.z});
        queueSaveDraft();
    }

    function renderCanvasSettings() {
        var block = findCanvasBlock(state.selectedCanvasBlockId);
        if (!block) {
            return '';
        }
        var content = block.content || {};
        var style = block.style || {};
        var shadow = style.shadow || '';
        var contentFields = '';
        if (block.type === 'image') {
            contentFields = '<div class="cms-builder-field"><label>图片地址</label><input type="text" data-canvas-content-key="src" value="' + escapeHtml(content.src || '') + '"></div>' +
                '<div class="cms-builder-field"><label>图片说明</label><input type="text" data-canvas-content-key="alt" value="' + escapeHtml(content.alt || '') + '"></div>' +
                '<div class="cms-builder-field"><label>填充方式</label><select data-canvas-style-key="fit"><option value="cover"' + ((style.fit || 'cover') === 'cover' ? ' selected' : '') + '>裁剪铺满</option><option value="contain"' + (style.fit === 'contain' ? ' selected' : '') + '>完整显示</option></select></div>';
        } else if (block.type === 'button') {
            contentFields = '<div class="cms-builder-field"><label>按钮文字</label><input type="text" data-canvas-content-key="text" value="' + escapeHtml(content.text || '') + '"></div>' +
                '<div class="cms-builder-field"><label>链接地址</label><input type="text" data-canvas-content-key="href" value="' + escapeHtml(content.href || '') + '"></div>';
        } else if (block.type === 'html') {
            contentFields = '<div class="cms-builder-field"><label>HTML 内容</label><textarea data-canvas-content-key="html" style="min-height:160px;">' + escapeHtml(content.html || '') + '</textarea></div>';
        } else {
            contentFields = '<div class="cms-builder-field"><label>标题</label><input type="text" data-canvas-content-key="title" value="' + escapeHtml(content.title || '') + '"></div>' +
                '<div class="cms-builder-field"><label>正文</label><textarea data-canvas-content-key="text">' + escapeHtml(content.text || '') + '</textarea></div>';
        }
        return '<div class="cms-builder-group">' +
            '<div class="cms-builder-group-title">自由画布模块</div>' +
            '<div class="cms-builder-group-desc">当前模块不依赖模板 DOM，位置、尺寸、样式和内容都保存到页面 JSON。</div>' +
            '<div class="cms-builder-badges"><span class="cms-builder-badge">' + escapeHtml(canvasTypeLabel(block.type)) + '</span>' + (block.locked ? '<span class="cms-builder-badge">已锁定</span>' : '') + '</div>' +
            '<div class="cms-builder-action-grid">' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="lock" data-canvas-id="' + escapeHtml(block.id) + '">' + (block.locked ? '取消锁定' : '锁定模块') + '</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="duplicate" data-canvas-id="' + escapeHtml(block.id) + '">复制模块</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="back" data-canvas-id="' + escapeHtml(block.id) + '">下移一层</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="front" data-canvas-id="' + escapeHtml(block.id) + '">置于顶层</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="delete" data-canvas-id="' + escapeHtml(block.id) + '">删除模块</button>' +
            '</div>' +
            '<div class="cms-builder-group-desc" style="margin-top:10px;">快速对齐：左/中/右、上/中/下。键盘也可直接微调当前模块。</div>' +
            '<div class="cms-builder-action-grid">' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="align-left" data-canvas-id="' + escapeHtml(block.id) + '">左对齐</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="align-hcenter" data-canvas-id="' + escapeHtml(block.id) + '">水平居中</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="align-right" data-canvas-id="' + escapeHtml(block.id) + '">右对齐</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="align-top" data-canvas-id="' + escapeHtml(block.id) + '">顶对齐</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="align-vcenter" data-canvas-id="' + escapeHtml(block.id) + '">垂直居中</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="align-bottom" data-canvas-id="' + escapeHtml(block.id) + '">底对齐</button>' +
            '</div>' +
            '<div class="cms-builder-fields">' +
                '<div class="cms-builder-canvas-field-row">' +
                    '<div class="cms-builder-field"><label>X</label><input type="number" data-canvas-geom-key="x" value="' + escapeHtml(block.x) + '"></div>' +
                    '<div class="cms-builder-field"><label>Y</label><input type="number" data-canvas-geom-key="y" value="' + escapeHtml(block.y) + '"></div>' +
                    '<div class="cms-builder-field"><label>宽度</label><input type="number" min="48" data-canvas-geom-key="w" value="' + escapeHtml(block.w) + '"></div>' +
                    '<div class="cms-builder-field"><label>高度</label><input type="number" min="32" data-canvas-geom-key="h" value="' + escapeHtml(block.h) + '"></div>' +
                '</div>' +
                '<div class="cms-builder-canvas-field-row">' +
                    '<div class="cms-builder-field"><label>层级</label><input type="number" min="1" data-canvas-geom-key="z" value="' + escapeHtml(block.z) + '"></div>' +
                    '<div class="cms-builder-field"><label>透明度</label><input type="number" min="1" max="100" data-canvas-style-key="opacity" value="' + escapeHtml(style.opacity == null ? 100 : style.opacity) + '"></div>' +
                    '<div class="cms-builder-field"><label>标签名</label><input type="text" data-canvas-meta-key="label" value="' + escapeHtml(block.label || '') + '"></div>' +
                    '<div class="cms-builder-field"><label>显示状态</label><select data-canvas-meta-key="hidden"><option value="0"' + (!block.hidden ? ' selected' : '') + '>显示</option><option value="1"' + (block.hidden ? ' selected' : '') + '>隐藏</option></select></div>' +
                '</div>' +
                contentFields +
                '<div class="cms-builder-canvas-field-row">' +
                    '<div class="cms-builder-field"><label>背景</label><input type="text" data-canvas-style-key="background" value="' + escapeHtml(style.background || '') + '"></div>' +
                    '<div class="cms-builder-field"><label>文字颜色</label><input type="text" data-canvas-style-key="color" value="' + escapeHtml(style.color || '') + '"></div>' +
                    '<div class="cms-builder-field"><label>圆角</label><input type="number" min="0" data-canvas-style-key="radius" value="' + escapeHtml(style.radius == null ? 18 : style.radius) + '"></div>' +
                    '<div class="cms-builder-field"><label>内边距</label><input type="number" min="0" data-canvas-style-key="padding" value="' + escapeHtml(style.padding == null ? 0 : style.padding) + '"></div>' +
                '</div>' +
                '<div class="cms-builder-canvas-field-row">' +
                    '<div class="cms-builder-field"><label>边框宽度</label><input type="number" min="0" data-canvas-style-key="border_width" value="' + escapeHtml(style.border_width == null ? 0 : style.border_width) + '"></div>' +
                    '<div class="cms-builder-field"><label>边框样式</label><select data-canvas-style-key="border_style"><option value="solid"' + ((style.border_style || 'solid') === 'solid' ? ' selected' : '') + '>实线</option><option value="dashed"' + (style.border_style === 'dashed' ? ' selected' : '') + '>虚线</option><option value="dotted"' + (style.border_style === 'dotted' ? ' selected' : '') + '>点线</option></select></div>' +
                    '<div class="cms-builder-field"><label>边框颜色</label><input type="text" data-canvas-style-key="border_color" value="' + escapeHtml(style.border_color || '#dbe3f4') + '"></div>' +
                    '<div class="cms-builder-field"><label>阴影</label><input type="text" data-canvas-style-key="shadow" value="' + escapeHtml(shadow) + '" placeholder="如 0 18px 48px rgba(15,23,42,.16)"></div>' +
                '</div>' +
            '</div>' +
        '</div>';
    }

    function renderCanvasSelectionSettings() {
        var ids = selectedCanvasIds();
        if (ids.length <= 1) {
            return '';
        }
        return '<div class="cms-builder-group">' +
            '<div class="cms-builder-group-title">批量画布操作</div>' +
            '<div class="cms-builder-group-desc">当前已选中 ' + escapeHtml(ids.length) + ' 个自由模块。可直接做批量对齐、锁定、显隐和删除。</div>' +
            '<div class="cms-builder-action-grid">' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="align-left">左对齐</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="align-hcenter">水平居中</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="align-right">右对齐</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="align-top">顶对齐</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="align-vcenter">垂直居中</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="align-bottom">底对齐</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="selection-lock">批量锁定</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="selection-unlock">批量解锁</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="selection-hide">批量隐藏</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="selection-show">批量显示</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="selection-clear">取消多选</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-light" data-canvas-action="selection-delete">批量删除</button>' +
            '</div>' +
        '</div>';
    }

    function renderMaterialLibraryContent() {
        var presets = filteredMaterialPresets();
        var categoryTabs = materialCategories().map(function (item) {
            return '<button type="button" class="cms-builder-material-cat' + (state.materialCategory === item.key ? ' is-active' : '') + '" data-material-category="' + item.key + '">' + escapeHtml(item.label) + '</button>';
        }).join('');
        return '' +
            '<div class="cms-builder-material-wrap">' +
                '<div class="cms-builder-material-searchbar">' +
                    '<div class="cms-builder-material-search"><input type="text" placeholder="输入关键词搜索素材" value="' + escapeHtml(state.materialSearch || '') + '" data-material-search="1"><span class="cms-builder-material-search-icon">' + libraryIcon('search') + '</span></div>' +
                    '<button type="button" class="cms-builder-material-gear" title="素材设置">' + libraryIcon('settings') + '</button>' +
                '</div>' +
                '<div class="cms-builder-material-scopes">' +
                    '<button type="button" class="cms-builder-material-scope' + (state.materialScope === 'mine' ? ' is-active' : '') + '" data-material-scope="mine">我的</button>' +
                    '<button type="button" class="cms-builder-material-scope' + (state.materialScope === 'scene' ? ' is-active' : '') + '" data-material-scope="scene">场景</button>' +
                '</div>' +
                '<div class="cms-builder-material-cats">' + categoryTabs + '<span class="cms-builder-material-cats-more">' + libraryIcon('chevron') + '</span></div>' +
                '<div class="cms-builder-library-scroll"><div class="cms-builder-material-list">' + (presets.length ? presets.map(renderMaterialCard).join('') : '<div class="cms-builder-material-result-empty">没有找到匹配的素材方案，换个关键词或切换分类试试。</div>') + '</div></div>' +
            '</div>';
    }

    function renderLibraryShell(content) {
        return '<div class="cms-builder-library-shell">' + renderLibraryModeBar() + content + '</div>';
    }

    function applyMaterialPreset(key) {
        var preset = findMaterialPreset(key);
        if (!preset) {
            return;
        }
        addCanvasBlockFromType(preset.type, preset.options || {});
        state.materialPreviewKey = '';
        setStatus('已插入素材：' + preset.title);
    }

    function buildChannelNavModuleHtml() {
        var options = currentColumnOptions();
        var items = options.length ? options.slice(0, 6).map(function (item, index) {
            return {
                label: item.label || ('栏目 ' + (index + 1)),
                url: item.value || 'javascript:void(0)',
                active: normalizeColumnUrl(item.value || '') === normalizeColumnUrl(window.location.href)
            };
        }) : [
            {label: '首页', url: 'javascript:void(0)', active: true},
            {label: '关于我们', url: 'javascript:void(0)', active: false},
            {label: '产品中心', url: 'javascript:void(0)', active: false},
            {label: '新闻动态', url: 'javascript:void(0)', active: false},
            {label: '联系我们', url: 'javascript:void(0)', active: false}
        ];
        return '<nav style="padding:18px 22px;border-radius:22px;background:#fff;border:1px solid #e7edf7;"><div style="display:flex;flex-wrap:wrap;gap:12px;">' +
            items.map(function (item) {
                return '<a href="' + escapeHtml(item.url) + '" style="display:inline-flex;align-items:center;justify-content:center;padding:10px 18px;border-radius:999px;text-decoration:none;font-size:14px;font-weight:700;' + (item.active ? 'background:#111827;color:#fff;' : 'background:#f8fbff;color:#334155;border:1px solid #e7edf7;') + '">' + escapeHtml(item.label) + '</a>';
            }).join('') +
        '</div></nav>';
    }

    function buildProductCategoryModuleHtml() {
        var categories = Array.isArray(state.businessOptions.product_categories) ? state.businessOptions.product_categories : [];
        var items = categories.length ? categories.slice(0, 8).map(function (item, index) {
            return {
                label: item.channel_name || ('产品分类 ' + (index + 1)),
                active: index === 0
            };
        }) : [
            {label: '实木板材', active: true},
            {label: '门墙柜一体', active: false},
            {label: '全屋定制', active: false},
            {label: '工程配套', active: false},
            {label: '热门新品', active: false}
        ];
        return '<section style="padding:24px;border-radius:24px;background:linear-gradient(180deg,#fff,#f8fbff);border:1px solid #e7edf7;"><div style="display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;"><div><div style="font-size:12px;font-weight:700;color:#5b74e8;">产品分类</div><h3 style="margin:10px 0 0;font-size:28px;color:#0f172a;">当前站点产品栏目导航</h3></div><span style="padding:8px 14px;border-radius:999px;background:#eef2ff;color:#4664df;font-size:12px;font-weight:700;">支持继续改文字</span></div><div style="display:flex;flex-wrap:wrap;gap:12px;margin-top:18px;">' +
            items.map(function (item) {
                return '<span style="display:inline-flex;align-items:center;justify-content:center;padding:11px 18px;border-radius:999px;font-size:14px;font-weight:700;' + (item.active ? 'background:#111827;color:#fff;' : 'background:#fff;color:#334155;border:1px solid #dbe3f4;') + '">' + escapeHtml(item.label) + '</span>';
            }).join('') +
        '</div></section>';
    }

    function buildStaticModuleHtml(type) {
        if (type === 'image_text') {
            return materialHtmlTemplate('image_text_showcase', 1);
        }
        if (type === 'multi_image') {
            return '<section style="padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:flex;align-items:end;justify-content:space-between;gap:16px;flex-wrap:wrap;"><div><div style="font-size:12px;font-weight:700;color:#5b74e8;">多图展示</div><h2 style="margin:12px 0 0;font-size:32px;color:#0f172a;">四张图片并列展示当前主推内容</h2></div><p style="margin:0;color:#64748b;line-height:1.8;">适合案例列表、产品橱窗和场景图库。</p></div><div style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px;margin-top:22px;"><article><img src="' + materialMockImage('案例 A', '主推产品', '#dbeafe', '#eff6ff', false) + '" alt="展示图 A" style="display:block;width:100%;height:180px;object-fit:cover;border-radius:18px;"><h3 style="margin:14px 0 0;font-size:18px;color:#0f172a;">展示图 A</h3><p style="margin:8px 0 0;color:#64748b;line-height:1.8;">简洁说明文字，可直接编辑。</p></article><article><img src="' + materialMockImage('案例 B', '空间实景', '#fde68a', '#fff7ed', false) + '" alt="展示图 B" style="display:block;width:100%;height:180px;object-fit:cover;border-radius:18px;"><h3 style="margin:14px 0 0;font-size:18px;color:#0f172a;">展示图 B</h3><p style="margin:8px 0 0;color:#64748b;line-height:1.8;">支持替换成真实图片。</p></article><article><img src="' + materialMockImage('案例 C', '热门产品', '#c7d2fe', '#eef2ff', false) + '" alt="展示图 C" style="display:block;width:100%;height:180px;object-fit:cover;border-radius:18px;"><h3 style="margin:14px 0 0;font-size:18px;color:#0f172a;">展示图 C</h3><p style="margin:8px 0 0;color:#64748b;line-height:1.8;">适合首页主推区。</p></article><article><img src="' + materialMockImage('案例 D', '品牌展示', '#bbf7d0', '#ecfccb', false) + '" alt="展示图 D" style="display:block;width:100%;height:180px;object-fit:cover;border-radius:18px;"><h3 style="margin:14px 0 0;font-size:18px;color:#0f172a;">展示图 D</h3><p style="margin:8px 0 0;color:#64748b;line-height:1.8;">新增后可继续拖拽调整。</p></article></div></section>';
        }
        if (type === 'gallery') {
            return '<section style="padding:28px;border-radius:24px;background:linear-gradient(180deg,#fff,#fbfcff);border:1px solid #e7edf7;"><div><div style="font-size:12px;font-weight:700;color:#5b74e8;">图册目录</div><h2 style="margin:12px 0 0;font-size:32px;color:#0f172a;">用拼贴布局展示图册与案例合集</h2></div><div style="display:grid;grid-template-columns:1.2fr .8fr;gap:16px;margin-top:22px;"><img src="' + materialMockImage('图册封面', '精选案例集', '#dbeafe', '#eff6ff', false) + '" alt="图册主图" style="display:block;width:100%;height:320px;object-fit:cover;border-radius:22px;"><div style="display:grid;gap:16px;"><img src="' + materialMockImage('工厂实拍', '生产环境', '#c7d2fe', '#eef2ff', false) + '" alt="工厂实拍" style="display:block;width:100%;height:152px;object-fit:cover;border-radius:18px;"><img src="' + materialMockImage('项目落地', '空间效果', '#bfdbfe', '#e0f2fe', false) + '" alt="项目落地" style="display:block;width:100%;height:152px;object-fit:cover;border-radius:18px;"></div></div></section>';
        }
        if (type === 'video_embed') {
            return materialHtmlTemplate('video_showcase', 1);
        }
        if (type === 'channel_nav') {
            return buildChannelNavModuleHtml();
        }
        if (type === 'free_container') {
            return '<section style="min-height:220px;padding:28px;border-radius:24px;background:linear-gradient(180deg,#ffffff,#f8fbff);border:1px dashed #cbd5e1;"><div style="font-size:12px;font-weight:700;color:#5b74e8;">自由容器</div><h2 style="margin:12px 0 0;font-size:30px;color:#0f172a;">这是一个可自由承载内容的空白版块</h2><p style="margin:14px 0 0;font-size:14px;line-height:1.9;color:#64748b;">可以继续在这里编辑文字、替换图片，作为说明区、转化区或自定义内容容器使用。</p></section>';
        }
        if (type === 'hover_card') {
            return materialHtmlTemplate('hover_effect', 1);
        }
        if (type === 'tabs_horizontal') {
            return '<section style="padding:26px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:flex;gap:12px;flex-wrap:wrap;"><button type="button" style="padding:10px 18px;border:0;border-radius:999px;background:#111827;color:#fff;font-weight:700;">服务流程</button><button type="button" style="padding:10px 18px;border:1px solid #dbe3f4;border-radius:999px;background:#fff;color:#334155;font-weight:700;">交付标准</button><button type="button" style="padding:10px 18px;border:1px solid #dbe3f4;border-radius:999px;background:#fff;color:#334155;font-weight:700;">售后保障</button></div><div style="margin-top:18px;padding:22px;border-radius:18px;background:#f8fbff;"><h3 style="margin:0;font-size:24px;color:#0f172a;">横向标签内容区</h3><p style="margin:12px 0 0;color:#64748b;line-height:1.9;">适合在一个版块中组织多组说明内容。当前为默认示意，可继续改成真实标签文案。</p></div></section>';
        }
        if (type === 'tabs_vertical') {
            return '<section style="display:grid;grid-template-columns:220px 1fr;gap:18px;padding:26px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:grid;gap:10px;"><button type="button" style="padding:14px 18px;border:0;border-radius:16px;background:#111827;color:#fff;text-align:left;font-weight:700;">品牌介绍</button><button type="button" style="padding:14px 18px;border:1px solid #dbe3f4;border-radius:16px;background:#f8fbff;color:#334155;text-align:left;font-weight:700;">产品亮点</button><button type="button" style="padding:14px 18px;border:1px solid #dbe3f4;border-radius:16px;background:#f8fbff;color:#334155;text-align:left;font-weight:700;">合作模式</button></div><div style="padding:24px;border-radius:18px;background:linear-gradient(135deg,#eff6ff,#fff);"><div style="font-size:12px;font-weight:700;color:#5b74e8;">纵向标签</div><h3 style="margin:12px 0 0;font-size:28px;color:#0f172a;">左侧选择分类，右侧承载详细说明</h3><p style="margin:14px 0 0;color:#64748b;line-height:1.9;">更适合流程、说明、服务导航这类层级更清晰的内容布局。</p></div></section>';
        }
        if (type === 'collapse_panel') {
            return '<section style="padding:26px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><article style="padding:18px 20px;border-radius:18px;background:#111827;color:#fff;"><h3 style="margin:0;font-size:20px;">折叠标签一</h3><p style="margin:10px 0 0;line-height:1.9;color:rgba(255,255,255,.78);">默认展开的一组说明内容，适合 FAQ、工艺参数和服务说明。</p></article><article style="margin-top:14px;padding:18px 20px;border-radius:18px;background:#f8fbff;border:1px solid #e7edf7;"><h3 style="margin:0;font-size:20px;color:#0f172a;">折叠标签二</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.9;">后续可改成更多问答或参数条目。</p></article></section>';
        }
        if (type === 'image_tabs') {
            return '<section style="padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px;"><article><img src="' + materialMockImage('展厅环境', '品牌空间', '#dbeafe', '#eff6ff', false) + '" alt="图片标签一" style="display:block;width:100%;height:168px;object-fit:cover;border-radius:18px;"><h3 style="margin:12px 0 0;font-size:18px;color:#0f172a;">图片标签一</h3></article><article><img src="' + materialMockImage('产品细节', '工艺纹理', '#fde68a', '#fff7ed', false) + '" alt="图片标签二" style="display:block;width:100%;height:168px;object-fit:cover;border-radius:18px;"><h3 style="margin:12px 0 0;font-size:18px;color:#0f172a;">图片标签二</h3></article><article><img src="' + materialMockImage('工程案例', '空间落地', '#c7d2fe', '#eef2ff', false) + '" alt="图片标签三" style="display:block;width:100%;height:168px;object-fit:cover;border-radius:18px;"><h3 style="margin:12px 0 0;font-size:18px;color:#0f172a;">图片标签三</h3></article></div></section>';
        }
        if (type === 'accordion') {
            return '<section style="padding:26px;border-radius:24px;background:linear-gradient(180deg,#fff,#fbfcff);border:1px solid #e7edf7;"><div style="font-size:12px;font-weight:700;color:#5b74e8;">手风琴</div><div style="display:grid;gap:14px;margin-top:16px;"><article style="padding:18px 20px;border-radius:18px;background:#fff;border:1px solid #dbe3f4;"><h3 style="margin:0;font-size:20px;color:#0f172a;">需求沟通</h3><p style="margin:10px 0 0;color:#64748b;line-height:1.9;">第一项默认展开，适合展示步骤、服务流程或常见问题。</p></article><article style="padding:18px 20px;border-radius:18px;background:#fff;border:1px solid #dbe3f4;"><h3 style="margin:0;font-size:20px;color:#0f172a;">方案确认</h3></article><article style="padding:18px 20px;border-radius:18px;background:#fff;border:1px solid #dbe3f4;"><h3 style="margin:0;font-size:20px;color:#0f172a;">交付维护</h3></article></div></section>';
        }
        if (type === 'columns') {
            return '<section style="padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;"><article style="padding:22px;border-radius:20px;background:#f8fbff;"><h3 style="margin:0;font-size:22px;color:#0f172a;">第一列</h3><p style="margin:12px 0 0;color:#64748b;line-height:1.9;">适合优势、流程、参数、联系方式等信息并列展示。</p></article><article style="padding:22px;border-radius:20px;background:#f8fbff;"><h3 style="margin:0;font-size:22px;color:#0f172a;">第二列</h3><p style="margin:12px 0 0;color:#64748b;line-height:1.9;">列数与内容都可继续在页面中调整。</p></article><article style="padding:22px;border-radius:20px;background:#f8fbff;"><h3 style="margin:0;font-size:22px;color:#0f172a;">第三列</h3><p style="margin:12px 0 0;color:#64748b;line-height:1.9;">更适合栅格型内容排布。</p></article></div></section>';
        }
        if (type === 'fullwidth') {
            return '<section style="padding:34px;border-radius:28px;background:linear-gradient(135deg,#0f172a,#1d4ed8);color:#fff;"><div style="max-width:980px;margin:0 auto;"><div style="font-size:12px;font-weight:700;color:#bfdbfe;">通栏排版</div><h2 style="margin:14px 0 0;font-size:38px;line-height:1.35;">适合作为整屏横幅、品牌口号或大转化区</h2><p style="margin:14px 0 0;font-size:15px;line-height:1.9;color:rgba(255,255,255,.76);">新增后仍可继续编辑文字、背景色、图片、按钮和尺寸。</p><a href="javascript:void(0)" style="display:inline-flex;margin-top:18px;padding:12px 18px;border-radius:999px;background:#fff;color:#0f172a;text-decoration:none;font-weight:700;">立即咨询</a></div></section>';
        }
        if (type === 'product_category') {
            return buildProductCategoryModuleHtml();
        }
        if (type === 'product_filter') {
            return '<section style="padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:flex;align-items:end;justify-content:space-between;gap:16px;flex-wrap:wrap;"><div><div style="font-size:12px;font-weight:700;color:#5b74e8;">产品筛选</div><h2 style="margin:12px 0 0;font-size:32px;color:#0f172a;">用标签和卡片快速组织产品内容</h2></div><div style="display:flex;flex-wrap:wrap;gap:10px;"><span style="padding:8px 14px;border-radius:999px;background:#111827;color:#fff;font-size:12px;font-weight:700;">全部</span><span style="padding:8px 14px;border-radius:999px;background:#f8fbff;color:#334155;border:1px solid #dbe3f4;font-size:12px;font-weight:700;">板材</span><span style="padding:8px 14px;border-radius:999px;background:#f8fbff;color:#334155;border:1px solid #dbe3f4;font-size:12px;font-weight:700;">门墙</span><span style="padding:8px 14px;border-radius:999px;background:#f8fbff;color:#334155;border:1px solid #dbe3f4;font-size:12px;font-weight:700;">整装</span></div></div><div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;margin-top:22px;"><article style="padding:16px;border-radius:18px;background:#f8fbff;"><img src="' + materialMockImage('生态板系列', '环保饰面', '#c7d2fe', '#e0f2fe', false) + '" alt="生态板系列" style="display:block;width:100%;height:180px;object-fit:cover;border-radius:14px;"><h3 style="margin:14px 0 0;font-size:18px;color:#0f172a;">生态板系列</h3><p style="margin:8px 0 0;color:#2563eb;font-weight:700;">￥168/张</p></article><article style="padding:16px;border-radius:18px;background:#f8fbff;"><img src="' + materialMockImage('实木门板', '木纹质感', '#f5d0fe', '#dbeafe', false) + '" alt="实木门板" style="display:block;width:100%;height:180px;object-fit:cover;border-radius:14px;"><h3 style="margin:14px 0 0;font-size:18px;color:#0f172a;">实木门板</h3><p style="margin:8px 0 0;color:#2563eb;font-weight:700;">￥899/套</p></article><article style="padding:16px;border-radius:18px;background:#f8fbff;"><img src="' + materialMockImage('景观木', '耐候处理', '#bbf7d0', '#dbeafe', false) + '" alt="景观木" style="display:block;width:100%;height:180px;object-fit:cover;border-radius:14px;"><h3 style="margin:14px 0 0;font-size:18px;color:#0f172a;">户外景观木</h3><p style="margin:8px 0 0;color:#2563eb;font-weight:700;">￥268/平</p></article></div></section>';
        }
        if (type === 'site_search') {
            return '<section style="padding:26px;border-radius:24px;background:linear-gradient(135deg,#eff6ff,#fff);border:1px solid #e7edf7;"><div style="font-size:12px;font-weight:700;color:#5b74e8;">全站搜索</div><h2 style="margin:12px 0 0;font-size:30px;color:#0f172a;">在页面中加入明显的搜索入口</h2><form action="javascript:void(0)" style="display:flex;gap:12px;flex-wrap:wrap;margin-top:18px;"><input type="search" value="搜索产品、文章、案例" style="flex:1 1 280px;height:48px;padding:0 16px;border:1px solid #cbd5e1;border-radius:999px;background:#fff;color:#0f172a;"><button type="button" style="height:48px;padding:0 20px;border:0;border-radius:999px;background:#111827;color:#fff;font-weight:700;">立即搜索</button></form></section>';
        }
        if (type === 'site_share') {
            return '<section style="padding:24px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;"><div><div style="font-size:12px;font-weight:700;color:#5b74e8;">分享网站</div><h3 style="margin:12px 0 0;font-size:28px;color:#0f172a;">一键分享当前页面到常用渠道</h3></div><div style="display:flex;flex-wrap:wrap;gap:12px;"><span style="padding:11px 16px;border-radius:999px;background:#22c55e;color:#fff;font-weight:700;">微信</span><span style="padding:11px 16px;border-radius:999px;background:#2563eb;color:#fff;font-weight:700;">QQ</span><span style="padding:11px 16px;border-radius:999px;background:#0ea5e9;color:#fff;font-weight:700;">微博</span><span style="padding:11px 16px;border-radius:999px;background:#111827;color:#fff;font-weight:700;">复制链接</span></div></div></section>';
        }
        if (type === 'query_form') {
            return '<section style="display:grid;grid-template-columns:.92fr 1.08fr;gap:18px;padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div><div style="font-size:12px;font-weight:700;color:#5b74e8;">在线查询</div><h2 style="margin:12px 0 0;font-size:32px;color:#0f172a;">适合订单、物流、编号或服务状态查询</h2><p style="margin:14px 0 0;color:#64748b;line-height:1.9;">这是可视化初始版式，后续可以继续替换标题和表单文案。</p></div><form action="javascript:void(0)" style="display:grid;gap:12px;padding:22px;border-radius:20px;background:#f8fbff;"><input type="text" value="请输入订单号或手机号" style="height:46px;padding:0 14px;border:1px solid #dbe3f4;border-radius:12px;background:#fff;"><input type="text" value="验证码 / 查询码" style="height:46px;padding:0 14px;border:1px solid #dbe3f4;border-radius:12px;background:#fff;"><button type="button" style="height:46px;border:0;border-radius:12px;background:#111827;color:#fff;font-weight:700;">立即查询</button></form></section>';
        }
        if (type === 'advanced_block') {
            return materialHtmlTemplate('company_advantage', 5);
        }
        return '';
    }

    function renderLibraryPanelBody() {
        if (state.libraryPanelMode === 'modules') {
            return renderLibraryShell(renderModuleLibraryContent());
        }
        if (state.libraryPanelMode === 'style') {
            return renderLibraryShell('<div class="cms-builder-library-content">' + renderPageSettings() + '</div>');
        }
        if (state.libraryPanelMode === 'settings') {
            return renderLibraryShell('<div class="cms-builder-library-content">' + renderSettingsLibraryContent() + '</div>');
        }
        return renderLibraryShell('<div class="cms-builder-library-content"><div class="cms-builder-library-note">这里保留撤销、重做、发布体检、发布上线、恢复原始模板和页面区块导航，便于装修过程中快速操作。草稿由系统自动保存。</div><div class="cms-builder-library-scroll"><div class="cms-builder-group"><div class="cms-builder-group-title">快捷操作</div><div class="cms-builder-action-grid"><button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_tool_undo">撤销</button><button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_tool_redo">重做</button><button type="button" class="cms-builder-btn cms-builder-btn-light cms-builder-ui-hidden" id="cms_builder_tool_save">保存草稿</button><button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_tool_preflight">发布体检</button><button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_tool_publish">发布上线</button><button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_tool_reset_origin">恢复原始模板</button></div></div><div class="cms-builder-group"><div class="cms-builder-group-title">页面区块导航</div><div class="cms-builder-group-desc">快速定位当前页面区块。</div><div class="cms-builder-list" id="cms_builder_node_list"></div></div></div></div>');
    }

    function renderLibraryPanel() {
        var panel = panelNode('library');
        if (!panel) {
            return;
        }
        var bodyNode = qs('#cms_builder_library_body', panel);
        if (bodyNode) {
            bodyNode.innerHTML = renderLibraryPanelBody();
        }
        renderFloatingOverlays();
        qsa('[data-library-mode]').forEach(function (button) {
            button.classList.toggle('is-active', button.getAttribute('data-library-mode') === state.libraryPanelMode && !panel.classList.contains('is-hidden'));
        });
        qsa('[data-add-extra]').forEach(function (button) {
            button.setAttribute('draggable', 'true');
            if (!button.getAttribute('data-extra-bound')) {
                button.setAttribute('data-extra-bound', '1');
                button.addEventListener('click', function () {
                    addExtraSectionAtCurrentPosition(button.getAttribute('data-add-extra'));
                });
            }
        });
        renderNodeList();
    }

    function queueLibrarySearchRender(selector) {
        clearTimeout(state.librarySearchTimer);state.librarySearchTimer=setTimeout(function(){var active=document.activeElement,position=active&&typeof active.selectionStart==='number'?active.selectionStart:null;renderLibraryPanel();var input=qs(selector);if(input){input.focus();if(position!==null&&input.setSelectionRange)input.setSelectionRange(position,position);}},180);
    }

    function setLibraryMode(mode, forceOpen) {
        state.libraryPanelMode = mode || 'modules';
        syncEditorStateToPage();
        if (forceOpen === false) {
            renderLibraryPanel();
            hideLibraryPanel();
            queueSaveDraft();
            return;
        }
        showLibraryPanel();
        renderLibraryPanel();
        queueSaveDraft();
    }

    function showSettingsPanel(anchorNode) {
        var panel = panelNode('settings');
        if (!panel) {
            return;
        }
        state.settingsPanelOpen = true;
        panel.classList.remove('is-hidden');
        syncSettingsBackdrop();
        if (!state.panelPositions.settings
            || typeof state.panelPositions.settings.left !== 'number'
            || typeof state.panelPositions.settings.top !== 'number'
            || (state.panelPositions.settings.left === 0 && state.panelPositions.settings.top === 82)) {
            resetPanelPosition('settings');
        } else {
            applyPanelPosition('settings');
        }
    }

    function clonePageState() {
        syncEditorStateToPage();
        return JSON.parse(JSON.stringify({
            page: state.page,
            sections: state.sections
        }));
    }

    function restorePageState(snapshot) {
        state.page = snapshot.page || {};
        state.sections = snapshot.sections || [];
        ensureDefaults();
    }

    function pushHistory() {
        if (state.suspendHistory) {
            return;
        }
        state.history.push(clonePageState());
        if (state.history.length > 50) {
            state.history.shift();
        }
        state.future = [];
        syncHistoryButtons();
    }

    function syncHistoryButtons() {
        var undoButtons=qsa('#cms_builder_undo,#cms_builder_tool_undo'),redoButtons=qsa('#cms_builder_redo,#cms_builder_tool_redo');
        undoButtons.forEach(function(button){button.disabled=!state.history.length;button.setAttribute('aria-disabled',state.history.length?'false':'true');});
        redoButtons.forEach(function(button){button.disabled=!state.future.length;button.setAttribute('aria-disabled',state.future.length?'false':'true');});
    }

    function getDomPath(node) {
        if (!node || !node.parentElement) {
            return '';
        }
        var parts = [];
        var current = node;
        while (current && current.nodeType === 1 && current !== document.body) {
            var part = current.tagName.toLowerCase();
            if (current.id) {
                part += '#' + current.id;
                parts.unshift(part);
                break;
            }
            var index = 1;
            var sibling = current;
            while ((sibling = sibling.previousElementSibling)) {
                if (sibling.tagName === current.tagName) {
                    index++;
                }
            }
            part += ':nth-of-type(' + index + ')';
            parts.unshift(part);
            current = current.parentElement;
        }
        return parts.join(' > ');
    }

    function nodeStableKey(node) {
        if (!node || !(node instanceof Element)) {
            return '';
        }
        return node.getAttribute('data-builder-key') || '';
    }

    function nodeRef(node) {
        if (!node || !(node instanceof Element)) {
            return {path: '', key: ''};
        }
        return {
            path: getDomPath(node),
            key: nodeStableKey(node)
        };
    }

    function queryByStableKey(key) {
        if (!key) {
            return null;
        }
        return qs('[data-builder-key="' + cssEscape(key) + '"]');
    }

    function queryByRef(ref) {
        if (!ref) {
            return null;
        }
        if (typeof ref === 'string') {
            return queryByPath(ref);
        }
        if (ref.key) {
            var byKey = queryByStableKey(ref.key);
            if (byKey) {
                return byKey;
            }
        }
        if (ref.path) {
            return queryByPath(ref.path);
        }
        return null;
    }

    function refKey(ref) {
        if (!ref) {
            return '';
        }
        if (typeof ref === 'string') {
            return 'path:' + ref;
        }
        if (ref.key) {
            return 'key:' + ref.key;
        }
        if (ref.path) {
            return 'path:' + ref.path;
        }
        return '';
    }

    function refsEqual(a, b) {
        return refKey(a) !== '' && refKey(a) === refKey(b);
    }

    function normalizeStoredRef(value) {
        if (!value) {
            return {path: '', key: ''};
        }
        if (typeof value === 'string') {
            return {path: value, key: ''};
        }
        return {
            path: typeof value.path === 'string' ? value.path : '',
            key: typeof value.key === 'string' ? value.key : ''
        };
    }

    function cssEscape(value) {
        var text = String(value || '');
        var slash = String.fromCharCode(92);
        return text.split(slash).join(slash + slash).split('"').join(slash + '"');
    }

    function builderKeySegment(node) {
        if (!node || !(node instanceof Element) || !node.tagName) {
            return 'node';
        }
        var tag = node.tagName.toLowerCase();
        if (node.id) {
            return tag + '#id:' + node.id;
        }
        if (node.hasAttribute('data-builder-extra-id')) {
            return tag + '#extra:' + (node.getAttribute('data-builder-extra-id') || '');
        }
        if (node.hasAttribute('data-business-item-type') || node.hasAttribute('data-business-item-id')) {
            return tag + '#biz:' + (node.getAttribute('data-business-item-type') || '') + ':' + (node.getAttribute('data-business-item-id') || '');
        }
        var parent = node.parentElement;
        var sameTagIndex = 1;
        if (parent) {
            var sibling = node;
            while ((sibling = sibling.previousElementSibling)) {
                if (sibling.tagName === node.tagName) {
                    sameTagIndex++;
                }
            }
        }
        return tag + ':' + sameTagIndex;
    }

    function assignStableBuilderKeys() {
        var seed = 'page:' + String(boot.builder_type || 'page') + ':' + String(boot.builder_ref_id || 0);
        qsa('body *').forEach(function (node) {
            if (!node || !(node instanceof Element)) {
                return;
            }
            if (node.closest('#cms_frontend_builder_root,#cms_builder_overlay_layer,#cms_builder_page_widgets')) {
                return;
            }
            if (/^(script|style|link|meta|head|noscript)$/i.test((node.tagName || '').toLowerCase())) {
                return;
            }
            if (node.hasAttribute('data-builder-key')) {
                return;
            }
            var chain = [];
            var current = node;
            while (current && current instanceof Element && current !== document.body) {
                chain.unshift(builderKeySegment(current));
                if (current.id) {
                    break;
                }
                current = current.parentElement;
            }
            if (current === document.body) {
                chain.unshift('body');
            }
            node.setAttribute('data-builder-key', seed + '|' + chain.join('/'));
        });
    }

    function queryByPath(path) {
        if (!path) {
            return null;
        }
        try {
            return document.querySelector(path);
        } catch (error) {
            return null;
        }
    }

    function getExtraZone() {
        var zone = qs('#cms_builder_extra_zone');
        if (!zone) {
            zone = document.createElement('div');
            zone.id = 'cms_builder_extra_zone';
            zone.className = 'cms-builder-extra-zone';
            var anchor = qs('footer') || qs('main') || document.body;
            if (anchor && anchor.parentNode) {
                anchor.parentNode.insertBefore(zone, anchor);
            } else {
                document.body.appendChild(zone);
            }
        }
        return zone;
    }

    function renderExtraSections() {
        assignStableBuilderKeys();
        var zone = getExtraZone();
        qsa('.cms-builder-extra-item[data-builder-extra-id]').forEach(function (node) {
            if (node.parentNode) {
                node.parentNode.removeChild(node);
            }
        });
        zone.innerHTML = '';
        state.page.frontend_extra_sections.forEach(function (item) {
            var wrapper = document.createElement('section');
            wrapper.className = 'cms-builder-extra-item';
            wrapper.setAttribute('data-builder-extra-id', item.id || '');
            wrapper.setAttribute('data-builder-extra-type', item.type || '');
            wrapper.setAttribute('data-builder-extra-config', escapeHtml(JSON.stringify(item.config || {})));
            wrapper.innerHTML = item.html || '';
            var anchorPath = item.anchor_path || '';
            var anchorKey = item.anchor_key || '';
            var insertMode = item.insert_mode === 'before' ? 'before' : 'after';
            var anchorNode = anchorKey ? queryByStableKey(anchorKey) : null;
            if (!anchorNode && anchorPath) {
                anchorNode = queryByPath(anchorPath);
            }
            if (anchorNode && anchorNode.parentNode) {
                if (insertMode === 'before') {
                    anchorNode.parentNode.insertBefore(wrapper, anchorNode);
                } else {
                    anchorNode.parentNode.insertBefore(wrapper, anchorNode.nextSibling);
                }
            } else {
                zone.appendChild(wrapper);
            }
        });
    }

    function syncExtraSectionHtml(node) {
        var wrapper = node ? node.closest('[data-builder-extra-id]') : null;
        if (!wrapper) {
            return;
        }
        var extraId = wrapper.getAttribute('data-builder-extra-id');
        state.page.frontend_extra_sections = state.page.frontend_extra_sections.map(function (item) {
            if (String(item.id) === String(extraId)) {
                item.html = wrapper.innerHTML;
            }
            return item;
        });
    }

    function applyOverrides() {
        var overrides = currentOverrideBucket();
        Object.keys(overrides).forEach(function (bucketKey) {
            var ref = bucketKey.indexOf('key:') === 0
                ? {key: bucketKey.slice(4), path: ''}
                : (bucketKey.indexOf('path:') === 0 ? {path: bucketKey.slice(5), key: ''} : {path: bucketKey, key: ''});
            var node = queryByRef(ref);
            var data = overrides[bucketKey] || {};
            if (!node) {
                return;
            }
            if (Object.prototype.hasOwnProperty.call(data, 'text')) {
                node.textContent = data.text == null ? '' : String(data.text);
            }
            if (Object.prototype.hasOwnProperty.call(data, 'html')) {
                node.innerHTML = data.html == null ? '' : String(data.html);
            }
            if (Object.prototype.hasOwnProperty.call(data, 'src') && node.tagName.toLowerCase() === 'img') {
                node.setAttribute('src', data.src || '');
            }
            if (Object.prototype.hasOwnProperty.call(data, 'href') && node.tagName.toLowerCase() === 'a') {
                node.setAttribute('href', data.href || 'javascript:void(0)');
            }
            if (Object.prototype.hasOwnProperty.call(data, 'style')) {
                var nextStyle = sanitizeOverrideStyleForEdit(node, data.style || '');
                if (nextStyle) {
                    node.setAttribute('style', nextStyle);
                } else {
                    node.removeAttribute('style');
                }
                if (data.free_transform || nextStyle.indexOf('--cms-builder-free-transform') !== -1) {
                    node.classList.add('cms-builder-free-transform');
                } else {
                    node.classList.remove('cms-builder-free-transform');
                }
            }
        });
    }

    function applyVisibilityStates() {
        qsa('[data-builder-hidden="1"],[data-builder-hidden-preview="1"],[data-builder-deleted="1"]').forEach(function (node) {
            node.removeAttribute('data-builder-hidden');
            node.removeAttribute('data-builder-hidden-preview');
            node.removeAttribute('data-builder-deleted');
            node.classList.remove('cms-builder-block-hidden');
            node.classList.remove('cms-builder-block-hidden-preview');
            node.style.removeProperty('display');
            node.style.removeProperty('visibility');
            node.style.removeProperty('pointer-events');
        });
        currentHiddenPaths().forEach(function (item) {
            var node = queryByRef(normalizeStoredRef(item));
            if (!node) {
                return;
            }
            node.setAttribute('data-builder-hidden', '1');
            node.classList.add('cms-builder-block-hidden');
            if (boot.edit_mode) {
                node.setAttribute('data-builder-hidden-preview', '1');
                node.classList.add('cms-builder-block-hidden-preview');
                node.style.removeProperty('display');
                node.style.removeProperty('visibility');
                node.style.removeProperty('pointer-events');
            } else {
                node.style.setProperty('display', 'none', 'important');
            }
        });
        currentDeletedPaths().forEach(function (item) {
            var node = queryByRef(normalizeStoredRef(item));
            if (!node) {
                return;
            }
            node.setAttribute('data-builder-deleted', '1');
            node.style.setProperty('display', 'none', 'important');
        });
    }

    function applyLockedStates() {
        qsa('.cms-builder-block-locked').forEach(function (node) {
            node.classList.remove('cms-builder-block-locked');
        });
        (state.page.frontend_locked_paths || []).forEach(function (item) {
            var node = queryByRef(normalizeStoredRef(item));
            if (node) {
                node.classList.add('cms-builder-block-locked');
            }
        });
    }

    function applyBlockOrders() {
        (state.page.frontend_block_orders || []).forEach(function (entry) {
            if (!entry || !entry.parent || !Array.isArray(entry.paths)) {
                return;
            }
            var parent = queryByRef(normalizeStoredRef(entry.parent));
            if (!parent) {
                return;
            }
            entry.paths.forEach(function (item) {
                var child = queryByRef(normalizeStoredRef(item));
                if (child && child.parentNode === parent) {
                    parent.appendChild(child);
                }
            });
        });
    }

    function applyPageVisualSettings() {
        var style = qs('#' + PAGE_STYLE_ID);
        if (!style) {
            style = document.createElement('style');
            style.id = PAGE_STYLE_ID;
            document.head.appendChild(style);
        }
        var width = currentPreviewWidth();
        var padding = currentDevice() === 'mobile' ? parseInt(state.page.mobile_page_padding, 10) || 0 : parseInt(state.page.page_padding, 10) || 0;
        var layoutPreset = state.page.layout_preset || 'classic';
        var fontFamily = state.page.font_family || 'inherit';
        var themeColor = state.page.theme_color || '#5f7eeb';
        var backgroundMode = state.page.background_mode || 'default';
        var backgroundColor = state.page.background_color || '';
        var footerBackgroundColor = state.page.footer_background_color || '';
        var footerTextColor = state.page.footer_text_color || '';
        var footerTextAlign = state.page.footer_text_align || 'center';
        var footerPaddingTop = parseInt(state.page.footer_padding_top, 10);
        var footerPaddingBottom = parseInt(state.page.footer_padding_bottom, 10);
        var blockGap = layoutPreset === 'compact' ? 12 : (layoutPreset === 'wide' ? 28 : 18);
        var blockRadius = layoutPreset === 'compact' ? 12 : (layoutPreset === 'wide' ? 24 : 18);
        var blockShadow = layoutPreset === 'wide'
            ? '0 16px 34px rgba(15,23,42,.08)'
            : (layoutPreset === 'compact' ? '0 6px 18px rgba(15,23,42,.04)' : '0 10px 24px rgba(15,23,42,.06)');
        var containerSelectors = [
            '.w1200',
            '.wrap',
            'header .wrap',
            'footer .wrap',
            '.footer-columns',
            '.footer-contact',
            '#cms_builder_extra_zone'
        ].join(',');
        var blockSelectors = [
            'main > *',
            'section',
            'article',
            '.k740-banner',
            '.k740-search',
            '.productind',
            '.odds',
            '.about-k740',
            '.qa-news',
            '#cms_builder_extra_zone > *'
        ].join(',');
        var bodyBackground = 'transparent';
        if (backgroundMode === 'default') {
            bodyBackground = state.page.theme_surface === 'soft' ? 'linear-gradient(180deg, rgba(255,255,255,.98), rgba(95,126,235,.05))' : 'transparent';
        } else if (backgroundMode === 'custom' && backgroundColor) {
            bodyBackground = backgroundColor;
        }
        var css = ':root{--cms-builder-theme:' + themeColor + ';}';
        css += 'body{background:' + bodyBackground + ';font-family:' + fontFamily + ';padding-left:' + padding + 'px;padding-right:' + padding + 'px;}';
        css += containerSelectors + '{max-width:' + width + 'px;width:100%;margin-left:auto;margin-right:auto;}';
        css += blockSelectors + '{margin-left:auto;margin-right:auto;transition:max-width .2s ease, margin .2s ease, border-radius .2s ease, box-shadow .2s ease, padding .2s ease;}';
        css += 'main > * + *,#cms_builder_extra_zone > * + *{margin-top:' + blockGap + 'px;}';
        css += 'body[data-builder-layout-preset="compact"] ' + blockSelectors + '{border-radius:' + blockRadius + 'px;}';
        css += 'body[data-builder-layout-preset="classic"] ' + blockSelectors + '{border-radius:' + blockRadius + 'px;}';
        css += 'body[data-builder-layout-preset="wide"] ' + blockSelectors + '{border-radius:' + blockRadius + 'px;}';
        css += 'body[data-builder-layout-preset="compact"] main > *,body[data-builder-layout-preset="compact"] #cms_builder_extra_zone > *{padding-left:0;padding-right:0;}';
        css += 'body[data-builder-layout-preset="wide"] main > *,body[data-builder-layout-preset="wide"] #cms_builder_extra_zone > *{box-shadow:' + blockShadow + ';}';
        css += 'body[data-builder-layout-preset="classic"] main > *,body[data-builder-layout-preset="classic"] #cms_builder_extra_zone > *{box-shadow:' + blockShadow + ';}';
        css += 'body[data-builder-layout-preset="compact"] .builder-card,body[data-builder-layout-preset="classic"] .builder-card,body[data-builder-layout-preset="wide"] .builder-card{border-radius:' + blockRadius + 'px;box-shadow:' + blockShadow + ';overflow:hidden;}';
        css += '.cms-builder-hover-edit:hover,.cms-builder-library-tab.is-active,.cms-builder-style-tab.is-active{color:' + themeColor + ';}';
        css += '.cms-builder-library-tab.is-active:after,.cms-builder-style-tab.is-active:after,.cms-builder-btn-primary{background:' + themeColor + ';}';
        css += '.cms-builder-style-pill.is-active{border-color:' + themeColor + ';color:' + themeColor + ';background:rgba(255,255,255,.92);}';
        css += '.cms-builder-style-quick-btn:hover{border-color:' + themeColor + ';color:' + themeColor + ';}';
        css += '.builder-card .builder-title,.builder-extra-button a,.builder-extra-text h3{color:inherit;}';
        css += '.builder-extra-button a{background:' + themeColor + ';}';
        css += 'footer{' + (footerBackgroundColor ? 'background:' + footerBackgroundColor + ';' : '') + (footerTextColor ? 'color:' + footerTextColor + ';' : '') + 'text-align:' + footerTextAlign + ';padding-top:' + (isNaN(footerPaddingTop) ? 20 : footerPaddingTop) + 'px;padding-bottom:' + (isNaN(footerPaddingBottom) ? 20 : footerPaddingBottom) + 'px;}';
        style.textContent = css;

        document.body.setAttribute('data-builder-layout-preset', layoutPreset);
        document.body.classList.toggle('cms-builder-device-mobile', currentDevice() === 'mobile');
        document.body.classList.toggle('cms-builder-device-tablet', currentDevice() === 'tablet');
        if (isShellFrameMode()) {
            document.documentElement.classList.add('cms-builder-shell-frame');
        }
        document.documentElement.style.removeProperty('--cms-builder-mobile-width');
        document.body.style.maxWidth = '';
        document.body.style.width = '';
    }

    function applyPageHeadSettings() {
        var titleText = state.page.title || boot.page_title || document.title || '';
        if (state.page.browser_title_visible) {
            document.title = titleText;
        } else if (boot.page_title) {
            document.title = boot.page_title;
        }
        var faviconHref = state.page.favicon_url || '';
        var favicon = qs('#cms_builder_favicon');
        if (!favicon) {
            favicon = document.createElement('link');
            favicon.id = 'cms_builder_favicon';
            favicon.rel = 'icon';
            document.head.appendChild(favicon);
        }
        if (faviconHref) {
            favicon.href = faviconHref;
        }
    }

    function normalizedNodeText(node) {
        return String(node && (node.innerText || node.textContent || '') || '').replace(/\s+/g, ' ').trim();
    }

    function firstLinkByKeywords(keywords) {
        var found = '';
        qsa('a[href]').some(function (link) {
            var href = link.getAttribute('href') || '';
            var text = normalizedNodeText(link).toLowerCase();
            if (!href || href === 'javascript:void(0)' || href === '#') {
                return false;
            }
            var matched = keywords.some(function (keyword) {
                return text.indexOf(String(keyword).toLowerCase()) !== -1;
            });
            if (matched) {
                found = href;
                return true;
            }
            return false;
        });
        return found;
    }

    function siteHomeUrl() {
        return firstLinkByKeywords(['首页', 'home']) || '/';
    }

    function siteProductUrl() {
        return firstLinkByKeywords(['产品', 'product']) || siteHomeUrl();
    }

    function siteContactUrl() {
        return firstLinkByKeywords(['联系', '联系我们', 'contact']) || '#cms_builder_footer_meta';
    }

    function siteGuestbookUrl() {
        return firstLinkByKeywords(['留言', '咨询', '反馈', 'guestbook']) || siteContactUrl();
    }

    function currentPageVisitCount() {
        if (state.pageVisitCount > 0) {
            return state.pageVisitCount;
        }
        try {
            var key = 'cms_builder_page_counter:' + window.location.pathname + window.location.search;
            var count = parseInt(window.localStorage.getItem(key) || '0', 10);
            count = isNaN(count) ? 1 : (count + 1);
            window.localStorage.setItem(key, String(count));
            state.pageVisitCount = count;
            return count;
        } catch (error) {
            state.pageVisitCount = 1;
            return 1;
        }
    }

    function footerMetaMountNode() {
        var footer = qs('footer');
        if (!footer) {
            return null;
        }
        var existing = qs('#cms_builder_footer_meta', footer);
        if (existing) {
            return existing;
        }
        var node = document.createElement('div');
        node.id = 'cms_builder_footer_meta';
        node.style.cssText = 'margin-top:14px;padding:10px 14px;border-radius:12px;background:rgba(255,255,255,.06);font:500 12px/1.8 "Microsoft YaHei",sans-serif;color:inherit;';
        footer.appendChild(node);
        return node;
    }

    function advancedManagedNodes(kind) {
        var footer = qs('footer');
        if (!footer) {
            return [];
        }
        var matches = [];
        qsa('*', footer).forEach(function (node) {
            if (!(node instanceof Element) || node.id === 'cms_builder_footer_meta' || node.closest('#cms_builder_footer_meta') || node.children.length > 8) {
                return;
            }
            var text = normalizedNodeText(node).toLowerCase();
            var href = (node.getAttribute('href') || '').toLowerCase();
            var className = typeof node.className === 'string' ? node.className.toLowerCase() : '';
            var matched = false;
            if (kind === 'production_info') {
                matched = href.indexOf('yutoucms') !== -1 || text.indexOf('芋头cms') !== -1 || text.indexOf('技术支持') !== -1 || text.indexOf('powered by') !== -1;
            } else if (kind === 'icp') {
                matched = href.indexOf('beian.miit.gov.cn') !== -1 || /ICP备|备案号|icp/.test(text);
            } else if (kind === 'public_security') {
                matched = className.indexOf('police-icp') !== -1 || /公网安备|公安备案|公安部备案/.test(text);
            }
            if (matched) {
                matches.push(node);
            }
        });
        return matches;
    }

    function setManagedNodeVisibility(nodes, visible, key) {
        (nodes || []).forEach(function (node) {
            if (!(node instanceof Element)) {
                return;
            }
            if (visible) {
                node.removeAttribute('data-builder-advanced-hidden');
                node.style.removeProperty('display');
            } else {
                node.setAttribute('data-builder-advanced-hidden', key);
                node.style.setProperty('display', 'none', 'important');
            }
        });
    }

    function renderAdvancedFooterMeta() {
        var footer = qs('footer');
        if (!footer) {
            return;
        }
        var advanced = state.page.advanced_options || defaultAdvancedOptions();
        var contact = state.businessOptions.site_contact || {};
        setManagedNodeVisibility(advancedManagedNodes('production_info'), !!advanced.production_info, 'production_info');
        setManagedNodeVisibility(advancedManagedNodes('icp'), !!advanced.icp, 'icp');
        setManagedNodeVisibility(advancedManagedNodes('public_security'), !!advanced.public_security, 'public_security');

        var mount = footerMetaMountNode();
        if (!mount) {
            return;
        }

        var metaParts = [];
        if (advanced.icp && contact.site_icp && !advancedManagedNodes('icp').length) {
            metaParts.push('<a href="https://beian.miit.gov.cn/" target="_blank" rel="nofollow" style="color:inherit;text-decoration:none;">' + escapeHtml(contact.site_icp) + '</a>');
        }
        if (advanced.public_security && contact.site_police_icp && !advancedManagedNodes('public_security').length) {
            metaParts.push('<span>' + escapeHtml(contact.site_police_icp) + '</span>');
        }
        if (advanced.production_info && !advancedManagedNodes('production_info').length) {
            metaParts.push('<a href="https://www.yutoucms.cn/" target="_blank" rel="nofollow" style="color:inherit;text-decoration:none;">芋头CMS 专业版授权支持</a>');
        }
        mount.innerHTML = metaParts.length ? metaParts.join('<span style="margin:0 10px;opacity:.55;">|</span>') : '';
        mount.style.display = metaParts.length ? '' : 'none';
    }

    function currentPublicViewMode() {
        return state.publicMobilePreview ? 'mobile' : 'desktop';
    }

    function applyPublicMobilePreview() {
        if (currentPublicViewMode() === 'mobile') {
            document.body.classList.add('cms-builder-device-mobile');
        } else if (currentDevice() !== 'mobile') {
            document.body.classList.remove('cms-builder-device-mobile');
        }
        document.documentElement.style.removeProperty('--cms-builder-mobile-width');
        document.body.style.maxWidth = '';
        document.body.style.width = '';
        var toggle = qs('[data-widget-mobile-toggle]');
        if (toggle) {
            toggle.textContent = currentPublicViewMode() === 'mobile' ? '切换电脑版' : '切换手机版';
        }
    }

    function togglePublicMobilePreview() {
        state.publicMobilePreview = !state.publicMobilePreview;
        applyPublicMobilePreview();
        setStatus(state.publicMobilePreview ? '已切换到手机版预览' : '已切换回电脑版预览');
    }

    function scrollToWidgetTarget(target) {
        var node = null;
        if (target === 'top') {
            window.scrollTo({top: 0, behavior: 'smooth'});
            return;
        }
        if (target === 'contact') {
            node = qsa('section,article,div,footer').find(function (item) {
                var text = normalizedNodeText(item);
                return /联系|联系我们|电话|客服/.test(text);
            }) || qs('footer');
        } else if (target === 'footer') {
            node = qs('footer');
        }
        if (node) {
            centerNodeInViewport(node);
        }
    }

    function resolveMusicMedia() {
        return qs('audio') || qs('video');
    }

    function toggleWidgetMusic() {
        var media = resolveMusicMedia();
        var button = qs('[data-widget-music-toggle]');
        if (!media) {
            setStatus('当前页面没有可控制的音频或视频资源。');
            if (button) {
                button.textContent = '无音源';
            }
            return;
        }
        if (media.paused) {
            var playPromise = media.play();
            if (playPromise && typeof playPromise.then === 'function') {
                playPromise.then(function () {
                    if (button) {
                        button.textContent = '暂停音乐';
                    }
                }).catch(function () {
                    setStatus('浏览器阻止了自动播放，请先与页面交互后再试。');
                });
            } else if (button) {
                button.textContent = '暂停音乐';
            }
        } else {
            media.pause();
            if (button) {
                button.textContent = '播放音乐';
            }
        }
    }

    function buildSettingsWidgetPreviewHtml(key) {
        if (key === 'floating_service') {
            return '<div style="display:flex;justify-content:flex-end;"><div style="display:grid;gap:10px;"><div style="padding:12px 16px;border-radius:16px;background:#111827;color:#fff;">在线咨询</div><div style="padding:12px 16px;border-radius:16px;background:#fff;border:1px solid #e2e8f0;">电话客服</div></div></div>';
        }
        if (key === 'site_sidebar') {
            return '<div style="display:flex;gap:12px;"><div style="width:92px;display:grid;gap:10px;"><div style="padding:10px;border-radius:12px;background:#eef2ff;color:#4664df;text-align:center;">首页</div><div style="padding:10px;border-radius:12px;background:#fff;border:1px solid #e2e8f0;text-align:center;">产品</div><div style="padding:10px;border-radius:12px;background:#fff;border:1px solid #e2e8f0;text-align:center;">联系</div></div><div style="flex:1;min-height:140px;border:1px dashed #cbd5e1;border-radius:16px;background:#fff;"></div></div>';
        }
        if (key === 'top_ad') {
            return '<div style="padding:14px 16px;border-radius:16px;background:linear-gradient(135deg,#111827,#334155);color:#fff;text-align:center;font-weight:700;">新品上线，限时领取装修顾问服务</div>';
        }
        if (key === 'bottom_float') {
            return '<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;padding:16px 18px;border-radius:18px;background:#111827;color:#fff;"><div><div style="font-size:18px;font-weight:800;">立即获取专属方案</div><div style="margin-top:8px;color:rgba(255,255,255,.72);">底部浮层更适合转化按钮与咨询入口。</div></div><button type="button" style="border:0;border-radius:999px;padding:10px 16px;background:#fff;color:#111827;font-weight:700;">立即咨询</button></div>';
        }
        if (key === 'member_bar') {
            return '<div style="display:flex;justify-content:flex-end;gap:10px;"><span style="padding:10px 16px;border-radius:999px;background:#111827;color:#fff;">会员登录</span><span style="padding:10px 16px;border-radius:999px;border:1px solid #dbe3f4;background:#fff;">立即注册</span></div>';
        }
        if (key === 'home_popup') {
            return '<div style="padding:26px;border-radius:20px;background:#fff;border:1px solid #e2e8f0;box-shadow:0 18px 40px rgba(15,23,42,.08);text-align:center;"><div style="font-size:28px;font-weight:800;color:#0f172a;">欢迎访问企业官网</div><div style="margin-top:12px;color:#64748b;line-height:1.9;">首页弹窗适合新品活动、限时优惠和线索收集。</div><button type="button" style="margin-top:18px;border:0;border-radius:999px;padding:10px 18px;background:#111827;color:#fff;">立即查看</button></div>';
        }
        if (key === 'elevator_nav') {
            return '<div style="display:flex;gap:14px;"><div style="width:88px;display:grid;gap:8px;"><div style="padding:10px;border-radius:12px;background:#111827;color:#fff;text-align:center;">01</div><div style="padding:10px;border-radius:12px;background:#fff;border:1px solid #e2e8f0;text-align:center;">02</div><div style="padding:10px;border-radius:12px;background:#fff;border:1px solid #e2e8f0;text-align:center;">03</div></div><div style="flex:1;min-height:140px;border-radius:16px;background:linear-gradient(180deg,#fff,#f8fbff);border:1px dashed #cbd5e1;"></div></div>';
        }
        if (key === 'welcome_screen') {
            return '<div style="padding:32px;border-radius:22px;background:linear-gradient(135deg,#0f172a,#1d4ed8);color:#fff;text-align:center;"><div style="font-size:34px;font-weight:800;">WELCOME</div><div style="margin-top:12px;color:rgba(255,255,255,.76);line-height:1.9;">欢迎页面适合品牌开场和节日主题展示。</div></div>';
        }
        if (key === 'counter') {
            return '<div style="display:inline-flex;align-items:center;gap:12px;padding:14px 16px;border-radius:16px;background:#fff;border:1px solid #e2e8f0;"><strong style="font-size:24px;color:#111827;">1024</strong><span style="color:#64748b;">今日访问量</span></div>';
        }
        if (key === 'background_music') {
            return '<div style="display:flex;align-items:center;gap:12px;padding:14px 16px;border-radius:16px;background:#fff;border:1px solid #e2e8f0;"><span style="width:42px;height:42px;border-radius:999px;background:#111827;color:#fff;display:inline-flex;align-items:center;justify-content:center;">♪</span><div><div style="font-weight:700;color:#111827;">背景音乐开关</div><div style="margin-top:6px;color:#64748b;">可作为页面氛围入口使用。</div></div></div>';
        }
        return '<div style="padding:22px;border-radius:18px;background:#fff;border:1px solid #e2e8f0;">' + escapeHtml(widgetLabel(key)) + ' 预览</div>';
    }

    function renderSettingsWidgetPreviewModal() {
        var key = state.settingsWidgetPreviewKey || '';
        if (!key) {
            return '';
        }
        return '<div class="cms-builder-material-modal" id="cms_builder_settings_widget_modal">' +
            '<div class="cms-builder-material-modal-card cms-builder-no-copy">' +
                '<div class="cms-builder-material-modal-head"><div><div class="cms-builder-material-modal-title">' + escapeHtml(widgetLabel(key)) + '预览</div></div><button type="button" class="cms-builder-panel-tool" data-widget-preview-close="1">关闭</button></div>' +
                '<div class="cms-builder-material-modal-body"><div class="cms-builder-material-modal-preview">' + buildSettingsWidgetPreviewHtml(key) + '</div></div>' +
                '<div class="cms-builder-material-modal-actions"><button type="button" class="cms-builder-btn cms-builder-btn-light" data-widget-preview-close="1">关闭预览</button></div>' +
            '</div>' +
        '</div>';
    }

    function renderEnabledPageWidgets() {
        var widgets = state.page.site_widgets || defaultSiteWidgets();
        var advanced = state.page.advanced_options || defaultAdvancedOptions();
        var widgetConfigs = getSiteWidgetConfigs();
        var contact = state.businessOptions.site_contact || {};
        var memberState = state.businessOptions.member_state || {};
        var phone = widgetConfigText('floating_service', 'phone', contact.site_phone || contact.site_tel || '');
        var qq = widgetConfigText('floating_service', 'qq', contact.site_qq || '');
        var wechat = widgetConfigText('floating_service', 'wechat', contact.site_wechat || '');
        var email = widgetConfigText('floating_service', 'email', contact.site_email || '');
        var guestbookUrl = widgetConfigText('floating_service', 'guestbook_url', siteGuestbookUrl());
        var productUrl = widgetConfigText('site_sidebar', 'product_url', widgetConfigText('elevator_nav', 'product_url', siteProductUrl()));
        var contactLabel = widgetConfigText('site_sidebar', 'contact_label', widgetConfigText('elevator_nav', 'contact_label', '联系'));
        var siteName = contact.site_name || builderTitle();
        var visitCount = currentPageVisitCount();
        var parts = [];
        var topOffset = boot.edit_mode ? 74 : 14;
        if (widgets.top_ad) {
            parts.push('<a href="' + escapeHtml(widgetConfigText('top_ad', 'link_url', guestbookUrl)) + '" style="position:fixed;left:50%;top:' + topOffset + 'px;transform:translateX(-50%);z-index:8200;padding:10px 18px;border-radius:999px;background:#111827;color:#fff;box-shadow:0 16px 36px rgba(15,23,42,.2);text-decoration:none;">' + escapeHtml(widgetConfigText('top_ad', 'text', '网站顶部广告：立即领取专属咨询通道')) + '</a>');
        }
        if (widgets.member_bar) {
            if (memberState.logged_in) {
                parts.push('<div style="position:fixed;right:24px;top:' + (topOffset + 42) + 'px;z-index:8198;display:flex;gap:8px;"><a href="' + escapeHtml(memberState.center_url || '/member/index.php') + '" style="padding:10px 14px;border-radius:999px;background:#111827;color:#fff;text-decoration:none;">会员中心</a><a href="' + escapeHtml(memberState.logout_url || '/member/logout.php') + '" style="padding:10px 14px;border-radius:999px;background:#fff;border:1px solid #dbe3f4;color:#111827;text-decoration:none;">退出登录</a></div>');
            } else {
                parts.push('<div style="position:fixed;right:24px;top:' + (topOffset + 42) + 'px;z-index:8198;display:flex;gap:8px;"><a href="' + escapeHtml(memberState.login_url || '/member/login.php') + '" style="padding:10px 14px;border-radius:999px;background:#111827;color:#fff;text-decoration:none;">' + escapeHtml(widgetConfigText('member_bar', 'login_text', '会员登录')) + '</a><a href="' + escapeHtml(memberState.register_url || '/member/register.php') + '" style="padding:10px 14px;border-radius:999px;background:#fff;border:1px solid #dbe3f4;color:#111827;text-decoration:none;">' + escapeHtml(widgetConfigText('member_bar', 'register_text', '立即注册')) + '</a></div>');
            }
        }
        if (widgets.floating_service) {
            var serviceItems = [];
            serviceItems.push('<a href="' + escapeHtml(guestbookUrl) + '" style="border:0;border-radius:14px;padding:12px 14px;background:#111827;color:#fff;box-shadow:0 14px 30px rgba(15,23,42,.18);text-decoration:none;text-align:center;">在线咨询</a>');
            if (phone) {
                serviceItems.push('<a href="tel:' + escapeHtml(phone) + '" style="border:1px solid #dbe3f4;border-radius:14px;padding:12px 14px;background:#fff;color:#111827;text-decoration:none;text-align:center;">电话客服</a>');
            }
            if (qq) {
                serviceItems.push('<a href="https://wpa.qq.com/msgrd?v=3&uin=' + escapeHtml(qq) + '&site=qq&menu=yes" target="_blank" style="border:1px solid #dbe3f4;border-radius:14px;padding:12px 14px;background:#fff;color:#111827;text-decoration:none;text-align:center;">QQ咨询</a>');
            }
            if (wechat) {
                serviceItems.push('<button type="button" data-widget-popup-open="wechat_service" style="border:1px solid #dbe3f4;border-radius:14px;padding:12px 14px;background:#fff;color:#111827;">微信客服</button>');
            }
            parts.push('<div style="position:fixed;right:18px;top:50%;transform:translateY(-50%);z-index:8196;display:grid;gap:10px;">' + serviceItems.join('') + '</div>');
        }
        if (widgets.site_sidebar || widgets.elevator_nav) {
            parts.push('<div style="position:fixed;left:18px;top:50%;transform:translateY(-50%);z-index:8194;display:grid;gap:8px;">' +
                '<button type="button" data-widget-scroll="top" style="border:0;border-radius:12px;padding:10px 12px;background:#111827;color:#fff;">回顶部</button>' +
                '<a href="' + escapeHtml(productUrl) + '" style="border:1px solid #dbe3f4;border-radius:12px;padding:10px 12px;background:#fff;color:#111827;text-decoration:none;text-align:center;">产品</a>' +
                '<button type="button" data-widget-scroll="contact" style="border:1px solid #dbe3f4;border-radius:12px;padding:10px 12px;background:#fff;color:#111827;">' + escapeHtml(contactLabel) + '</button>' +
            '</div>');
        }
        if (widgets.bottom_float) {
            var bottomButtonUrl = widgetConfigText('bottom_float', 'button_url', '');
            var bottomButtonText = widgetConfigText('bottom_float', 'button_text', phone ? '电话咨询' : '立即咨询');
            var bottomButtonHtml = '';
            if (bottomButtonUrl !== '') {
                bottomButtonHtml = '<a href="' + escapeHtml(bottomButtonUrl) + '" style="border:0;border-radius:999px;padding:10px 16px;background:#fff;color:#111827;font-weight:700;text-decoration:none;">' + escapeHtml(bottomButtonText) + '</a>';
            } else if (phone) {
                bottomButtonHtml = '<a href="tel:' + escapeHtml(phone) + '" style="border:0;border-radius:999px;padding:10px 16px;background:#fff;color:#111827;font-weight:700;text-decoration:none;">' + escapeHtml(bottomButtonText) + '</a>';
            } else {
                bottomButtonHtml = '<button type="button" data-widget-scroll="contact" style="border:0;border-radius:999px;padding:10px 16px;background:#fff;color:#111827;font-weight:700;">' + escapeHtml(bottomButtonText) + '</button>';
            }
            parts.push('<div style="position:fixed;left:50%;bottom:18px;transform:translateX(-50%);z-index:8192;display:flex;align-items:center;gap:14px;min-width:min(640px,calc(100vw - 40px));justify-content:space-between;padding:14px 18px;border-radius:18px;background:#111827;color:#fff;box-shadow:0 20px 40px rgba(15,23,42,.24);"><span>' + escapeHtml(siteName) + '：' + escapeHtml(widgetConfigText('bottom_float', 'text', '立即获取专属方案')) + '</span>' + bottomButtonHtml + '</div>');
        }
        if (widgets.counter) {
            parts.push('<div style="position:fixed;left:18px;bottom:18px;z-index:8190;padding:12px 14px;border-radius:16px;background:#fff;border:1px solid #e2e8f0;box-shadow:0 12px 30px rgba(15,23,42,.1);"><strong style="font-size:22px;color:#111827;">' + escapeHtml(visitCount) + '</strong><span style="margin-left:8px;color:#64748b;">' + escapeHtml(widgetConfigText('counter', 'label', '本页访问')) + '</span></div>');
        }
        if (widgets.background_music) {
            var audioUrl = widgetConfigText('background_music', 'audio_url', '');
            if (audioUrl !== '') {
                parts.push('<audio data-widget-music-audio="1"' + (widgetConfigBool('background_music', 'autoplay') ? ' autoplay' : '') + ' preload="none" src="' + escapeHtml(audioUrl) + '" style="display:none;"></audio>');
            }
            parts.push('<button type="button" data-widget-music-toggle="1" style="position:fixed;right:20px;bottom:92px;z-index:8188;padding:0 14px;height:48px;border:0;border-radius:999px;background:#111827;color:#fff;box-shadow:0 16px 32px rgba(15,23,42,.2);">' + escapeHtml(widgetConfigText('background_music', 'button_text', '播放音乐')) + '</button>');
        }
        if (widgets.home_popup) {
            parts.push('<div data-widget-popup="home_popup" style="position:fixed;inset:0;z-index:8186;background:rgba(15,23,42,.42);display:flex;align-items:center;justify-content:center;padding:24px;"><div style="position:relative;width:min(460px,calc(100vw - 32px));padding:28px;border-radius:22px;background:#fff;box-shadow:0 28px 80px rgba(15,23,42,.28);text-align:center;"><button type="button" data-widget-dismiss="home_popup" style="position:absolute;right:14px;top:14px;border:0;border-radius:999px;width:30px;height:30px;background:#eef2f7;color:#475569;">×</button><div style="font-size:30px;font-weight:800;color:#111827;">' + escapeHtml(widgetConfigText('home_popup', 'title', '首页弹窗')) + '</div><div style="margin-top:12px;color:#64748b;line-height:1.9;">' + escapeHtml(widgetConfigText('home_popup', 'content', '这里可以展示活动信息、优惠券或者首页欢迎文案。')) + '</div><a href="' + escapeHtml(widgetConfigText('home_popup', 'button_url', guestbookUrl)) + '" style="display:inline-flex;margin-top:18px;border:0;border-radius:999px;padding:10px 18px;background:#111827;color:#fff;text-decoration:none;">' + escapeHtml(widgetConfigText('home_popup', 'button_text', '立即查看')) + '</a></div></div>');
        }
        if (widgets.welcome_screen) {
            parts.push('<div data-widget-popup="welcome_screen" style="position:fixed;inset:0;z-index:8184;background:linear-gradient(135deg,#0f172a,#1d4ed8);display:flex;align-items:center;justify-content:center;padding:24px;color:#fff;"><div style="text-align:center;"><div style="font-size:42px;font-weight:900;letter-spacing:4px;">' + escapeHtml(widgetConfigText('welcome_screen', 'title', 'WELCOME')) + '</div><div style="margin-top:14px;color:rgba(255,255,255,.76);line-height:1.9;">' + escapeHtml(widgetConfigText('welcome_screen', 'content', '欢迎页已开启，适合活动场景或品牌开场。')) + '</div><button type="button" data-widget-dismiss="welcome_screen" style="margin-top:22px;border:0;border-radius:999px;padding:10px 20px;background:#fff;color:#111827;font-weight:700;">' + escapeHtml(widgetConfigText('welcome_screen', 'button_text', '进入网站')) + '</button></div></div>');
        }
        if (false && advanced.admin_login) {
            parts.push('<a href="/admin/login.php" style="position:fixed;right:20px;bottom:20px;z-index:8182;padding:10px 16px;border-radius:999px;background:#fff;border:1px solid #dbe3f4;color:#111827;box-shadow:0 12px 30px rgba(15,23,42,.1);text-decoration:none;">管理员登录</a>');
        }
        if (false && advanced.mobile_site) {
            parts.push('<button type="button" data-widget-mobile-toggle="1" style="position:fixed;right:20px;bottom:' + (advanced.admin_login ? '74' : '20') + 'px;z-index:8181;padding:10px 16px;border-radius:999px;background:#fff;border:1px solid #dbe3f4;color:#111827;box-shadow:0 12px 30px rgba(15,23,42,.1);">切换手机版</button>');
        }
        if (wechat) {
            parts.push('<div data-widget-popup="wechat_service" style="display:none;position:fixed;inset:0;z-index:8183;background:rgba(15,23,42,.42);align-items:center;justify-content:center;padding:24px;"><div style="position:relative;width:min(360px,calc(100vw - 32px));padding:26px;border-radius:20px;background:#fff;box-shadow:0 28px 80px rgba(15,23,42,.28);text-align:center;"><button type="button" data-widget-dismiss="wechat_service" style="position:absolute;right:14px;top:14px;border:0;border-radius:999px;width:30px;height:30px;background:#eef2f7;color:#475569;">×</button><div style="font-size:24px;font-weight:800;color:#111827;">微信客服</div><div style="margin-top:12px;color:#64748b;line-height:1.9;">请添加微信号联系企业客服</div><div style="margin-top:18px;padding:14px 16px;border-radius:14px;background:#f8fafc;font-size:18px;font-weight:800;color:#111827;">' + escapeHtml(wechat) + '</div>' + (email ? '<a href="mailto:' + escapeHtml(email) + '" style="display:inline-flex;margin-top:16px;color:#2563eb;text-decoration:none;">发送邮件</a>' : '') + '</div></div>');
        }
        return parts.join('');
    }

    function ensurePageWidgetsLayer() {
        var layer = qs('#cms_builder_page_widgets');
        if (!layer) {
            layer = document.createElement('div');
            layer.id = 'cms_builder_page_widgets';
            document.body.appendChild(layer);
        }
        layer.innerHTML = renderEnabledPageWidgets();
        renderAdvancedFooterMeta();
        applyPublicMobilePreview();
    }

    function blockSelectors() {
        return [
            'header',
            '.topBar',
            'main > *',
            'main > div',
            'main > section',
            'main > article',
            '[class*="section"]',
            '[class*="module"]',
            '[class*="block"]',
            '[class*="wrap"]',
            '[class*="container"]',
            '[class*="panel"]',
            '[class*="box"]',
            'section',
            'article',
            '.w1200',
            '.k740-banner',
            '.k740-search',
            '.productind',
            '.odds',
            '.about-k740',
            '.qa-news',
            'footer',
            '#cms_builder_extra_zone > *'
        ].join(',');
    }

    function editableSelectors() {
        return [
            '[data-builder-extra-id]',
            '[data-business-item-type]',
            'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
            'p', 'a', 'img', 'button', 'li', 'strong', 'em', 'b',
            'section', 'article', 'div', 'span'
        ].join(',');
    }

    function blockCandidateSelectors() {
        return [
            'main > *',
            'body > main > *',
            '.container > *',
            '.wrapper > *',
            '.content > *',
            '.main > *',
            '[class*="section"]',
            '[class*="module"]',
            '[class*="block"]',
            '[class*="panel"]',
            '[class*="box"]'
        ].join(',');
    }

    function textEditableSelectors() {
        return [
            'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
            'p', 'a', 'button', 'li', 'span', 'strong', 'em', 'b'
        ].join(',');
    }

    function isInlineEditableNode(node) {
        if (!node || !(node instanceof Element) || !node.tagName) {
            return false;
        }
        if (node.closest('#cms_frontend_builder_root,#cms_builder_page_widgets,#cms_builder_overlay_layer')) {
            return false;
        }
        var tag = node.tagName.toLowerCase();
        if (/^(script|style|link|meta|head|noscript|br|html|body|main)$/i.test(tag)) {
            return false;
        }
        var rect = node.getBoundingClientRect();
        if (!rect || rect.width < 8 || rect.height < 8) {
            return false;
        }
        if (tag === 'img' || tag === 'a' || tag === 'button') {
            return true;
        }
        if (/^(h1|h2|h3|h4|h5|h6|p|li|span|strong|em|b)$/i.test(tag)) {
            return normalizedNodeText(node) !== '';
        }
        return false;
    }

    function isSimpleTextNode(node) {
        if (!node || !(node instanceof Element) || !node.tagName) {
            return false;
        }
        var tag = node.tagName.toLowerCase();
        if (tag !== 'div' && tag !== 'span') {
            return false;
        }
        if (node.hasAttribute('data-builder-extra-id') || node.hasAttribute('data-builder-block-path')) {
            return false;
        }
        if ((node.children || []).length > 0) {
            return false;
        }
        return ((node.innerText || node.textContent || '').replace(/\s+/g, ' ').trim()) !== '';
    }

    function resolveActionBlockNode(node) {
        if (!node || !(node instanceof Element)) {
            return null;
        }
        if (node.hasAttribute('data-builder-extra-id')) {
            return node;
        }
        var granular = isInlineEditableNode(node) || isSimpleTextNode(node);
        if (!granular && !isBuilderUnsafeRootNode(node)) {
            return node;
        }
        var current = node.parentElement;
        while (current && current instanceof Element && current !== document.body) {
            if (current.hasAttribute('data-builder-extra-id')) {
                return current;
            }
            if (!isBuilderUnsafeRootNode(current) && isBlockCandidateNode(current) && !isInlineEditableNode(current) && !isSimpleTextNode(current)) {
                return current;
            }
            current = current.parentElement;
        }
        return node;
    }

    function collectBlocks() {
        var seen = {};
        var nodes = qsa(blockSelectors());
        qsa(editableSelectors()).forEach(function (node) {
            if (nodes.indexOf(node) === -1) {
                nodes.push(node);
            }
        });
        qsa(blockCandidateSelectors()).forEach(function (node) {
            if (nodes.indexOf(node) === -1) {
                nodes.push(node);
            }
        });
        state.blocks = nodes.filter(function (node) {
            if (!node || !node.tagName) {
                return false;
            }
            if (node.closest('#cms_frontend_builder_root,#cms_builder_page_widgets,#cms_builder_overlay_layer')) {
                return false;
            }
            if (isProtectedEditableNode(node)) {
                return false;
            }
            var path = getDomPath(node);
            if (!path || seen[path] || listHasRef(currentDeletedPaths(), nodeRef(node))) {
                return false;
            }
            if (!isBlockCandidateNode(node) && !isInlineEditableNode(node)) {
                return false;
            }
            seen[path] = true;
            node.setAttribute('data-builder-block-path', path);
            node.setAttribute('draggable', 'false');
            return true;
        }).map(function (node, index) {
            var path = getDomPath(node);
            var key = nodeStableKey(node);
            var text = (node.innerText || node.textContent || '').replace(/\s+/g, ' ').trim();
            return {
                id: index + 1,
                path: path,
                key: key,
                tag: node.tagName.toLowerCase(),
                label: text ? text.slice(0, 44) : (node.className || node.id || node.tagName.toLowerCase()),
                extra_id: node.getAttribute('data-builder-extra-id') || '',
                extra_type: node.getAttribute('data-builder-extra-type') || '',
                hidden: listHasRef(currentHiddenPaths(), {path: path, key: key}),
                locked: listHasRef((state.page.frontend_locked_paths || []), {path: path, key: key}),
                protected_block: isProtectedBlockNode(node)
            };
        });
    }

    function isBlockCandidateNode(node) {
        if (!node || !(node instanceof Element)) {
            return false;
        }
        if (isProtectedEditableNode(node)) {
            return false;
        }
        if (node.hasAttribute('data-builder-extra-id')) {
            return true;
        }
        if (isBuilderUnsafeRootNode(node)) {
            return false;
        }
        var rect = node.getBoundingClientRect();
        if (!rect || rect.width < 40 || rect.height < 24) {
            return false;
        }
        var tag = (node.tagName || '').toLowerCase();
        if (/^(script|style|link|meta|head|noscript|br)$/i.test(tag)) {
            return false;
        }
        if (tag === 'main' || tag === 'body' || tag === 'html') {
            return false;
        }
        if (/^(ul|ol|table|tbody|thead|tr)$/i.test(tag)) {
            return false;
        }
        if (node.children.length === 0) {
            return false;
        }
        if (node.childElementCount === 1) {
            var onlyChild = node.firstElementChild;
            if (onlyChild) {
                var parentRect = rect;
                var childRect = onlyChild.getBoundingClientRect();
                if (childRect && Math.abs(parentRect.width - childRect.width) < 8 && Math.abs(parentRect.height - childRect.height) < 8) {
                    return false;
                }
            }
        }
        if (isStructuralWrapperNode(node) && rect.width >= window.innerWidth * 0.95 && rect.height >= window.innerHeight * 0.8) {
            return false;
        }
        return true;
    }

    function findNearestBlockContainer(target) {
        var current = target;
        while (current && current instanceof Element && current !== document.body) {
            if (current.hasAttribute('data-builder-extra-id') || current.hasAttribute('data-builder-block-path')) {
                return current;
            }
            if (isBlockCandidateNode(current)) {
                return current;
            }
            current = current.parentElement;
        }
        return null;
    }

    function closestEditableNode(target) {
        if (!target || !(target instanceof Element)) {
            return null;
        }
        var directTextNode = target.closest(textEditableSelectors());
        if (directTextNode && isInlineEditableNode(directTextNode) && !target.closest('#cms_frontend_builder_root')) {
            return directTextNode;
        }
        if (target.matches('img,a,button') && isInlineEditableNode(target) && !target.closest('#cms_frontend_builder_root')) {
            return target;
        }
        if (!target.closest('#cms_frontend_builder_root')) {
            var nearestBlock = findNearestBlockContainer(target);
            if (nearestBlock) {
                var nestedTextNode = target.closest(textEditableSelectors());
                if (nestedTextNode && isInlineEditableNode(nestedTextNode)) {
                    return nestedTextNode;
                }
                if (target.matches('img,a,button') && isInlineEditableNode(target)) {
                    return target;
                }
                return nearestBlock;
            }
        }
        var businessNode = target.closest('[data-business-item-type]');
        if (businessNode) {
            return businessNode;
        }
        var blockNode = target.closest('[data-builder-block-path]');
        if (blockNode && isInlineEditableNode(blockNode)) {
            return blockNode;
        }
        if (blockNode && !target.closest('#cms_frontend_builder_root')) {
            return blockNode;
        }
        var textNode = target.closest(textEditableSelectors());
        if (textNode && isInlineEditableNode(textNode)) {
            return textNode;
        }
        if (isSimpleTextNode(target)) {
            return target;
        }
        var extraNode = target.closest('[data-builder-extra-id]');
        if (extraNode) {
            return extraNode;
        }
        return target.closest(editableSelectors());
    }

    function isProtectedBlockNode(node) {
        if (!node || !(node instanceof Element) || node.hasAttribute('data-builder-extra-id')) {
            return false;
        }
        if (isBuilderUnsafeRootNode(node) || isStructuralWrapperNode(node)) {
            return true;
        }
        if (node.closest('footer') || node.id === 'cms_builder_footer_meta' || node.closest('#cms_builder_footer_meta')) {
            return true;
        }
        var identity = [
            node.id || '',
            typeof node.className === 'string' ? node.className : '',
            node.getAttribute('data-builder-block-path') || ''
        ].join(' ').toLowerCase();
        return /copyright|copy-right|banquan|footer|foot\b/.test(identity);
    }

    function protectedBlockMessage() {
        return '底部版权区块为受保护内容，不能删除或隐藏。';
    }

    function isProtectedEditableNode(node) {
        if (!node || !(node instanceof Element)) {
            return false;
        }
        return !!(node.closest && (node.closest('footer') || node.id === 'cms_builder_footer_meta' || node.closest('#cms_builder_footer_meta')));
    }

    function blockProtectedEditMessage() {
        return '底部版权区域受保护，禁止修改内容、样式和位置。';
    }

    function activeNodeMeta(node) {
        if (!node) {
            return null;
        }
        return {
            path: getDomPath(node),
            tag: node.tagName.toLowerCase(),
            text: (node.innerText || node.textContent || '').replace(/\s+/g, ' ').trim(),
            html: node.innerHTML || '',
            src: node.getAttribute('src') || '',
            href: node.getAttribute('href') || '',
            style: node.getAttribute('style') || '',
            extra_id: node.closest('[data-builder-extra-id]') ? node.closest('[data-builder-extra-id]').getAttribute('data-builder-extra-id') : ''
        };
    }

    function businessTypeLabel(type) {
        return type === 'product' ? '产品' : '文章';
    }

    function businessItemMeta(node) {
        if (!node || !(node instanceof Element)) {
            return null;
        }
        var itemNode = node.closest('[data-business-item-type]');
        if (!itemNode) {
            itemNode = node.closest('a[href*="m=article&id="],a[href*="m=product&id="]');
        }
        if (!itemNode) {
            return null;
        }
        var href = itemNode.getAttribute('href') || '';
        var itemType = itemNode.getAttribute('data-business-item-type') || '';
        var itemId = itemNode.getAttribute('data-business-item-id') || '';
        if ((!itemType || !itemId) && href) {
            var articleMatch = href.match(/[?&]m=article\b/i);
            var productMatch = href.match(/[?&]m=product\b/i);
            var idMatch = href.match(/[?&]id=(\d+)/i);
            if (!itemType) {
                itemType = articleMatch ? 'article' : (productMatch ? 'product' : '');
            }
            if (!itemId && idMatch) {
                itemId = idMatch[1];
            }
        }
        itemType = itemType === 'product' ? 'product' : (itemType === 'article' ? 'article' : '');
        itemId = parseInt(itemId, 10) || 0;
        if ((!itemType || !itemId) && boot && boot.builder_ref_id > 0 && node.closest && !node.closest('[data-builder-extra-id]')) {
            if (boot.builder_type === 'article_detail') {
                itemType = 'article';
                itemId = parseInt(boot.builder_ref_id, 10) || 0;
                itemNode = node;
            } else if (boot.builder_type === 'product_detail') {
                itemType = 'product';
                itemId = parseInt(boot.builder_ref_id, 10) || 0;
                itemNode = node;
            }
        }
        if (!itemType || !itemId) {
            return null;
        }
        return {
            node: itemNode,
            type: itemType,
            id: itemId,
            extraId: itemNode.getAttribute('data-business-extra-id') || '',
            title: itemNode.getAttribute('data-business-title') || '',
            summary: itemNode.getAttribute('data-business-summary') || '',
            price: itemNode.getAttribute('data-business-price') || '',
            litpic: itemNode.getAttribute('data-business-litpic') || ''
        };
    }

    function fetchBusinessItem(type, id) {
        var dataType = type === 'product' ? 'product_item' : 'article_item';
        return fetchBusinessData(dataType, {
            item_id: id
        }).then(function (result) {
            return result && result.data ? result.data : null;
        }).catch(function () {
            return null;
        });
    }

    function blockPrimaryActionMeta(node) {
        var business = businessItemMeta(node);
        if (business) {
            return {
                label: '编辑数据',
                kind: 'business',
                business: business
            };
        }
        if (node && node.tagName && node.tagName.toLowerCase() === 'img') {
            return {
                label: '编辑图片',
                kind: 'image'
            };
        }
        if (node && node.tagName && node.tagName.toLowerCase() === 'a') {
            return {
                label: '编辑链接',
                kind: 'text'
            };
        }
        if (node && node.tagName && node.tagName.toLowerCase() === 'button') {
            return {
                label: '编辑按钮',
                kind: 'text'
            };
        }
        if (node && node.tagName && /^(h1|h2|h3|h4|h5|h6)$/i.test(node.tagName.toLowerCase())) {
            return {
                label: '编辑标题',
                kind: 'text'
            };
        }
        if (node && node instanceof Element && (node.hasAttribute('data-builder-extra-id') || node.hasAttribute('data-builder-block-path') || node.matches('section,article,header,footer,main'))) {
            return {
                label: '管理栏目',
                kind: 'section'
            };
        }
        return {
            label: '编辑文本',
            kind: 'text'
        };
    }

    function clearHighlights() {
        qsa('.cms-builder-highlight').forEach(function (node) {
            node.classList.remove('cms-builder-highlight');
        });
        qsa('.cms-builder-selected').forEach(function (node) {
            node.classList.remove('cms-builder-selected');
        });
        qsa('.cms-builder-canvas-drop').forEach(function (node) {
            node.classList.remove('cms-builder-canvas-drop');
        });
    }

    function refreshCanvasChrome() {
        clearHighlights();
        if (state.selectedNode) {
            state.selectedNode.classList.add('cms-builder-selected');
        }
        if (state.hoveredPath && state.hoveredPath !== state.selectedPath) {
            var hovered = queryByPath(state.hoveredPath);
            if (hovered && !listHasRef(currentHiddenPaths(), nodeRef(hovered))) {
                hovered.classList.add('cms-builder-highlight');
            }
        }
        renderFloatingOverlays();
    }

    function setHoveredNode(node) {
        if (isFreeLayoutMode()) {
            return;
        }
        if (state.selectedCanvasBlockId || state.resizingMeta || state.draggingPath || state.draggingNewExtraType || state.mouseDragMeta) {
            return;
        }
        if (state.selectedPath) {
            return;
        }
        if (state.hoverToolbarActive) {
            return;
        }
        clearTimeout(state.hoverLeaveTimer);
        var path = node ? getDomPath(node) : '';
        if (node && listHasRef(currentHiddenPaths(), nodeRef(node))) {
            path = '';
        }
        if (!path) {
            state.hoverLeaveTimer = setTimeout(function () {
                if (!state.selectedPath) {
                    state.hoveredPath = '';
                } else {
                    state.hoveredPath = '';
                }
                refreshCanvasChrome();
            }, 180);
            return;
        }
        if (state.hoveredPath === path) {
            return;
        }
        state.hoveredPath = path;
        refreshCanvasChrome();
    }

    function clearSelectionState(options) {
        options = options || {};
        if (state.liveEditToken || state.businessEditor || state.materialPreviewKey) {
            return;
        }
        clearTimeout(state.hoverLeaveTimer);
        state.hoverToolbarActive = false;
        state.hoveredPath = '';
        state.selectedNode = null;
        state.selectedPath = '';
        state.selectedCanvasBlockId = '';
        if (options.closeSettings !== false) {
            hideSettingsPanel();
        }
        refreshCanvasChrome();
        renderCanvasLayer();
        renderNodeList();
        renderSettings();
    }

    function clearHoverState() {
        if (isFreeLayoutMode()) {
            return;
        }
        if (state.selectedCanvasBlockId || state.hoverToolbarActive) {
            return;
        }
        clearTimeout(state.hoverLeaveTimer);
        state.hoveredPath = '';
        refreshCanvasChrome();
    }

    function scheduleHoverClear(delay) {
        if (isFreeLayoutMode()) {
            return;
        }
        if (state.selectedCanvasBlockId || state.hoverToolbarActive) {
            return;
        }
        clearTimeout(state.hoverLeaveTimer);
        state.hoverLeaveTimer = setTimeout(function () {
            if (state.selectedCanvasBlockId || state.hoverToolbarActive) {
                return;
            }
            state.hoveredPath = '';
            refreshCanvasChrome();
        }, typeof delay === 'number' ? delay : 180);
    }

    function renderNodeList() {
        var list = qs('#cms_builder_node_list');
        if (!list) {
            return;
        }
        collectBlocks();
        var canvasItems = canvasBlocks();
        if (!state.blocks.length && !canvasItems.length) {
            list.innerHTML = '<div class="cms-builder-empty">当前页面还没有可编辑区块，先从左侧添加模块，或点击页面中的现有区块开始编辑。</div>';
            return;
        }
        var canvasHtml = canvasItems.map(function (block) {
            var active = isCanvasBlockSelected(block.id) ? ' is-active' : '';
            var badges = [];
            if (block.hidden) {
                badges.push('<span class="cms-builder-badge">已隐藏</span>');
            }
            if (block.locked) {
                badges.push('<span class="cms-builder-badge">已锁定</span>');
            }
            return '<button type="button" class="' + active.trim() + '" data-canvas-select="' + escapeHtml(block.id) + '">' +
                '<div class="cms-builder-list-title">画布 · ' + escapeHtml(block.label || canvasTypeLabel(block.type)) + '</div>' +
                '<div class="cms-builder-list-meta">x:' + escapeHtml(block.x) + ' y:' + escapeHtml(block.y) + ' / ' + escapeHtml(block.w) + 'x' + escapeHtml(block.h) + '</div>' +
                (badges.length ? '<div class="cms-builder-badges">' + badges.join('') + '</div>' : '') +
            '</button>';
        }).join('');
        var domListTitle = isFreeLayoutMode()
            ? '<div class="cms-builder-empty" style="margin-top:12px;">当前页面存在历史自由画布数据，系统会自动恢复为模板区块模式并在保存时清理。</div>'
            : '';
        var blockDepths=state.blocks.map(function(item){return Math.max(0,String(item.path||'').split(' > ').length-1);});var minBlockDepth=blockDepths.length?Math.min.apply(Math,blockDepths):0;
        var domHtml = state.blocks.map(function (item,itemIndex) {
            var active = item.path === state.selectedPath ? ' is-active' : '';
            var treeDepth=Math.max(0,Math.min(6,(blockDepths[itemIndex]||0)-minBlockDepth));
            var badges = [];
            if (item.hidden) {
                badges.push('<span class="cms-builder-badge">已隐藏</span>');
            }
            if (item.locked) {
                badges.push('<span class="cms-builder-badge">已锁定</span>');
            }
            if (item.protected_block) {
                badges.push('<span class="cms-builder-badge">底部版权保护</span>');
            }
            return '<button type="button" draggable="' + (isFreeLayoutMode() ? 'false' : 'true') + '" class="cms-builder-tree-item ' + (treeDepth===0?'cms-builder-tree-root ':'') + active.trim() + '" style="--cms-tree-indent:'+(treeDepth*12)+'px" data-tree-depth="'+treeDepth+'" data-select-path="' + escapeHtml(item.path) + '"' + (isFreeLayoutMode() ? '' : ' data-drag-path="' + escapeHtml(item.path) + '"') + '>' +
                '<div class="cms-builder-list-title">' + escapeHtml(item.tag.toUpperCase() + ' · ' + (item.label || '未命名区块')) + '</div>' +
                '<div class="cms-builder-list-meta">' + escapeHtml(item.path) + '</div>' +
                (badges.length ? '<div class="cms-builder-badges">' + badges.join('') + '</div>' : '') +
            '</button>';
        }).join('');
        list.innerHTML = canvasHtml + domListTitle + domHtml;
    }

    function overrideValue(key, fallback) {
        return overrideValueByPath(state.selectedPath, key, fallback);
    }

    function inlineStylePresets(kind) {
        if (kind === 'image') {
            return [
                {key: 'clean', label: '清空样式', value: ''},
                {key: 'shadow', label: '圆角阴影', value: 'display:block; border-radius:18px; box-shadow:0 18px 40px rgba(15,23,42,.18);'},
                {key: 'banner', label: '横幅铺满', value: 'display:block; width:100%; height:320px; object-fit:cover; border-radius:22px;'},
                {key: 'card', label: '白底卡片', value: 'display:block; width:100%; padding:12px; border-radius:22px; background:#ffffff; box-shadow:0 12px 30px rgba(15,23,42,.12);'},
                {key: 'soft', label: '柔和灰度', value: 'display:block; max-width:100%; border-radius:18px; filter:grayscale(100%); opacity:.92;'},
                {key: 'frame', label: '相框留白', value: 'display:block; width:100%; padding:10px; border:1px solid #e2e8f0; border-radius:20px; background:#ffffff;'},
                {key: 'circle', label: '圆形头像', value: 'display:block; width:180px; height:180px; object-fit:cover; border-radius:999px; margin:0 auto;'},
                {key: 'tilt', label: '轻微倾斜', value: 'display:block; max-width:100%; border-radius:18px; transform:rotate(-3deg); box-shadow:0 14px 34px rgba(15,23,42,.16);'},
                {key: 'glass', label: '玻璃卡片', value: 'display:block; width:100%; padding:12px; border-radius:24px; background:rgba(255,255,255,.55); backdrop-filter:blur(10px); box-shadow:0 12px 32px rgba(15,23,42,.12);'},
                {key: 'duotone', label: '冷调滤镜', value: 'display:block; max-width:100%; border-radius:18px; filter:saturate(.75) contrast(1.05) hue-rotate(190deg);'}
            ];
        }
        if (kind === 'section') {
            return [
                {key: 'clean', label: '清空样式', value: ''},
                {key: 'card', label: '白底卡片', value: 'padding:24px; border:1px solid #e2e8f0; border-radius:22px; background:#ffffff; box-shadow:0 10px 26px rgba(15,23,42,.08);'},
                {key: 'gradient', label: '渐变块', value: 'padding:28px; border-radius:24px; background:linear-gradient(135deg,#eff6ff,#ffffff);'},
                {key: 'outline', label: '描边面板', value: 'padding:24px; border:1px dashed #94a3b8; border-radius:24px; background:#f8fafc;'},
                {key: 'dark', label: '深色横幅', value: 'padding:28px; border-radius:24px; background:#0f172a; color:#ffffff;'},
                {key: 'split', label: '左右分栏', value: 'display:grid; grid-template-columns:1fr 1fr; gap:20px; padding:28px; border-radius:24px; background:#ffffff;'},
                {key: 'glass', label: '玻璃面板', value: 'padding:28px; border-radius:26px; background:rgba(255,255,255,.62); backdrop-filter:blur(12px); border:1px solid rgba(255,255,255,.55); box-shadow:0 12px 32px rgba(15,23,42,.12);'},
                {key: 'warm', label: '暖色卡片', value: 'padding:28px; border-radius:24px; background:linear-gradient(135deg,#fff7ed,#fffbeb); border:1px solid #fed7aa;'},
                {key: 'spot', label: '聚焦光斑', value: 'padding:30px; border-radius:28px; background:radial-gradient(circle at top right,#dbeafe,#ffffff 48%,#eff6ff);'},
                {key: 'strip', label: '横向条带', value: 'padding:24px 30px; border-left:6px solid #2563eb; border-radius:18px; background:#f8fbff;'}
            ];
        }
        return [
            {key: 'clean', label: '清空样式', value: ''},
            {key: 'lead', label: '大标题', value: 'display:block; font-size:28px; line-height:1.5; font-weight:700; color:#0f172a;'},
            {key: 'note', label: '提示卡片', value: 'display:block; padding:14px 16px; border-radius:16px; background:#f8fafc; color:#334155;'},
            {key: 'center', label: '居中强调', value: 'display:block; text-align:center; font-size:20px; line-height:1.7; color:#2563eb;'},
            {key: 'pill', label: '深色胶囊', value: 'display:inline-block; padding:10px 18px; border-radius:999px; background:#0f172a; color:#ffffff;'},
            {key: 'muted', label: '浅灰说明', value: 'display:block; font-size:14px; line-height:1.9; color:#64748b;'},
            {key: 'accent', label: '橙色强调', value: 'display:block; font-size:22px; line-height:1.6; font-weight:700; color:#f97316;'},
            {key: 'underline', label: '标题下划线', value: 'display:inline-block; font-size:22px; line-height:1.5; font-weight:700; color:#0f172a; border-bottom:3px solid #2563eb; padding-bottom:4px;'},
            {key: 'tag', label: '标签样式', value: 'display:inline-block; padding:8px 14px; border-radius:12px; background:#eff6ff; color:#2563eb; font-weight:700;'},
            {key: 'quote', label: '引用样式', value: 'display:block; padding:14px 18px; border-left:4px solid #2563eb; background:#f8fbff; color:#334155; line-height:1.9;'}
        ];
    }

    function detectInlineStylePresetKey(kind, styleValue) {
        var normalized = String(styleValue || '').trim();
        var matched = 'custom';
        inlineStylePresets(kind).forEach(function (item) {
            if (normalized === String(item.value || '').trim()) {
                matched = item.key;
            }
        });
        return matched;
    }

    function findInlineStylePreset(kind, key) {
        var matched = null;
        inlineStylePresets(kind).forEach(function (item) {
            if (item.key === key) {
                matched = item;
            }
        });
        return matched;
    }

    function renderInlineStylePresetField(kind, currentStyle) {
        var selectedKey = detectInlineStylePresetKey(kind, currentStyle);
        var options = '<option value="custom"' + (selectedKey === 'custom' ? ' selected' : '') + '>自定义样式</option>';
        inlineStylePresets(kind).forEach(function (item) {
            options += '<option value="' + escapeHtml(item.key) + '"' + (selectedKey === item.key ? ' selected' : '') + '>' + escapeHtml(item.label) + '</option>';
        });
        return '<div class="cms-builder-field"><label>样式方案</label><select data-style-preset-select="' + escapeHtml(kind) + '">' + options + '</select></div>';
    }

    function syncInlineStylePresetSelect(kind, styleValue) {
        qsa('[data-style-preset-select="' + kind + '"]').forEach(function (select) {
            if (select) {
                select.value = detectInlineStylePresetKey(kind, styleValue);
            }
        });
    }

    function syncSelectedStyleTextarea(styleValue) {
        qsa('[data-front-key="style"]').forEach(function (input) {
            if (input) {
                input.value = styleValue;
            }
        });
    }

    function syncExtraColorInputs(key, value) {
        qsa('[data-extra-color-key="' + key + '"]').forEach(function (input) {
            if (input) {
                input.value = normalizeColorPickerValue(value, '#ffffff');
            }
        });
    }

    function syncExtraStyleInputs(key, value) {
        qsa('[data-extra-style-key="' + key + '"]').forEach(function (input) {
            if (input && input.type !== 'color') {
                input.value = value;
            }
        });
        qsa('[data-extra-style-color-key="' + key + '"]').forEach(function (input) {
            if (input) {
                input.value = normalizeColorPickerValue(value, '#ffffff');
            }
        });
    }

    function stylePanelTabs() {
        return [
            {key: 'theme', label: '主题'},
            {key: 'basic', label: '基础'},
            {key: 'layout', label: '版式'},
            {key: 'footer', label: '底部'}
        ];
    }

    function themePalette() {
        return ['#ff4d4f', '#f759ab', '#cf1322', '#ff7a45', '#ffc53d', '#73d13d', '#36cfc9', '#1890ff', '#2f54eb', '#13c2c2', '#722ed1', '#262626'];
    }

    function currentPageWidthMode() {
        if (state.page.page_width_mode) {
            return state.page.page_width_mode;
        }
        var width = parseInt(state.page.page_width, 10) || 1200;
        if (width === 960) {
            return '960';
        }
        if (width === 1200) {
            return '1200';
        }
        return 'custom';
    }

    function currentBackgroundMode() {
        if (state.page.background_mode) {
            return state.page.background_mode;
        }
        return state.page.background_color ? 'custom' : 'default';
    }

    function stylePartLabel(part) {
        if (part === 'header') {
            return '顶部区';
        }
        if (part === 'footer') {
            return '底部区';
        }
        return '版式区';
    }

    function containsNode(container, node) {
        return !!(container && node && container instanceof Element && node instanceof Element && (container === node || container.contains(node)));
    }

    function detectStylePartFromNode(node) {
        if (!node || !(node instanceof Element)) {
            return currentStylePanelPart();
        }
        var footerNode = findStylePartNode('footer');
        if (containsNode(footerNode, node)) {
            return 'footer';
        }
        var headerNode = findStylePartNode('header');
        if (containsNode(headerNode, node)) {
            return 'header';
        }
        return 'layout';
    }

    function currentStylePanelPart() {
        return ['header', 'layout', 'footer'].indexOf(state.stylePanelPart) !== -1 ? state.stylePanelPart : 'layout';
    }

    function stylePartMeta(part) {
        var node = findStylePartNode(part);
        var path = node ? getDomPath(node) : '';
        return {
            part: part,
            label: stylePartLabel(part),
            node: node,
            path: path,
            exists: !!node,
            hidden: !!(path && listHasRef(currentHiddenPaths(), node ? nodeRef(node) : {path: path, key: ''})),
            protected_block: !!(node && isProtectedBlockNode(node))
        };
    }

    function overrideValueByPath(path, key, fallback) {
        var item = overrideEntryForRef(normalizeStoredRef(path ? {path: path, key: ''} : null)) || {};
        return Object.prototype.hasOwnProperty.call(item, key) ? item[key] : fallback;
    }

    function nodeStylePropertyValue(node, path, propertyName) {
        if (!node) {
            return '';
        }
        var styleText = overrideValueByPath(path, 'style', node.getAttribute('style') || '');
        var probe = document.createElement('div');
        probe.style.cssText = styleText || '';
        var direct = probe.style.getPropertyValue(propertyName) || '';
        if (direct) {
            return direct;
        }
        try {
            var computed = window.getComputedStyle(node).getPropertyValue(propertyName) || '';
            if (/rgba?\(\s*0\s*,\s*0\s*,\s*0\s*,\s*0\s*\)/i.test(computed) || computed === 'transparent') {
                return '';
            }
            return computed.trim();
        } catch (error) {
            return '';
        }
    }

    function stylePartBackgroundColor(part) {
        var meta = stylePartMeta(part);
        return meta.exists ? nodeStylePropertyValue(meta.node, meta.path, 'background-color') : '';
    }

    function syncStylePartBackgroundInputs(part, value) {
        qsa('[data-style-part-bg="' + part + '"]').forEach(function (input) {
            if (input && input.type !== 'color') {
                input.value = value;
            }
        });
        qsa('[data-style-part-bg-color="' + part + '"]').forEach(function (input) {
            if (input) {
                input.value = normalizeColorPickerValue(value, '#ffffff');
            }
        });
    }

    function updateStylePartBackgroundLive(part, value) {
        var meta = stylePartMeta(part);
        if (!meta.exists) {
            return;
        }
        state.stylePanelPart = part;
        var nextStyle = buildStyleTextWithProperty(overrideValueByPath(meta.path, 'style', meta.node.getAttribute('style') || ''), 'background-color', value);
        syncStylePartBackgroundInputs(part, value);
        setStyleOverrideByPath(meta.path, nextStyle);
        meta.node.setAttribute('style', nextStyle);
        if (state.selectedPath === meta.path) {
            syncSelectedStylePropInputs('background-color', value);
            syncSelectedStyleTextarea(nextStyle);
        }
        renderFloatingOverlays();
        queueSaveDraft();
    }

    function finalizeStylePartBackgroundEdit(part) {
        var meta = stylePartMeta(part);
        endLiveEdit();
        if (!meta.exists) {
            return;
        }
        commitMutation(meta.path);
        setStatus(meta.label + '背景颜色已更新');
    }

    function selectStylePart(part, scrollIntoView) {
        var meta = stylePartMeta(part);
        state.stylePanelPart = part;
        if (!meta.exists) {
            renderSettings();
            renderLibraryPanel();
            setStatus('当前页面未找到可定位的' + stylePartLabel(part));
            return;
        }
        selectNode(meta.node, !!scrollIntoView, false);
        setStatus('已选择' + meta.label + '，可直接控制该区域显示、隐藏和背景。');
    }

    function toggleStylePartHidden(part) {
        var meta = stylePartMeta(part);
        state.stylePanelPart = part;
        if (!meta.exists) {
            setStatus('当前页面未找到' + stylePartLabel(part));
            return;
        }
        if (meta.protected_block) {
            setStatus(protectedBlockMessage());
            return;
        }
        toggleBlockHiddenByPath(meta.path);
    }

    function layoutPartBlocks() {
        var meta = stylePartMeta('layout');
        if (!meta.exists) {
            return [];
        }
        collectBlocks();
        return state.blocks.filter(function (item) {
            var node = queryByPath(item.path);
            if (!node || item.path === meta.path) {
                return false;
            }
            return containsNode(meta.node, node);
        });
    }

    function applyPageStylePatch(patch, nextSelectedPath, statusText) {
        pushHistory();
        Object.keys(patch || {}).forEach(function (key) {
            state.page[key] = patch[key];
        });
        commitMutation(nextSelectedPath || state.selectedPath);
        if (statusText) {
            setStatus(statusText);
        }
    }

    function applyPageWidthPreset(mode) {
        var patch = {page_width_mode: mode};
        if (mode === 'default' || mode === '1200') {
            patch.page_width = 1200;
        } else if (mode === '960') {
            patch.page_width = 960;
        } else if (mode === 'wide') {
            patch.page_width = 1440;
        }
        applyPageStylePatch(patch, '', '网站宽度已更新');
    }

    function applyPageBackgroundMode(mode) {
        var patch = {background_mode: mode};
        if (mode === 'hidden') {
            patch.background_color = '';
            patch.theme_surface = 'clean';
        }
        applyPageStylePatch(patch, '', '页面背景模式已更新');
    }

    function applyLayoutPreset(preset) {
        var patch = {layout_preset: preset};
        if (preset === 'compact') {
            patch.page_width = 960;
            patch.page_width_mode = '960';
            patch.page_padding = 12;
        } else if (preset === 'wide') {
            patch.page_width = 1440;
            patch.page_width_mode = 'wide';
            patch.page_padding = 32;
        } else {
            patch.page_width = 1200;
            patch.page_width_mode = '1200';
            patch.page_padding = 18;
        }
        applyPageStylePatch(patch, '', '版式预设已切换');
    }

    function findStylePartNode(part) {
        if (part === 'header') {
            return qs('header') || qs('.topBar') || qs('body > div');
        }
        if (part === 'footer') {
            return qs('footer');
        }
        return qs('main') || qs('.w1200') || qs('.wrap');
    }

    function jumpToStylePart(part) {
        selectStylePart(part, true);
    }

    function renderStylePartMap() {
        var activePart = currentStylePanelPart();
        return ['header', 'layout', 'footer'].map(function (part) {
            var meta = stylePartMeta(part);
            var className = part === 'header'
                ? 'cms-builder-style-map-bar'
                : (part === 'footer' ? 'cms-builder-style-map-footer' : 'cms-builder-style-map-main');
            var label = meta.label + (meta.hidden ? ' · 已隐藏' : '');
            var style = 'width:100%;border:0;cursor:pointer;' + (activePart === part ? 'box-shadow:inset 0 0 0 2px rgba(37,99,235,.28);' : '') + (meta.hidden ? 'opacity:.56;' : '');
            return '<button type="button" class="' + className + '" data-style-part-select="' + part + '" title="点击选择' + meta.label + '" style="' + style + '">' + label + '</button>';
        }).join('');
    }

    function renderStylePartControls() {
        var activePart = currentStylePanelPart();
        var meta = stylePartMeta(activePart);
        var bgColor = stylePartBackgroundColor(activePart);
        return '<div class="cms-builder-group">' +
            '<div class="cms-builder-group-title">当前区域控制</div>' +
            '<div class="cms-builder-badges">' +
                '<span class="cms-builder-badge">当前区域：' + escapeHtml(meta.label) + '</span>' +
                (meta.hidden ? '<span class="cms-builder-badge">当前已隐藏</span>' : '<span class="cms-builder-badge">当前显示中</span>') +
                (meta.protected_block ? '<span class="cms-builder-badge">底部版权保护</span>' : '') +
            '</div>' +
            '<div class="cms-builder-fields" style="margin-top:12px;">' +
                '<div class="cms-builder-field"><label>区域背景颜色</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-style-part-bg="' + activePart + '" value="' + escapeHtml(bgColor || '') + '" placeholder="transparent / #ffffff"' + (meta.exists ? '' : ' disabled') + '><input type="color" data-style-part-bg-color="' + activePart + '" value="' + escapeHtml(normalizeColorPickerValue(bgColor || '', '#ffffff')) + '" style="width:52px;height:42px;padding:4px;"' + (meta.exists ? '' : ' disabled') + '></div></div>' +
            '</div>' +
            '<div class="cms-builder-style-quick-grid" style="margin-top:12px;">' +
                '<button type="button" class="cms-builder-style-quick-btn" data-style-part-select="' + activePart + '"' + (meta.exists ? '' : ' disabled') + '>选择区域</button>' +
                '<button type="button" class="cms-builder-style-quick-btn" data-style-part-toggle-hidden="' + activePart + '"' + (!meta.exists || meta.protected_block ? ' disabled' : '') + '>' + (meta.hidden ? '显示区域' : '隐藏区域') + '</button>' +
                '<button type="button" class="cms-builder-style-quick-btn" data-style-locate-part="' + activePart + '"' + (meta.exists ? '' : ' disabled') + '>滚动定位</button>' +
            '</div>' +
        '</div>';
    }

    function renderLayoutBlockManager() {
        var blocks = layoutPartBlocks();
        if (!blocks.length) {
            return '<div class="cms-builder-empty">当前版式区还没有识别到可控制的区块。</div>';
        }
        return '<div class="cms-builder-fields">' + blocks.map(function (item) {
            return '<div style="padding:12px;border:1px solid #e2e8f0;border-radius:14px;background:#fff;">' +
                '<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:10px;"><div><div style="color:#0f172a;font:700 13px/1.4 &quot;Microsoft YaHei&quot;,sans-serif;">' + escapeHtml(blockModuleLabel(item)) + '</div><div style="margin-top:4px;color:#64748b;font:500 12px/1.6 &quot;Microsoft YaHei&quot;,sans-serif;">' + escapeHtml(item.label || item.path) + '</div></div>' + (item.hidden ? '<span class="cms-builder-badge">已隐藏</span>' : '') + '</div>' +
                '<div class="cms-builder-style-quick-grid" style="margin-top:10px;"><button type="button" class="cms-builder-style-quick-btn" data-layout-block-select="' + escapeHtml(item.path) + '">选择区块</button><button type="button" class="cms-builder-style-quick-btn" data-layout-block-toggle-hidden="' + escapeHtml(item.path) + '">' + (item.hidden ? '显示区块' : '隐藏区块') + '</button><button type="button" class="cms-builder-style-quick-btn" data-style-locate-path="' + escapeHtml(item.path) + '">滚动定位</button></div>' +
            '</div>';
        }).join('') + '</div>';
    }

    function renderStylePanelTabs() {
        var activeTab = state.stylePanelTab || 'theme';
        return '<div class="cms-builder-style-tabs">' +
            stylePanelTabs().map(function (item) {
                return '<button type="button" class="cms-builder-style-tab' + (activeTab === item.key ? ' is-active' : '') + '" data-style-panel-tab="' + item.key + '">' + item.label + '</button>';
            }).join('') +
        '</div>';
    }

    function renderThemePanelContent() {
        var themeColor = state.page.theme_color || '#5f7eeb';
        var surface = state.page.theme_surface || 'clean';
        return '<div class="cms-builder-style-body">' +
            '<p class="cms-builder-style-subtitle">选择当前页面的主题色和整体质感，主题色会实时作用到装修面板高亮与新增模块中的重点按钮、标题强调。</p>' +
            '<div class="cms-builder-style-swatches">' +
                themePalette().map(function (color) {
                    return '<button type="button" class="cms-builder-style-swatch' + (themeColor.toLowerCase() === color.toLowerCase() ? ' is-active' : '') + '" data-page-theme-color="' + color + '" style="background:' + color + ';"></button>';
                }).join('') +
            '</div>' +
            '<div class="cms-builder-style-card-grid">' +
                '<button type="button" class="cms-builder-style-preview-card' + (surface === 'clean' ? ' is-active' : '') + '" data-page-theme-surface="clean"><div class="cms-builder-style-preview-stage"><div style="height:8px;border-radius:999px;background:#2f3542;"></div><div style="height:34px;margin-top:8px;border-radius:8px;background:#f4f2ef;"></div><div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px;"><div style="height:26px;border-radius:8px;background:#f8f8f8;"></div><div style="height:26px;border-radius:8px;background:' + themeColor + ';opacity:.18;"></div></div><div style="height:8px;margin-top:10px;border-radius:999px;background:#2f3542;"></div></div><div class="cms-builder-style-preview-caption">简洁浅色主题</div></button>' +
                '<button type="button" class="cms-builder-style-preview-card' + (surface === 'soft' ? ' is-active' : '') + '" data-page-theme-surface="soft"><div class="cms-builder-style-preview-stage" style="background:linear-gradient(180deg,rgba(255,255,255,.96),rgba(95,126,235,.06));"><div style="height:8px;border-radius:999px;background:' + themeColor + ';"></div><div style="height:34px;margin-top:8px;border-radius:8px;background:#fff;"></div><div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px;"><div style="height:26px;border-radius:8px;background:' + themeColor + ';opacity:.18;"></div><div style="height:26px;border-radius:8px;background:#fff;"></div></div><div style="height:8px;margin-top:10px;border-radius:999px;background:' + themeColor + ';"></div></div><div class="cms-builder-style-preview-caption">柔和氛围主题</div></button>' +
            '</div>' +
        '</div>';
    }

    function renderBasicPanelContent() {
        var widthMode = currentPageWidthMode();
        var widthValue = parseInt(state.page.page_width, 10) || 1200;
        return '<div class="cms-builder-style-body">' +
            '<div class="cms-builder-group">' +
                '<div class="cms-builder-group-title">页面结构</div>' +
                '<div class="cms-builder-style-blocks">' +
                    '<div class="cms-builder-style-blockmap">' + renderStylePartMap() + '</div>' +
                    '<div class="cms-builder-note-box">点击上面的顶部区、版式区、底部区，会直接选中真实页面区域。隐藏和背景颜色只作用到当前区域，不再影响整个网页。</div>' +
                '</div>' +
            '</div>' +
            renderStylePartControls() +
            '<div class="cms-builder-group">' +
                '<div class="cms-builder-group-title">网站宽度</div>' +
                '<div class="cms-builder-style-choice">' +
                    '<button type="button" class="cms-builder-style-pill' + (widthMode === '1200' || widthMode === 'default' ? ' is-active' : '') + '" data-page-width-preset="1200">默认</button>' +
                    '<button type="button" class="cms-builder-style-pill' + (widthMode === '960' ? ' is-active' : '') + '" data-page-width-preset="960">960</button>' +
                    '<button type="button" class="cms-builder-style-pill' + (widthMode === 'wide' ? ' is-active' : '') + '" data-page-width-preset="wide">1440</button>' +
                    '<button type="button" class="cms-builder-style-pill' + (widthMode === 'custom' ? ' is-active' : '') + '" data-page-width-preset="custom">自定义</button>' +
                '</div>' +
                '<div class="cms-builder-fields">' +
                    '<div class="cms-builder-field"><label>宽度调节</label><div class="cms-builder-style-range"><input type="range" min="760" max="1600" step="10" data-page-key="page_width" value="' + escapeHtml(widthValue) + '"><input type="number" min="760" max="1600" step="10" data-page-key="page_width" value="' + escapeHtml(widthValue) + '"></div></div>' +
                '</div>' +
            '</div>' +
            '<div class="cms-builder-group">' +
                '<div class="cms-builder-group-title">页面信息</div>' +
                '<div class="cms-builder-fields">' +
                    '<div class="cms-builder-field"><label>页面标题</label><input type="text" data-page-key="title" value="' + escapeHtml(state.page.title || '') + '"></div>' +
                    '<div class="cms-builder-field"><label>编辑视图</label><div class="cms-builder-device-switch"><button type="button" id="cms_builder_device_pc" class="' + (currentDevice() === 'pc' ? 'is-active' : '') + '">PC</button><button type="button" id="cms_builder_device_tablet" class="' + (currentDevice() === 'tablet' ? 'is-active' : '') + '">平板</button><button type="button" id="cms_builder_device_mobile" class="' + (currentDevice() === 'mobile' ? 'is-active' : '') + '">移动端</button></div><div class="cms-builder-group-desc">平板视图使用 768px 预览并继承 PC 样式；移动端可单独覆盖。</div></div>' +
                '</div>' +
            '</div>' +
        '</div>';
    }

    function renderLayoutPanelContent() {
        var preset = state.page.layout_preset || 'classic';
        return '<div class="cms-builder-style-body">' +
            '<div class="cms-builder-group">' +
                '<div class="cms-builder-group-title">版式预设</div>' +
                '<div class="cms-builder-group-desc">快速切换当前页面的整体宽度和留白节奏，再继续细调各区块。</div>' +
                '<div class="cms-builder-style-preview-layout"><div class="cms-builder-style-layout-frame"><div class="cms-builder-style-layout-top">横幅和导航</div><div style="height:30px;margin-top:10px;border-radius:8px;background:#f3f4f6;display:flex;align-items:center;justify-content:center;color:#555;font:600 12px/1 &quot;Microsoft YaHei&quot;,sans-serif;">4</div><div class="cms-builder-style-layout-row"><div class="cms-builder-style-layout-side">1</div><div class="cms-builder-style-layout-center">2</div><div class="cms-builder-style-layout-side">3</div></div><div class="cms-builder-style-layout-bottom">5</div></div></div>' +
                '<div class="cms-builder-style-card-grid" style="margin-top:14px;">' +
                    '<button type="button" class="cms-builder-style-preview-card' + (preset === 'classic' ? ' is-active' : '') + '" data-page-layout-preset="classic"><div class="cms-builder-style-preview-stage"><div style="height:10px;border-radius:999px;background:#2f3542;"></div><div style="height:40px;margin-top:10px;border-radius:10px;background:#f4f2ef;"></div><div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px;"><div style="height:24px;border-radius:8px;background:#f8f8f8;"></div><div style="height:24px;border-radius:8px;background:#f8f8f8;"></div></div><div style="height:10px;margin-top:10px;border-radius:999px;background:#2f3542;"></div></div><div class="cms-builder-style-preview-caption">经典标准版式</div></button>' +
                    '<button type="button" class="cms-builder-style-preview-card' + (preset === 'wide' ? ' is-active' : '') + '" data-page-layout-preset="wide"><div class="cms-builder-style-preview-stage"><div style="height:10px;border-radius:999px;background:#ff7a45;"></div><div style="height:40px;margin-top:10px;border-radius:10px;background:#fff;"></div><div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:10px;"><div style="height:24px;border-radius:8px;background:#ffefe8;"></div><div style="height:24px;border-radius:8px;background:#ffefe8;"></div></div><div style="height:10px;margin-top:10px;border-radius:999px;background:#ff7a45;"></div></div><div class="cms-builder-style-preview-caption">宽屏留白版式</div></button>' +
                '</div>' +
            '</div>' +
            '<div class="cms-builder-group">' +
                '<div class="cms-builder-group-title">版式区块控制</div>' +
                '<div class="cms-builder-group-desc">下面列出当前版式区内识别到的区块。点击按钮即可选择、滚动定位、隐藏或显示。</div>' +
                '<div style="margin-top:12px;">' + renderLayoutBlockManager() + '</div>' +
            '</div>' +
        '</div>';
    }

    function renderFooterPanelContent() {
        var footerPaddingTop = state.page.footer_padding_top;
        var footerPaddingBottom = state.page.footer_padding_bottom;
        return '<div class="cms-builder-style-body">' +
            '<div class="cms-builder-note-box">底部版权区仍保持受保护状态，不开放删除和隐藏。这里提供底部背景、文字颜色、对齐和上下留白设置。</div>' +
            '<div class="cms-builder-group" style="margin-top:14px;">' +
                '<div class="cms-builder-group-title">底部样式</div>' +
                '<div class="cms-builder-fields">' +
                    '<div class="cms-builder-field"><label>文字对齐</label><select data-page-key="footer_text_align"><option value="left"' + (state.page.footer_text_align === 'left' ? ' selected' : '') + '>靠左</option><option value="center"' + ((state.page.footer_text_align || 'center') === 'center' ? ' selected' : '') + '>居中</option><option value="right"' + (state.page.footer_text_align === 'right' ? ' selected' : '') + '>靠右</option></select></div>' +
                    '<div class="cms-builder-field"><label>顶部留白</label><input type="number" min="0" max="400" step="2" data-page-key="footer_padding_top" value="' + escapeHtml(footerPaddingTop || footerPaddingTop === 0 ? footerPaddingTop : 20) + '"></div>' +
                    '<div class="cms-builder-field"><label>底部留白</label><input type="number" min="0" max="400" step="2" data-page-key="footer_padding_bottom" value="' + escapeHtml(footerPaddingBottom || footerPaddingBottom === 0 ? footerPaddingBottom : 20) + '"></div>' +
                    '<div class="cms-builder-field"><label>背景颜色</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-page-key="footer_background_color" value="' + escapeHtml(state.page.footer_background_color || '') + '" placeholder="#ffffff"><input type="color" data-page-color-key="footer_background_color" value="' + escapeHtml(normalizeColorPickerValue(state.page.footer_background_color || '', '#ffffff')) + '" style="width:52px;height:42px;padding:4px;"></div></div>' +
                    '<div class="cms-builder-field"><label>文字颜色</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-page-key="footer_text_color" value="' + escapeHtml(state.page.footer_text_color || '') + '" placeholder="#333333"><input type="color" data-page-color-key="footer_text_color" value="' + escapeHtml(normalizeColorPickerValue(state.page.footer_text_color || '', '#333333')) + '" style="width:52px;height:42px;padding:4px;"></div></div>' +
                '</div>' +
            '</div>' +
        '</div>';
    }

    function renderPageSettings() {
        var tab = state.stylePanelTab || 'theme';
        var content = renderThemePanelContent();
        if (tab === 'basic') {
            content = renderBasicPanelContent();
        } else if (tab === 'layout') {
            content = renderLayoutPanelContent();
        } else if (tab === 'footer') {
            content = renderFooterPanelContent();
        }
        return '<div class="cms-builder-style-shell">' + renderStylePanelTabs() + content + '</div>';
    }

    function settingsPanelTabs() {
        return [
            {key: 'basic', label: '基础设置'},
            {key: 'page', label: '页面设置'},
            {key: 'advanced', label: '高级设置'}
        ];
    }

    function widgetGroups() {
        return [
            {
                key: 'common',
                title: '常用组件',
                items: [
                    {key: 'floating_service', label: '悬浮客服'},
                    {key: 'site_sidebar', label: '网站侧栏'},
                    {key: 'top_ad', label: '网站顶部广告'},
                    {key: 'bottom_float', label: '网站底部浮层'},
                    {key: 'member_bar', label: '会员登录条'}
                ]
            },
            {
                key: 'featured',
                title: '特色组件',
                badge: '标',
                items: [
                    {key: 'home_popup', label: '首页弹窗'},
                    {key: 'elevator_nav', label: '电梯导航'},
                    {key: 'welcome_screen', label: '欢迎页面'},
                    {key: 'counter', label: '计数器'},
                    {key: 'background_music', label: '背景音乐'}
                ]
            }
        ];
    }

    function widgetLabel(key) {
        var label = key;
        widgetGroups().forEach(function (group) {
            group.items.forEach(function (item) {
                if (item.key === key) {
                    label = item.label;
                }
            });
        });
        return label;
    }

    function widgetConfigSchema() {
        return {
            top_ad: [
                {key: 'text', label: '广告文案', type: 'text', placeholder: '网站顶部广告：立即领取专属咨询通道'},
                {key: 'link_url', label: '跳转链接', type: 'text', placeholder: siteGuestbookUrl()}
            ],
            member_bar: [
                {key: 'login_text', label: '登录文案', type: 'text', placeholder: '会员登录'},
                {key: 'register_text', label: '注册文案', type: 'text', placeholder: '立即注册'}
            ],
            floating_service: [
                {key: 'guestbook_url', label: '咨询链接', type: 'text', placeholder: siteGuestbookUrl()},
                {key: 'phone', label: '电话', type: 'text', placeholder: '400-000-0000'},
                {key: 'qq', label: 'QQ', type: 'text', placeholder: '80000000'},
                {key: 'wechat', label: '微信', type: 'text', placeholder: '客服微信号'},
                {key: 'email', label: '邮箱', type: 'text', placeholder: 'service@example.com'}
            ],
            site_sidebar: [
                {key: 'product_url', label: '产品链接', type: 'text', placeholder: siteProductUrl()},
                {key: 'contact_label', label: '联系文案', type: 'text', placeholder: '联系'}
            ],
            elevator_nav: [
                {key: 'product_url', label: '产品链接', type: 'text', placeholder: siteProductUrl()},
                {key: 'contact_label', label: '联系文案', type: 'text', placeholder: '联系'}
            ],
            bottom_float: [
                {key: 'text', label: '浮层文案', type: 'text', placeholder: '立即获取专属方案'},
                {key: 'button_text', label: '按钮文案', type: 'text', placeholder: '立即咨询'},
                {key: 'button_url', label: '按钮链接', type: 'text', placeholder: siteGuestbookUrl()}
            ],
            counter: [
                {key: 'label', label: '计数文案', type: 'text', placeholder: '本页访问'}
            ],
            background_music: [
                {key: 'audio_url', label: '音频地址', type: 'text', placeholder: '/uploads/music.mp3'},
                {key: 'button_text', label: '按钮文案', type: 'text', placeholder: '播放音乐'},
                {key: 'autoplay', label: '自动播放', type: 'checkbox'}
            ],
            home_popup: [
                {key: 'title', label: '弹窗标题', type: 'text', placeholder: '首页弹窗'},
                {key: 'content', label: '弹窗内容', type: 'textarea', placeholder: '这里可以展示活动信息、优惠券或者首页欢迎文案。'},
                {key: 'button_text', label: '按钮文案', type: 'text', placeholder: '立即查看'},
                {key: 'button_url', label: '按钮链接', type: 'text', placeholder: siteGuestbookUrl()}
            ],
            welcome_screen: [
                {key: 'title', label: '欢迎标题', type: 'text', placeholder: 'WELCOME'},
                {key: 'content', label: '欢迎内容', type: 'textarea', placeholder: '欢迎页已开启，适合活动场景或品牌开场。'},
                {key: 'button_text', label: '进入按钮', type: 'text', placeholder: '进入网站'}
            ]
        };
    }

    function widgetConfigValue(widgetKey, fieldKey) {
        var configs = normalizeSiteWidgetConfigs(state.page.site_widget_configs);
        var widgetConfig = configs[widgetKey] || {};
        return typeof widgetConfig[fieldKey] === 'undefined' ? '' : widgetConfig[fieldKey];
    }

    function renderWidgetConfigFields(widgetKey) {
        var schema = widgetConfigSchema()[widgetKey] || [];
        var syncActionHtml = '<div style="margin-top:10px;display:flex;justify-content:flex-end;"><button type="button" class="cms-builder-btn cms-builder-btn-light" data-widget-sync-all="' + escapeHtml(widgetKey) + '" style="padding:8px 12px;">同步到所有页面</button></div>';
        if (!schema.length) {
            return syncActionHtml;
        }
        return '<div style="margin-top:10px;padding:12px 14px;border-radius:14px;background:#f8fafc;border:1px solid #e2e8f0;display:grid;gap:10px;">' +
            schema.map(function (field) {
                var value = widgetConfigValue(widgetKey, field.key);
                if (field.type === 'checkbox') {
                    return '<label style="display:flex;align-items:center;gap:8px;color:#334155;font:500 12px/1.7 &quot;Microsoft YaHei&quot;,sans-serif;"><input type="checkbox" data-widget-config-key="' + escapeHtml(widgetKey) + '" data-widget-config-field="' + escapeHtml(field.key) + '"' + (value ? ' checked' : '') + '>' + escapeHtml(field.label) + '</label>';
                }
                if (field.type === 'textarea') {
                    return '<div class="cms-builder-field"><label>' + escapeHtml(field.label) + '</label><textarea data-widget-config-key="' + escapeHtml(widgetKey) + '" data-widget-config-field="' + escapeHtml(field.key) + '" placeholder="' + escapeHtml(field.placeholder || '') + '" rows="3">' + escapeHtml(value) + '</textarea></div>';
                }
                return '<div class="cms-builder-field"><label>' + escapeHtml(field.label) + '</label><input type="text" data-widget-config-key="' + escapeHtml(widgetKey) + '" data-widget-config-field="' + escapeHtml(field.key) + '" value="' + escapeHtml(value) + '" placeholder="' + escapeHtml(field.placeholder || '') + '"></div>';
            }).join('') +
            syncActionHtml +
        '</div>';
    }

    function renderSettingsPanelTabs() {
        var activeTab = state.settingsPanelTab || 'basic';
        return '<div class="cms-builder-style-tabs">' +
            settingsPanelTabs().map(function (item) {
                return '<button type="button" class="cms-builder-style-tab' + (activeTab === item.key ? ' is-active' : '') + '" data-settings-panel-tab="' + item.key + '">' + item.label + '</button>';
            }).join('') +
        '</div>';
    }

    function renderSettingsWidgetGroup(group) {
        return '<div class="cms-builder-group">' +
            '<div class="cms-builder-group-title">' + escapeHtml(group.title) + (group.badge ? '<span style="display:inline-flex;align-items:center;justify-content:center;margin-left:8px;padding:2px 6px;border-radius:6px;background:#ffedd5;color:#f97316;font:700 11px/1 &quot;Microsoft YaHei&quot;,sans-serif;">' + escapeHtml(group.badge) + '</span>' : '') + '</div>' +
            '<div class="cms-builder-fields">' +
                group.items.map(function (item) {
                    var checked = state.page.site_widgets && state.page.site_widgets[item.key] ? ' checked' : '';
                    return '<div style="padding:6px 0;color:#334155;font:500 13px/1.7 &quot;Microsoft YaHei&quot;,sans-serif;"><div style="display:flex;align-items:center;justify-content:space-between;gap:12px;"><label style="display:flex;align-items:center;gap:8px;cursor:pointer;"><input type="checkbox" data-widget-toggle="' + escapeHtml(item.key) + '"' + checked + '>' + escapeHtml(item.label) + '</label><button type="button" class="cms-builder-panel-tool" data-widget-preview="' + escapeHtml(item.key) + '" style="border-radius:8px;padding:6px 10px;background:#f8fafc;">预览</button></div>' + renderWidgetConfigFields(item.key) + '</div>';
                }).join('') +
            '</div>' +
        '</div>';
    }

    function renderSettingsBasicContent() {
        var titleVisible = state.page.browser_title_visible ? 'show' : 'hide';
        return '<div class="cms-builder-style-body">' +
            '<div class="cms-builder-group">' +
                '<div class="cms-builder-group-title">浏览器信息</div>' +
                '<div class="cms-builder-fields">' +
                    '<div class="cms-builder-field"><label>浏览器标题层级</label><div class="cms-builder-style-choice"><button type="button" class="cms-builder-style-pill' + (titleVisible === 'show' ? ' is-active' : '') + '" data-browser-title-visible="1">显示</button><button type="button" class="cms-builder-style-pill' + (titleVisible === 'hide' ? ' is-active' : '') + '" data-browser-title-visible="0">隐藏</button></div></div>' +
                    '<div class="cms-builder-field"><label>浏览器图标</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-page-key="favicon_url" value="' + escapeHtml(state.page.favicon_url || '') + '" placeholder="上传或粘贴 favicon 地址"><button type="button" class="cms-builder-btn cms-builder-btn-light" data-page-upload-key="favicon_url" style="flex:0 0 auto;white-space:nowrap;background:#111827;color:#fff;border-color:#111827;">添加图片</button></div><div style="margin-top:8px;color:#94a3b8;font:500 12px/1.6 &quot;Microsoft YaHei&quot;,sans-serif;">建议尺寸：16*16px</div></div>' +
                '</div>' +
            '</div>' +
            '<div class="cms-builder-group"><div class="cms-builder-group-title">基础组件</div><div class="cms-builder-group-desc">该区域用于保留全站组件位，后续可继续扩充为更多系统挂件。</div></div>' +
            widgetGroups().map(renderSettingsWidgetGroup).join('') +
        '</div>';
    }

    function renderSettingsPageContent() {
        return '<div class="cms-builder-style-body">' +
            '<div class="cms-builder-group"><div class="cms-builder-group-title">页面交互</div><div class="cms-builder-group-desc">当前前台装修已切回模板区块模式。鼠标移入显示管理按钮，点击“编辑”后再打开属性面板；页面修改会同步保存到数据库页面 JSON。</div></div>' +
            '<div class="cms-builder-group"><div class="cms-builder-group-title">当前状态</div><div class="cms-builder-group-desc">当前设备：' + escapeHtml(currentDevice() === 'mobile' ? '移动端' : (currentDevice()==='tablet'?'平板':'PC')) + '<br>编辑模式：模板区块模式<br>已选中模块：' + escapeHtml(state.selectedPath || '未选中') + '</div></div>' +
            '<div class="cms-builder-group"><div class="cms-builder-group-title">模板模式说明</div><div class="cms-builder-group-desc">已取消自由画布。系统会自动清理历史自由模块数据，后续发布和搬家时只依赖数据库中的模板装修配置。</div></div>' +
            '<div class="cms-builder-group"><div class="cms-builder-group-title">快捷入口</div><div class="cms-builder-style-quick-grid"><button type="button" class="cms-builder-style-quick-btn" data-style-locate-part="header">顶部区</button><button type="button" class="cms-builder-style-quick-btn" data-style-locate-part="layout">版式区</button></div></div>' +
        '</div>';
    }

    function renderSettingsAdvancedContent() {
        var advanced = state.page.advanced_options || defaultAdvancedOptions();
        return '<div class="cms-builder-style-body">' +
            '<div class="cms-builder-group">' +
                '<div class="cms-builder-group-title">高级设置</div>' +
                '<div class="cms-builder-fields">' +
                    '<label style="display:flex;align-items:center;gap:8px;color:#334155;font:500 13px/1.7 &quot;Microsoft YaHei&quot;,sans-serif;"><input type="checkbox" data-advanced-toggle="production_info"' + (advanced.production_info ? ' checked' : '') + '>制作信息</label>' +
                    '<label style="display:flex;align-items:center;gap:8px;color:#334155;font:500 13px/1.7 &quot;Microsoft YaHei&quot;,sans-serif;"><input type="checkbox" data-advanced-toggle="icp"' + (advanced.icp ? ' checked' : '') + '>工信部备案号</label>' +
                    '<label style="display:flex;align-items:center;gap:8px;color:#334155;font:500 13px/1.7 &quot;Microsoft YaHei&quot;,sans-serif;"><input type="checkbox" data-advanced-toggle="public_security"' + (advanced.public_security ? ' checked' : '') + '>公安部备案号</label>' +
                    '<label style="display:flex;align-items:center;gap:8px;color:#334155;font:500 13px/1.7 &quot;Microsoft YaHei&quot;,sans-serif;"><input type="checkbox" data-advanced-toggle="admin_login"' + (advanced.admin_login ? ' checked' : '') + '>管理员登录</label>' +
                    '<label style="display:flex;align-items:center;gap:8px;color:#334155;font:500 13px/1.7 &quot;Microsoft YaHei&quot;,sans-serif;"><input type="checkbox" data-advanced-toggle="mobile_site"' + (advanced.mobile_site ? ' checked' : '') + '>手机版</label>' +
                '</div>' +
            '</div>' +
        '</div>';
    }

    function renderSettingsLibraryContent() {
        var tab = state.settingsPanelTab || 'basic';
        var content = renderSettingsBasicContent();
        if (tab === 'page') {
            content = renderSettingsPageContent();
        } else if (tab === 'advanced') {
            content = renderSettingsAdvancedContent();
        }
        return '<div class="cms-builder-style-shell">' + renderSettingsPanelTabs() + content + '</div>';
    }

    function selectedExtraConfig() {
        if (!state.selectedNode) {
            return null;
        }
        var wrapper = state.selectedNode.closest('[data-builder-extra-id]');
        if (!wrapper) {
            return null;
        }
        var extraId = wrapper.getAttribute('data-builder-extra-id');
        for (var i = 0; i < state.page.frontend_extra_sections.length; i++) {
            if (String(state.page.frontend_extra_sections[i].id) === String(extraId)) {
                return state.page.frontend_extra_sections[i];
            }
        }
        return null;
    }

    function renderBusinessSettings(extra) {
        if (!extra) {
            return '';
        }
        var config = extra.config || {};
        var styleDeviceKey = currentDevice() === 'mobile' ? 'mobile' : 'pc';
        var mobileStyleState = extra && extra.config && typeof extra.config.mobile_style === 'object' ? extra.config.mobile_style : {};
        var styleFieldsHtml = renderStyleStateFields(selectedExtraStyleState(extra), styleDeviceKey);
        styleFieldsHtml += '<div class="cms-builder-group" style="margin-top:16px;"><div class="cms-builder-group-title">移动端独立样式</div><div class="cms-builder-fields">' +
            '<div class="cms-builder-field"><label>仅移动端显示</label><select data-extra-style-key="mobile:show_mobile_only">' +
                '<option value="0"' + (String(styleValueOrDefault(mobileStyleState, 'show_mobile_only', '0')) === '0' ? ' selected' : '') + '>否</option>' +
                '<option value="1"' + (String(styleValueOrDefault(mobileStyleState, 'show_mobile_only', '0')) === '1' ? ' selected' : '') + '>是</option>' +
            '</select></div>' +
            '<div class="cms-builder-field"><label>移动端隐藏</label><select data-extra-style-key="mobile:hide_mobile">' +
                '<option value="0"' + (String(styleValueOrDefault(mobileStyleState, 'hide_mobile', '0')) === '0' ? ' selected' : '') + '>不隐藏</option>' +
                '<option value="1"' + (String(styleValueOrDefault(mobileStyleState, 'hide_mobile', '0')) === '1' ? ' selected' : '') + '>隐藏</option>' +
            '</select></div>' +
        '</div></div>';
        var articleOptions = '<option value="0">全部文章分类</option>';
        state.businessOptions.article_categories.forEach(function (item) {
            articleOptions += '<option value="' + escapeHtml(item.id) + '"' + (String(config.channel_id || 0) === String(item.id) ? ' selected' : '') + '>' + escapeHtml(item.channel_name || ('分类 ' + item.id)) + '</option>';
        });
        var productOptions = '<option value="0">全部产品分类</option>';
        state.businessOptions.product_categories.forEach(function (item) {
            productOptions += '<option value="' + escapeHtml(item.id) + '"' + (String(config.channel_id || 0) === String(item.id) ? ' selected' : '') + '>' + escapeHtml(item.channel_name || ('分类 ' + item.id)) + '</option>';
        });
        var navOptions = '<option value="auto">自动读取当前导航</option>';
        currentColumnOptions().forEach(function (item) {
            navOptions += '<option value="' + escapeHtml(item.value || '') + '"' + (String(config.active_url || 'auto') === String(item.value || '') ? ' selected' : '') + '>' + escapeHtml(item.label || item.value || '') + '</option>';
        });
        if (extra.type === 'text') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">文本模块设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>文本类型</label><select data-extra-key="mode">' +
                            '<option value="single"' + (config.mode === 'single' ? ' selected' : '') + '>单行文本</option>' +
                            '<option value="multiline"' + (config.mode === 'multiline' ? ' selected' : '') + '>多行文本</option>' +
                            '<option value="title"' + (config.mode === 'title' ? ' selected' : '') + '>标题</option>' +
                            '<option value="paragraph"' + ((config.mode || 'paragraph') === 'paragraph' ? ' selected' : '') + '>段落</option>' +
                            '<option value="rich"' + (config.mode === 'rich' ? ' selected' : '') + '>富文本</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>文本内容</label><textarea data-extra-key="text">' + escapeHtml(config.text || '请输入文本内容') + '</textarea></div>' +
                        '<div class="cms-builder-field"><label>富文本 HTML</label><textarea data-extra-key="html" rows="6">' + escapeHtml(config.html || '<p>请输入富文本内容</p>') + '</textarea><button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_open_rich_editor" style="width:100%;margin-top:8px;">使用新版 UEditor 编辑</button></div>' +
                        '<div class="cms-builder-field"><label>文本对齐</label><select data-extra-key="align">' +
                            '<option value="left"' + ((config.align || 'left') === 'left' ? ' selected' : '') + '>左对齐</option>' +
                            '<option value="center"' + (config.align === 'center' ? ' selected' : '') + '>居中</option>' +
                            '<option value="right"' + (config.align === 'right' ? ' selected' : '') + '>右对齐</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>文字颜色</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-extra-key="color" value="' + escapeHtml(config.color || '#0f172a') + '" placeholder="#0f172a"><input type="color" data-extra-color-key="color" value="' + escapeHtml(normalizeColorPickerValue(config.color || '#0f172a', '#0f172a')) + '" style="width:52px;height:42px;padding:4px 6px;border-radius:10px;flex:0 0 52px;cursor:pointer;"></div></div>' +
                        '<div class="cms-builder-field"><label>字号</label><input type="number" data-extra-key="font_size" value="' + escapeHtml(config.font_size || 16) + '"></div>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'image') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">图片模块设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>图片地址</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-extra-key="image_url" value="' + escapeHtml(config.image_url || '') + '" placeholder="可直接粘贴图片地址"><button type="button" class="cms-builder-btn cms-builder-btn-light" data-extra-upload-key="image_url" style="flex:0 0 auto;white-space:nowrap;background:#111827;color:#ffffff;border-color:#111827;">上传本地图片</button></div></div>' +
                        '<div class="cms-builder-field"><label>图片说明</label><input type="text" data-extra-key="alt" value="' + escapeHtml(config.alt || '') + '" placeholder="图片描述"></div>' +
                        '<div class="cms-builder-field"><label>跳转链接</label><input type="text" data-extra-key="link_url" value="' + escapeHtml(config.link_url || '') + '" placeholder="https://example.com"></div>' +
                        '<div class="cms-builder-field"><label>打开方式</label><select data-extra-key="target">' +
                            '<option value="_self"' + ((config.target || '_self') === '_self' ? ' selected' : '') + '>当前窗口</option>' +
                            '<option value="_blank"' + (config.target === '_blank' ? ' selected' : '') + '>新窗口</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>宽度模式</label><select data-extra-key="width_mode">' +
                            '<option value="full"' + ((config.width_mode || 'full') === 'full' ? ' selected' : '') + '>铺满容器</option>' +
                            '<option value="auto"' + (config.width_mode === 'auto' ? ' selected' : '') + '>原始比例</option>' +
                            '<option value="custom"' + (config.width_mode === 'custom' ? ' selected' : '') + '>自定义宽度</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>自定义宽度</label><input type="number" data-extra-key="width" value="' + escapeHtml(config.width || 480) + '"></div>' +
                        '<div class="cms-builder-field"><label>显示高度</label><input type="number" data-extra-key="height" value="' + escapeHtml(config.height || 320) + '"></div>' +
                        '<div class="cms-builder-field"><label>缩放方式</label><select data-extra-key="fit">' +
                            '<option value="cover"' + ((config.fit || 'cover') === 'cover' ? ' selected' : '') + '>裁剪铺满</option>' +
                            '<option value="contain"' + (config.fit === 'contain' ? ' selected' : '') + '>完整显示</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>对齐方式</label><select data-extra-key="align">' +
                            '<option value="left"' + (config.align === 'left' ? ' selected' : '') + '>左对齐</option>' +
                            '<option value="center"' + ((config.align || 'center') === 'center' ? ' selected' : '') + '>居中</option>' +
                            '<option value="right"' + (config.align === 'right' ? ' selected' : '') + '>右对齐</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>圆角</label><input type="number" data-extra-key="radius" value="' + escapeHtml(config.radius || 16) + '"></div>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'button') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">按钮模块设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>按钮文字</label><input type="text" data-extra-key="text" value="' + escapeHtml(config.text || '立即咨询') + '"></div>' +
                        '<div class="cms-builder-field"><label>交互方式</label><select data-extra-key="action_type">' +
                            '<option value="link"' + (((config.action_type || 'link') === 'link') ? ' selected' : '') + '>普通链接</option>' +
                            '<option value="new_window"' + (config.action_type === 'new_window' ? ' selected' : '') + '>新窗口打开</option>' +
                            '<option value="phone"' + (config.action_type === 'phone' ? ' selected' : '') + '>拨打电话</option>' +
                            '<option value="email"' + (config.action_type === 'email' ? ' selected' : '') + '>发送邮件</option>' +
                            '<option value="anchor"' + (config.action_type === 'anchor' ? ' selected' : '') + '>页面锚点</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>交互值</label><input type="text" data-extra-key="action_value" value="' + escapeHtml(config.action_value || '') + '" placeholder="链接 / 电话 / 邮箱 / #锚点"></div>' +
                        '<div class="cms-builder-field"><label>按钮尺寸</label><select data-extra-key="size">' +
                            '<option value="sm"' + (config.size === 'sm' ? ' selected' : '') + '>小</option>' +
                            '<option value="md"' + (((config.size || 'md') === 'md') ? ' selected' : '') + '>中</option>' +
                            '<option value="lg"' + (config.size === 'lg' ? ' selected' : '') + '>大</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>按钮样式</label><select data-extra-key="variant">' +
                            '<option value="solid"' + (((config.variant || 'solid') === 'solid') ? ' selected' : '') + '>实心</option>' +
                            '<option value="outline"' + (config.variant === 'outline' ? ' selected' : '') + '>描边</option>' +
                            '<option value="ghost"' + (config.variant === 'ghost' ? ' selected' : '') + '>透明</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>图标</label><select data-extra-key="icon">' +
                            '<option value=""' + ((config.icon || '') === '' ? ' selected' : '') + '>无图标</option>' +
                            '<option value="arrow"' + (config.icon === 'arrow' ? ' selected' : '') + '>箭头</option>' +
                            '<option value="phone"' + (config.icon === 'phone' ? ' selected' : '') + '>电话</option>' +
                            '<option value="mail"' + (config.icon === 'mail' ? ' selected' : '') + '>邮箱</option>' +
                            '<option value="star"' + (config.icon === 'star' ? ' selected' : '') + '>星标</option>' +
                            '<option value="plus"' + (config.icon === 'plus' ? ' selected' : '') + '>加号</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>图标位置</label><select data-extra-key="icon_position">' +
                            '<option value="left"' + (((config.icon_position || 'left') === 'left') ? ' selected' : '') + '>左侧</option>' +
                            '<option value="right"' + (config.icon_position === 'right' ? ' selected' : '') + '>右侧</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>背景颜色</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-extra-key="bg_color" value="' + escapeHtml(config.bg_color || '#111827') + '" placeholder="#111827"><input type="color" data-extra-color-key="bg_color" value="' + escapeHtml(normalizeColorPickerValue(config.bg_color || '#111827', '#111827')) + '" style="width:52px;height:42px;padding:4px 6px;border-radius:10px;flex:0 0 52px;cursor:pointer;"></div></div>' +
                        '<div class="cms-builder-field"><label>文字颜色</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-extra-key="text_color" value="' + escapeHtml(config.text_color || '#ffffff') + '" placeholder="#ffffff"><input type="color" data-extra-color-key="text_color" value="' + escapeHtml(normalizeColorPickerValue(config.text_color || '#ffffff', '#ffffff')) + '" style="width:52px;height:42px;padding:4px 6px;border-radius:10px;flex:0 0 52px;cursor:pointer;"></div></div>' +
                        '<div class="cms-builder-field"><label>圆角风格</label><select data-extra-key="radius_mode">' +
                            '<option value="pill"' + (((config.radius_mode || 'pill') === 'pill') ? ' selected' : '') + '>胶囊</option>' +
                            '<option value="rounded"' + (config.radius_mode === 'rounded' ? ' selected' : '') + '>圆角</option>' +
                            '<option value="square"' + (config.radius_mode === 'square' ? ' selected' : '') + '>直角</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>对齐方式</label><select data-extra-key="align">' +
                            '<option value="left"' + (config.align === 'left' ? ' selected' : '') + '>左对齐</option>' +
                            '<option value="center"' + (((config.align || 'center') === 'center') ? ' selected' : '') + '>居中</option>' +
                            '<option value="right"' + (config.align === 'right' ? ' selected' : '') + '>右对齐</option>' +
                        '</select></div>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'article_list') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">文章列表设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>模块标题</label><input type="text" data-extra-key="title" value="' + escapeHtml(config.title || '文章列表') + '"></div>' +
                        '<div class="cms-builder-field"><label>文章分类</label><select data-extra-key="channel_id">' + articleOptions + '</select></div>' +
                        '<div class="cms-builder-field"><label>标签筛选</label><input type="text" data-extra-key="tag" value="' + escapeHtml(config.tag || '') + '" placeholder="按标签过滤"></div>' +
                        '<div class="cms-builder-field"><label>数据关键词</label><input type="text" data-extra-key="keyword" value="' + escapeHtml(config.keyword || '') + '" placeholder="标题 / 标签 / SEO 关键词"></div>' +
                        '<div class="cms-builder-field"><label>发布时间起</label><input type="date" data-extra-key="time_from" value="' + escapeHtml(config.time_from || '') + '"></div>' +
                        '<div class="cms-builder-field"><label>发布时间止</label><input type="date" data-extra-key="time_to" value="' + escapeHtml(config.time_to || '') + '"></div>' +
                        '<div class="cms-builder-field"><label>显示数量</label><input type="number" data-extra-key="limit" value="' + escapeHtml(config.limit || 6) + '"></div>' +
                        '<div class="cms-builder-field"><label>排序方式</label><select data-extra-key="order_by">' +
                            '<option value="id_desc"' + ((config.order_by || 'id_desc') === 'id_desc' ? ' selected' : '') + '>最新 ID</option>' +
                            '<option value="id_asc"' + (config.order_by === 'id_asc' ? ' selected' : '') + '>最早 ID</option>' +
                            '<option value="time_desc"' + (config.order_by === 'time_desc' ? ' selected' : '') + '>最新时间</option>' +
                            '<option value="time_asc"' + (config.order_by === 'time_asc' ? ' selected' : '') + '>最早时间</option>' +
                            '<option value="sort_asc"' + (config.order_by === 'sort_asc' ? ' selected' : '') + '>排序正序</option>' +
                            '<option value="sort_desc"' + (config.order_by === 'sort_desc' ? ' selected' : '') + '>排序倒序</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>展示样式</label><select data-extra-key="variant">' +
                            '<option value="default"' + ((config.variant || 'default') === 'default' ? ' selected' : '') + '>默认列表</option>' +
                            '<option value="timeline"' + (config.variant === 'timeline' ? ' selected' : '') + '>时间线</option>' +
                            '<option value="magazine"' + (config.variant === 'magazine' ? ' selected' : '') + '>杂志卡片</option>' +
                            '<option value="compact"' + (config.variant === 'compact' ? ' selected' : '') + '>紧凑列表</option>' +
                            '<option value="highlight"' + (config.variant === 'highlight' ? ' selected' : '') + '>高亮首条</option>' +
                        '</select></div>' +
                        '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_reload_business">刷新数据</button>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'product_list') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">产品展示设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>模块标题</label><input type="text" data-extra-key="title" value="' + escapeHtml(config.title || '产品展示') + '"></div>' +
                        '<div class="cms-builder-field"><label>产品分类</label><select data-extra-key="channel_id">' + productOptions + '</select></div>' +
                        '<div class="cms-builder-field"><label>标签筛选</label><input type="text" data-extra-key="tag" value="' + escapeHtml(config.tag || '') + '" placeholder="按标签过滤"></div>' +
                        '<div class="cms-builder-field"><label>数据关键词</label><input type="text" data-extra-key="keyword" value="' + escapeHtml(config.keyword || '') + '" placeholder="标题 / 标签 / SEO 关键词"></div>' +
                        '<div class="cms-builder-field"><label>发布时间起</label><input type="date" data-extra-key="time_from" value="' + escapeHtml(config.time_from || '') + '"></div>' +
                        '<div class="cms-builder-field"><label>发布时间止</label><input type="date" data-extra-key="time_to" value="' + escapeHtml(config.time_to || '') + '"></div>' +
                        '<div class="cms-builder-field"><label>显示数量</label><input type="number" data-extra-key="limit" value="' + escapeHtml(config.limit || 6) + '"></div>' +
                        '<div class="cms-builder-field"><label>排序方式</label><select data-extra-key="order_by">' +
                            '<option value="id_desc"' + ((config.order_by || 'id_desc') === 'id_desc' ? ' selected' : '') + '>最新 ID</option>' +
                            '<option value="id_asc"' + (config.order_by === 'id_asc' ? ' selected' : '') + '>最早 ID</option>' +
                            '<option value="time_desc"' + (config.order_by === 'time_desc' ? ' selected' : '') + '>最新时间</option>' +
                            '<option value="time_asc"' + (config.order_by === 'time_asc' ? ' selected' : '') + '>最早时间</option>' +
                            '<option value="sort_asc"' + (config.order_by === 'sort_asc' ? ' selected' : '') + '>排序正序</option>' +
                            '<option value="sort_desc"' + (config.order_by === 'sort_desc' ? ' selected' : '') + '>排序倒序</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>展示样式</label><select data-extra-key="variant">' +
                            '<option value="default"' + ((config.variant || 'default') === 'default' ? ' selected' : '') + '>默认卡片</option>' +
                            '<option value="showcase"' + (config.variant === 'showcase' ? ' selected' : '') + '>橱窗样式</option>' +
                            '<option value="compact"' + (config.variant === 'compact' ? ' selected' : '') + '>紧凑列表</option>' +
                            '<option value="cards"' + (config.variant === 'cards' ? ' selected' : '') + '>网格卡片</option>' +
                            '<option value="minimal"' + (config.variant === 'minimal' ? ' selected' : '') + '>极简样式</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>价格显示</label><select data-extra-key="show_price">' +
                            '<option value="1"' + (String(config.show_price || 1) === '1' ? ' selected' : '') + '>显示</option>' +
                            '<option value="0"' + (String(config.show_price) === '0' ? ' selected' : '') + '>隐藏</option>' +
                        '</select></div>' +
                        '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_reload_business">刷新数据</button>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'carousel') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">轮播图设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>模块标题</label><input type="text" data-extra-key="title" value="' + escapeHtml(config.title || '轮播图') + '"></div>' +
                        '<div class="cms-builder-field"><label>自动播放秒数</label><input type="number" data-extra-key="autoplay_seconds" value="' + escapeHtml(config.autoplay_seconds || 4) + '"></div>' +
                        '<div class="cms-builder-field"><label>图片高度</label><input type="number" data-extra-key="height" value="' + escapeHtml(config.height || 420) + '"></div>' +
                        '<div class="cms-builder-field"><label>展示样式</label><select data-extra-key="variant">' +
                            '<option value="banner"' + ((config.variant || 'banner') === 'banner' ? ' selected' : '') + '>横幅</option>' +
                            '<option value="cards"' + (config.variant === 'cards' ? ' selected' : '') + '>卡片轮播</option>' +
                            '<option value="fade"' + (config.variant === 'fade' ? ' selected' : '') + '>淡入淡出</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>指示器</label><select data-extra-key="show_dots">' +
                            '<option value="1"' + (String(config.show_dots != null ? config.show_dots : 1) === '1' ? ' selected' : '') + '>显示</option>' +
                            '<option value="0"' + (String(config.show_dots) === '0' ? ' selected' : '') + '>隐藏</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>使用后台幻灯片</label><select data-extra-key="data_source">' +
                            '<option value="slides"' + (((config.data_source || 'slides') === 'slides') ? ' selected' : '') + '>是</option>' +
                            '<option value="custom"' + (config.data_source === 'custom' ? ' selected' : '') + '>否，自定义轮播图</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>自定义轮播图</label><textarea data-extra-key="items" rows="6" placeholder="每行一项：图片地址|标题|链接">' + escapeHtml(config.items || '') + '</textarea></div>' +
                        '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_reload_business">刷新轮播</button>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'image_text') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">图文展示设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>模块标题</label><input type="text" data-extra-key="title" value="' + escapeHtml(config.title || '图文展示') + '"></div>' +
                        '<div class="cms-builder-field"><label>说明副标题</label><input type="text" data-extra-key="subtitle" value="' + escapeHtml(config.subtitle || '支持左图右文、右图左文、上图下文等排版') + '"></div>' +
                        '<div class="cms-builder-field"><label>版式</label><select data-extra-key="layout">' +
                            '<option value="left-image"' + (((config.layout || 'left-image') === 'left-image') ? ' selected' : '') + '>左图右文</option>' +
                            '<option value="right-image"' + (config.layout === 'right-image' ? ' selected' : '') + '>右图左文</option>' +
                            '<option value="top-image"' + (config.layout === 'top-image' ? ' selected' : '') + '>上图下文</option>' +
                            '<option value="bottom-image"' + (config.layout === 'bottom-image' ? ' selected' : '') + '>上文下图</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>图片地址</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-extra-key="image_url" value="' + escapeHtml(config.image_url || '') + '" placeholder="可直接粘贴图片地址"><button type="button" class="cms-builder-btn cms-builder-btn-light" data-extra-upload-key="image_url" style="flex:0 0 auto;white-space:nowrap;background:#111827;color:#ffffff;border-color:#111827;">上传本地图片</button></div></div>' +
                        '<div class="cms-builder-field"><label>主标题</label><input type="text" data-extra-key="heading" value="' + escapeHtml(config.heading || '模块主标题') + '"></div>' +
                        '<div class="cms-builder-field"><label>正文内容</label><textarea data-extra-key="content" rows="5">' + escapeHtml(config.content || '这里填写图文模块的介绍内容，适合品牌介绍、优势说明、服务介绍等。') + '</textarea></div>' +
                        '<div class="cms-builder-field"><label>按钮文字</label><input type="text" data-extra-key="button_text" value="' + escapeHtml(config.button_text || '了解更多') + '"></div>' +
                        '<div class="cms-builder-field"><label>按钮链接</label><input type="text" data-extra-key="button_url" value="' + escapeHtml(config.button_url || '') + '" placeholder="https://example.com 或 /public/index.php?..."></div>' +
                        '<div class="cms-builder-field"><label>间距</label><input type="number" data-extra-key="gap" value="' + escapeHtml(config.gap || 24) + '"></div>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'multi_image') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">列表多图设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>模块标题</label><input type="text" data-extra-key="title" value="' + escapeHtml(config.title || '列表多图') + '"></div>' +
                        '<div class="cms-builder-field"><label>列数</label><input type="number" data-extra-key="columns" value="' + escapeHtml(config.columns || 4) + '"></div>' +
                        '<div class="cms-builder-field"><label>图片高度</label><input type="number" data-extra-key="image_height" value="' + escapeHtml(config.image_height || 180) + '"></div>' +
                        '<div class="cms-builder-field"><label>模块间距</label><input type="number" data-extra-key="gap" value="' + escapeHtml(config.gap || 16) + '"></div>' +
                        '<div class="cms-builder-field"><label>卡片圆角</label><input type="number" data-extra-key="radius" value="' + escapeHtml(config.radius || 18) + '"></div>' +
                        '<div class="cms-builder-field"><label>展示项目</label><textarea data-extra-key="items" rows="8" placeholder="每行一项：图片地址|标题|说明|链接">' + escapeHtml(config.items || '') + '</textarea></div>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'gallery') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">图册目录设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>模块标题</label><input type="text" data-extra-key="title" value="' + escapeHtml(config.title || '图册目录') + '"></div>' +
                        '<div class="cms-builder-field"><label>说明文字</label><input type="text" data-extra-key="subtitle" value="' + escapeHtml(config.subtitle || '点击缩略图可查看大图') + '"></div>' +
                        '<div class="cms-builder-field"><label>缩略图高度</label><input type="number" data-extra-key="thumb_height" value="' + escapeHtml(config.thumb_height || 160) + '"></div>' +
                        '<div class="cms-builder-field"><label>列数</label><input type="number" data-extra-key="columns" value="' + escapeHtml(config.columns || 4) + '"></div>' +
                        '<div class="cms-builder-field"><label>图片列表</label><textarea data-extra-key="items" rows="8" placeholder="每行一项：缩略图地址|大图地址|标题">' + escapeHtml(config.items || '') + '</textarea></div>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'video_embed') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">在线视频设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>模块标题</label><input type="text" data-extra-key="title" value="' + escapeHtml(config.title || '在线视频') + '"></div>' +
                        '<div class="cms-builder-field"><label>说明文字</label><input type="text" data-extra-key="subtitle" value="' + escapeHtml(config.subtitle || '支持本地视频上传或外链嵌入') + '"></div>' +
                        '<div class="cms-builder-field"><label>视频模式</label><select data-extra-key="mode">' +
                            '<option value="upload"' + (((config.mode || 'upload') === 'upload') ? ' selected' : '') + '>本地视频 / MP4 地址</option>' +
                            '<option value="embed"' + (config.mode === 'embed' ? ' selected' : '') + '>外链嵌入代码</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>视频地址</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-extra-key="video_url" value="' + escapeHtml(config.video_url || '') + '" placeholder="/uploads/video/... 或 https://...mp4"><button type="button" class="cms-builder-btn cms-builder-btn-light" data-extra-upload-key="video_url" data-extra-upload-type="video" style="flex:0 0 auto;white-space:nowrap;background:#111827;color:#ffffff;border-color:#111827;">上传本地视频</button></div></div>' +
                        '<div class="cms-builder-field"><label>封面图</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-extra-key="poster_url" value="' + escapeHtml(config.poster_url || '') + '" placeholder="视频封面图"><button type="button" class="cms-builder-btn cms-builder-btn-light" data-extra-upload-key="poster_url" style="flex:0 0 auto;white-space:nowrap;background:#111827;color:#ffffff;border-color:#111827;">上传封面</button></div></div>' +
                        '<div class="cms-builder-field"><label>嵌入代码</label><textarea data-extra-key="embed_html" rows="5" placeholder="粘贴 iframe/embed 代码">' + escapeHtml(config.embed_html || '') + '</textarea></div>' +
                        '<div class="cms-builder-field"><label>视频高度</label><input type="number" data-extra-key="height" value="' + escapeHtml(config.height || 420) + '"></div>' +
                        '<div class="cms-builder-field"><label>自动播放</label><select data-extra-key="autoplay">' +
                            '<option value="0"' + (String(config.autoplay || 0) === '0' ? ' selected' : '') + '>关闭</option>' +
                            '<option value="1"' + (String(config.autoplay) === '1' ? ' selected' : '') + '>开启</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>循环播放</label><select data-extra-key="loop">' +
                            '<option value="0"' + (String(config.loop || 0) === '0' ? ' selected' : '') + '>关闭</option>' +
                            '<option value="1"' + (String(config.loop) === '1' ? ' selected' : '') + '>开启</option>' +
                        '</select></div>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'channel_nav') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">栏目导航设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>导航标题</label><input type="text" data-extra-key="title" value="' + escapeHtml(config.title || '栏目导航') + '"></div>' +
                        '<div class="cms-builder-field"><label>展示样式</label><select data-extra-key="variant">' +
                            '<option value="pill"' + (((config.variant || 'pill') === 'pill') ? ' selected' : '') + '>胶囊导航</option>' +
                            '<option value="line"' + (config.variant === 'line' ? ' selected' : '') + '>横线导航</option>' +
                            '<option value="grid"' + (config.variant === 'grid' ? ' selected' : '') + '>网格导航</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>当前高亮</label><select data-extra-key="active_url">' + navOptions + '</select></div>' +
                        '<div class="cms-builder-field"><label>导航项目</label><textarea data-extra-key="items" rows="6" placeholder="每行一项：名称|链接">' + escapeHtml(config.items || '') + '</textarea></div>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'guestbook_form') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">留言表单设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>模块标题</label><input type="text" data-extra-key="title" value="' + escapeHtml(config.title || '在线留言') + '"></div>' +
                        '<div class="cms-builder-field"><label>说明文字</label><textarea data-extra-key="subtitle">' + escapeHtml(config.subtitle || '留下您的联系方式和需求，我们会尽快与您联系。') + '</textarea></div>' +
                        '<div class="cms-builder-field"><label>提交按钮文案</label><input type="text" data-extra-key="button_text" value="' + escapeHtml(config.button_text || '提交留言') + '"></div>' +
                        '<div class="cms-builder-field"><label>展示样式</label><select data-extra-key="variant">' +
                            '<option value="default"' + ((config.variant || 'default') === 'default' ? ' selected' : '') + '>默认表单</option>' +
                            '<option value="centered"' + (config.variant === 'centered' ? ' selected' : '') + '>居中卡片</option>' +
                            '<option value="split"' + (config.variant === 'split' ? ' selected' : '') + '>左右分栏</option>' +
                            '<option value="banner"' + (config.variant === 'banner' ? ' selected' : '') + '>横幅 CTA</option>' +
                            '<option value="minimal"' + (config.variant === 'minimal' ? ' selected' : '') + '>极简表单</option>' +
                        '</select></div>' +
                        '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_reload_business">刷新表单</button>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'qrcode') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">二维码设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>标题</label><input type="text" data-extra-key="title" value="' + escapeHtml(config.title || '扫码关注') + '"></div>' +
                        '<div class="cms-builder-field"><label>说明文字</label><input type="text" data-extra-key="subtitle" value="' + escapeHtml(config.subtitle || '微信扫码获取更多信息') + '"></div>' +
                        '<div class="cms-builder-field"><label>二维码图片</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-extra-key="image_url" value="' + escapeHtml(config.image_url || '') + '" placeholder="可直接粘贴图片地址"><button type="button" class="cms-builder-btn cms-builder-btn-light" data-extra-upload-key="image_url" style="flex:0 0 auto;white-space:nowrap;background:#111827;color:#ffffff;border-color:#111827;">上传本地图片</button></div></div>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'friend_links') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">友情链接设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>模块标题</label><input type="text" data-extra-key="title" value="' + escapeHtml(config.title || '友情链接') + '"></div>' +
                        '<div class="cms-builder-field"><label>展示方式</label><select data-extra-key="variant">' +
                            '<option value="text"' + ((config.variant || 'text') === 'text' ? ' selected' : '') + '>文字链接</option>' +
                            '<option value="logo"' + (config.variant === 'logo' ? ' selected' : '') + '>Logo 链接</option>' +
                            '<option value="icon"' + (config.variant === 'icon' ? ' selected' : '') + '>图标链接</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>列数</label><input type="number" data-extra-key="columns" value="' + escapeHtml(config.columns || 4) + '"></div>' +
                        '<div class="cms-builder-field"><label>间距</label><input type="number" data-extra-key="gap" value="' + escapeHtml(config.gap || 14) + '"></div>' +
                        '<div class="cms-builder-field"><label>使用后台友情链接</label><select data-extra-key="data_source">' +
                            '<option value="links"' + (((config.data_source || 'links') === 'links') ? ' selected' : '') + '>是</option>' +
                            '<option value="custom"' + (config.data_source === 'custom' ? ' selected' : '') + '>否，自定义链接</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>自定义链接</label><textarea data-extra-key="items" rows="7" placeholder="每行一项：名称|链接|图标/Logo地址">' + escapeHtml(config.items || '') + '</textarea></div>' +
                        '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_reload_business">刷新友情链接</button>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'service_panel') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">在线客服设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>模块标题</label><input type="text" data-extra-key="title" value="' + escapeHtml(config.title || '在线客服') + '"></div>' +
                        '<div class="cms-builder-field"><label>说明文字</label><textarea data-extra-key="subtitle">' + escapeHtml(config.subtitle || '支持电话、QQ、微信和邮箱等客服联系方式展示。') + '</textarea></div>' +
                        '<div class="cms-builder-field"><label>电话</label><input type="text" data-extra-key="phone" value="' + escapeHtml(config.phone || '') + '"></div>' +
                        '<div class="cms-builder-field"><label>QQ</label><input type="text" data-extra-key="qq" value="' + escapeHtml(config.qq || '') + '"></div>' +
                        '<div class="cms-builder-field"><label>微信</label><input type="text" data-extra-key="wechat" value="' + escapeHtml(config.wechat || '') + '"></div>' +
                        '<div class="cms-builder-field"><label>邮箱</label><input type="text" data-extra-key="email" value="' + escapeHtml(config.email || '') + '"></div>' +
                        '<div class="cms-builder-field"><label>展示样式</label><select data-extra-key="variant">' +
                            '<option value="cards"' + ((config.variant || 'cards') === 'cards' ? ' selected' : '') + '>卡片式</option>' +
                            '<option value="floating"' + (config.variant === 'floating' ? ' selected' : '') + '>悬浮式</option>' +
                            '<option value="compact"' + (config.variant === 'compact' ? ' selected' : '') + '>紧凑式</option>' +
                        '</select></div>' +
                        '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_reload_business">刷新客服模块</button>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'member_portal') {
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">会员入口设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>模块标题</label><input type="text" data-extra-key="title" value="' + escapeHtml(config.title || '会员中心') + '"></div>' +
                        '<div class="cms-builder-field"><label>登录按钮文案</label><input type="text" data-extra-key="login_text" value="' + escapeHtml(config.login_text || '会员登录') + '"></div>' +
                        '<div class="cms-builder-field"><label>注册按钮文案</label><input type="text" data-extra-key="register_text" value="' + escapeHtml(config.register_text || '立即注册') + '"></div>' +
                        '<div class="cms-builder-field"><label>中心按钮文案</label><input type="text" data-extra-key="center_text" value="' + escapeHtml(config.center_text || '进入会员中心') + '"></div>' +
                        '<div class="cms-builder-field"><label>展示样式</label><select data-extra-key="variant">' +
                            '<option value="card"' + ((config.variant || 'card') === 'card' ? ' selected' : '') + '>卡片式</option>' +
                            '<option value="banner"' + (config.variant === 'banner' ? ' selected' : '') + '>横幅 CTA</option>' +
                            '<option value="minimal"' + (config.variant === 'minimal' ? ' selected' : '') + '>极简入口</option>' +
                        '</select></div>' +
                        '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_reload_business">刷新会员状态</button>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        if (extra.type === 'vote_form') {
            var voteSelectOptions = '<option value="0">请选择投票主题</option>';
            state.businessOptions.votes.forEach(function (item) {
                voteSelectOptions += '<option value="' + escapeHtml(item.id) + '"' + (String(config.vote_id || 0) === String(item.id) ? ' selected' : '') + '>' + escapeHtml(item.vote_title || ('投票 ' + item.id)) + '</option>';
            });
            return '' +
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">在线投票设置</div>' +
                    '<div class="cms-builder-fields">' +
                        '<div class="cms-builder-field"><label>模块标题</label><input type="text" data-extra-key="title" value="' + escapeHtml(config.title || '在线投票') + '"></div>' +
                        '<div class="cms-builder-field"><label>投票主题</label><select data-extra-key="vote_id">' + voteSelectOptions + '</select></div>' +
                        '<div class="cms-builder-field"><label>状态筛选</label><select data-extra-key="status">' +
                            '<option value=""' + ((config.status || '') === '' ? ' selected' : '') + '>全部状态</option>' +
                            '<option value="active"' + (config.status === 'active' ? ' selected' : '') + '>进行中</option>' +
                            '<option value="ended"' + (config.status === 'ended' ? ' selected' : '') + '>已结束</option>' +
                            '<option value="upcoming"' + (config.status === 'upcoming' ? ' selected' : '') + '>未开始</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>关键词</label><input type="text" data-extra-key="keyword" value="' + escapeHtml(config.keyword || '') + '" placeholder="投票标题关键词"></div>' +
                        '<div class="cms-builder-field"><label>显示数量</label><input type="number" data-extra-key="limit" value="' + escapeHtml(config.limit || 10) + '"></div>' +
                        '<div class="cms-builder-field"><label>排序方式</label><select data-extra-key="order_by">' +
                            '<option value="id_desc"' + ((config.order_by || 'id_desc') === 'id_desc' ? ' selected' : '') + '>最新 ID</option>' +
                            '<option value="time_desc"' + (config.order_by === 'time_desc' ? ' selected' : '') + '>最新时间</option>' +
                            '<option value="time_asc"' + (config.order_by === 'time_asc' ? ' selected' : '') + '>最早时间</option>' +
                        '</select></div>' +
                        '<div class="cms-builder-field"><label>按钮文案</label><input type="text" data-extra-key="button_text" value="' + escapeHtml(config.button_text || '立即投票') + '"></div>' +
                        '<div class="cms-builder-field"><label>结果显示</label><select data-extra-key="show_result">' +
                            '<option value="1"' + (String(config.show_result || 1) === '1' ? ' selected' : '') + '>显示票数</option>' +
                            '<option value="0"' + (String(config.show_result) === '0' ? ' selected' : '') + '>隐藏票数</option>' +
                        '</select></div>' +
                        '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_reload_business">刷新投票</button>' +
                    '</div>' +
                '</div>' + styleFieldsHtml;
        }
        return '';
    }

    function renderSettings() {
        var container = qs('#cms_builder_settings');
        if (!container) {
            return;
        }
        if (selectedCanvasIds().length > 1) {
            container.innerHTML = renderCanvasSelectionSettings();
            syncDeviceButtons();
            return;
        }
        if (state.selectedCanvasBlockId) {
            container.innerHTML = renderCanvasSettings();
            syncDeviceButtons();
            return;
        }
        if (!state.selectedNode) {
            container.innerHTML = '<div class="cms-builder-empty">请选择画布模块或页面区块后，在这里编辑当前模块属性。</div>';
            syncDeviceButtons();
            return;
        }
        var meta = activeNodeMeta(state.selectedNode);
        var selectedRef = nodeRef(state.selectedNode);
        var isHidden = listHasRef(currentHiddenPaths(), selectedRef);
        var isLocked = listHasRef((state.page.frontend_locked_paths || []), selectedRef);
        var isProtected = isProtectedBlockNode(currentBlockNode() || state.selectedNode);
        var primaryAction = blockPrimaryActionMeta(state.selectedNode);
        var selectedExtra = selectedExtraConfig();
        var selectedExtraImage = selectedExtraImageNode();
        var canInsertImage = canInsertImageIntoSelectedNode();
        var selectedBackgroundColor = selectedStylePropertyValue('background-color');
        var globalContactField = detectGlobalContactField(state.selectedNode, meta);
        var contentFieldsHtml = '';
        var insertActionsHtml = '';
        var globalContactSyncHtml = globalContactField
            ? '<button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_sync_global_contact" data-global-contact-key="' + escapeHtml(globalContactField.key) + '" data-global-contact-label="' + escapeHtml(globalContactField.label) + '" style="margin-top:10px;">同步为全站' + escapeHtml(globalContactField.label) + '</button>'
            : '';
        if (isProtectedEditableNode(state.selectedNode)) {
            container.innerHTML =
                '<div class="cms-builder-group">' +
                    '<div class="cms-builder-group-title">底部版权保护</div>' +
                    '<div class="cms-builder-badges"><span class="cms-builder-badge">受保护区域</span></div>' +
                    '<div class="cms-builder-empty">底部版权区域禁止修改内容、样式、位置、显隐和业务属性。</div>' +
                '</div>';
            syncDeviceButtons();
            return;
        }
        if (selectedExtra && (selectedExtra.type === 'text' || selectedExtra.type === 'image' || selectedExtra.type === 'button' || selectedExtra.type === 'image_text' || selectedExtra.type === 'multi_image' || selectedExtra.type === 'gallery' || selectedExtra.type === 'video_embed' || selectedExtra.type === 'channel_nav')) {
            contentFieldsHtml = '<div class="cms-builder-fields">' +
                '<div class="cms-builder-empty">当前选中的是独立' + escapeHtml(moduleTypeLabel(selectedExtra.type)) + '。请在下方模块设置中编辑内容、链接、样式和交互方式。</div>' +
            '</div>';
        } else if (selectedExtra && selectedExtra.type === 'image' && selectedExtraImage) {
            contentFieldsHtml = '<div class="cms-builder-fields">' +
                '<div class="cms-builder-field"><label>图片地址</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-front-key="src" value="' + escapeHtml(overrideValue('src', selectedExtraImage.getAttribute('src') || meta.src || '')) + '" placeholder="可直接粘贴图片地址"><button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_upload_image" style="flex:0 0 auto;white-space:nowrap;background:#111827;color:#ffffff;border-color:#111827;">上传本地图片</button></div></div>' +
                renderInlineStylePresetField('image', overrideValue('style', selectedExtraImage.getAttribute('style') || meta.style || '')) +
                '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_clear_override">清除当前覆盖</button>' +
            '</div>';
        } else if (primaryAction.kind === 'business') {
            var business = primaryAction.business || businessItemMeta(state.selectedNode);
            contentFieldsHtml = '<div class="cms-builder-fields">' +
                '<div class="cms-builder-empty">当前选中的是后台' + escapeHtml(businessTypeLabel(business ? business.type : 'article')) + '数据卡片。点击下面按钮，会直接弹出数据编辑卡片并同步回原业务表。</div>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_open_business_editor">编辑当前数据</button>' +
                '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_reload_business">刷新业务模块</button>' +
                insertActionsHtml +
                '</div>';
        } else if (primaryAction.kind === 'image') {
            contentFieldsHtml = '<div class="cms-builder-fields">' +
                '<div class="cms-builder-field"><label>图片地址</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-front-key="src" value="' + escapeHtml(overrideValue('src', meta.src || '')) + '" placeholder="可直接粘贴图片地址"><button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_upload_image" style="flex:0 0 auto;white-space:nowrap;background:#111827;color:#ffffff;border-color:#111827;">上传本地图片</button></div></div>' +
                renderInlineStylePresetField('image', overrideValue('style', meta.style || '')) +
                '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_clear_override">清除当前覆盖</button>' +
            '</div>';
        } else if (primaryAction.kind === 'section') {
            contentFieldsHtml = '<div class="cms-builder-fields">' +
                '<div class="cms-builder-empty">当前选中的是栏目/区块容器。可继续管理显隐、排序、复制，也可以切到左侧“栏目模块”统一管理当前页面的所有区块。</div>' +
                insertActionsHtml +
                renderInlineStylePresetField('section', overrideValue('style', meta.style || '')) +
                '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_clear_override">清除当前覆盖</button>' +
            '</div>';
        } else {
            contentFieldsHtml = '<div class="cms-builder-fields">' +
                '<div class="cms-builder-field"><label>文本内容</label><textarea data-front-key="text">' + escapeHtml(overrideValue('text', meta.text || '')) + '</textarea></div>' +
                (meta.tag === 'a' ? '<div class="cms-builder-field"><label>链接地址</label><input type="text" data-front-key="href" value="' + escapeHtml(overrideValue('href', meta.href || '')) + '"></div>' : '') +
                insertActionsHtml +
                renderInlineStylePresetField('text', overrideValue('style', meta.style || '')) +
                '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_clear_override">清除当前覆盖</button>' +
            '</div>';
        }
        container.innerHTML =
            '<div class="cms-builder-group">' +
                '<div class="cms-builder-group-title">当前选中区块</div>' +
                '<div class="cms-builder-badges">' +
                    (isHidden ? '<span class="cms-builder-badge">当前已隐藏</span>' : '') +
                    (isLocked ? '<span class="cms-builder-badge">当前已锁定</span>' : '') +
                    (isProtected ? '<span class="cms-builder-badge">底部版权保护</span>' : '') +
                    (currentDevice() === 'mobile' ? '<span class="cms-builder-badge">移动端视图</span>' : '<span class="cms-builder-badge">PC 视图</span>') +
                '</div>' +
                (isProtected ? '<div class="cms-builder-group-desc" style="margin-top:10px;color:#b45309;">底部版权区块为受保护内容，禁止删除和隐藏。</div>' : '') +
                '<div class="cms-builder-action-grid">' +
                    '<button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_toggle_hide"' + (isProtected ? ' disabled' : '') + '>' + (isHidden ? '取消隐藏' : '隐藏区块') + '</button>' +
                    '<button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_toggle_lock">' + (isLocked ? '取消锁定' : '锁定区块') + '</button>' +
                    '<button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_move_up">上移一层</button>' +
                    '<button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_move_down">下移一层</button>' +
                    '<button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_duplicate">复制区块</button>' +
                    '<button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_delete"' + (isProtected ? ' disabled' : '') + '>删除区块</button>' +
                '</div>' +
                '<div class="cms-builder-fields" style="margin-top:12px;">' +
                    '<div class="cms-builder-field"><label>区块背景颜色</label><div style="display:flex;align-items:center;gap:10px;"><input type="text" data-front-style-prop="background-color" value="' + escapeHtml(selectedBackgroundColor || '') + '" placeholder="transparent / #ffffff"><input type="color" data-front-style-color-prop="background-color" value="' + escapeHtml(normalizeColorPickerValue(selectedBackgroundColor || '', '#ffffff')) + '" style="width:52px;height:42px;padding:4px 6px;border-radius:10px;flex:0 0 52px;cursor:pointer;"></div></div>' +
                '</div>' +
                contentFieldsHtml +
                globalContactSyncHtml +
            '</div>' +
            renderBusinessSettings(selectedExtraConfig());
        syncDeviceButtons();
    }

    function applyPageState() {
        assignStableBuilderKeys();
        renderExtraSections();
        renderCanvasLayer();
        applyOverrides();
        applyBlockOrders();
        applyVisibilityStates();
        applyLockedStates();
        applyPageVisualSettings();
        applyPageHeadSettings();
        ensurePageWidgetsLayer();
        renderLibraryPanel();
        renderNodeList();
        renderSettings();
        syncDeviceButtons();
    }

    function currentSelectionNodeForPanelReset(role) {
        if (role !== 'settings') {
            return null;
        }
        return currentSettingsAnchorNode();
    }

    function selectNode(node, scrollIntoView, openSettings) {
        if (!node) {
            return;
        }
        if (typeof openSettings === 'undefined') {
            openSettings = false;
        }
        state.selectedNode = node;
        state.selectedPath = getDomPath(node);
        state.selectedCanvasBlockId = '';
        state.stylePanelPart = detectStylePartFromNode(node);
        if (scrollIntoView) { centerNodeInViewport(node); }
        renderLibraryPanel();
        renderNodeList();
        renderSettings();
        refreshCanvasChrome();
        postShellMessage('select_node', nodeMeta(node));
        if (openSettings) {
            ensureSettingsPanelForCurrentSelection();
        } else if (!state.selectedCanvasBlockId) {
            hideSettingsPanel();
        }
    }

    function refreshSelectionByPath(path) {
        var next = path ? queryByPath(path) : null;
        if (next) {
            selectNode(next, false, state.settingsPanelOpen);
            return;
        }
        state.selectedNode = null;
        state.selectedPath = '';
        if (state.hoveredPath === path) {
            state.hoveredPath = '';
        }
        renderLibraryPanel();
        renderNodeList();
        renderSettings();
        refreshCanvasChrome();
        hideSettingsPanel();
    }

    function hasPendingDraft() {
        return !!(state.dirty || state.saveTimer || state.saveInFlight || state.saveQueued);
    }

    function startDraftSave(manual) {
        if (!boot.edit_mode || !boot.can_operate) {
            return Promise.resolve({code:403,msg:'当前账号没有保存权限'});
        }
        if(state.conflict){showVersionConflict();return Promise.resolve({code:409,msg:'页面草稿已在其他标签页更新'});}
        clearTimeout(state.saveTimer);
        state.saveTimer=null;
        clearTimeout(state.retryTimer);state.retryTimer=null;
        if(state.saveInFlight){state.saveQueued=true;return state.savePromise;}
        state.online=navigator.onLine!==false;
        if(!state.online){state.dirty=true;setStatus('当前离线 · 修改已保留，联网后自动保存');scheduleDraftRetry();return Promise.resolve({code:0,msg:'当前离线',offline:true});}
        if(!BUILDER_FREE_CANVAS_ENABLED){disableFreeCanvasState();syncEditorStateToPage();}
        var revision=state.changeRevision;state.saveInFlight=true;state.saveQueued=false;setStatus(manual?'正在保存草稿...':'正在自动保存...');
        state.savePromise=postApi('save_draft',{title:builderTitle(),is_enabled:1,layout:buildLayout(),expected_version:state.serverVersion}).then(function(result){
            if(result&&result.code===200){state.serverVersion=parseInt(result.version_no||state.serverVersion,10)||state.serverVersion;state.savedRevision=Math.max(state.savedRevision,revision);state.dirty=state.changeRevision>state.savedRevision;state.retryAttempt=0;state.lastSavedAt=Date.now();announceSavedVersion();if(state.dirty)queueLocalRecovery();else clearLocalRecovery();setStatus(state.dirty?'检测到新修改，等待继续保存...':'草稿已保存 · '+savedTimeLabel(state.lastSavedAt));}
            else if(result&&result.code===409){showVersionConflict(result);}
            else{state.dirty=true;setStatus(manual?'草稿保存失败':'自动保存失败');if(!result||!result.code||result.code>=500)scheduleDraftRetry();}
            return result;
        },function(error){state.dirty=true;state.online=navigator.onLine!==false;scheduleDraftRetry();return{code:0,msg:'网络请求失败',network_error:true,error:error};}).then(function(result){
            state.saveInFlight=false;if(state.saveQueued&&!state.conflict){state.saveQueued=false;return startDraftSave(false);}state.saveQueued=false;return result;
        });
        return state.savePromise;
    }

    function queueSaveDraft() {
        if (!boot.edit_mode || !boot.can_operate) { return; }
        state.changeRevision++;state.dirty=true;clearTimeout(state.saveTimer);
        queueLocalRecovery();
        if(state.conflict){setStatus('检测到其他标签页更新 · 当前修改尚未覆盖');return;}
        state.saveTimer=setTimeout(function(){state.saveTimer=null;if(state.saveInFlight){state.saveQueued=true;return;}startDraftSave(false).catch(function(){});},900);
    }

    function saveDraftNow() {
        return startDraftSave(true);
    }

    function beginLiveEdit(token) {
        if (!token || state.liveEditToken === token) {
            return;
        }
        pushHistory();
        state.liveEditToken = token;
    }

    function endLiveEdit() {
        state.liveEditToken = '';
    }

    function commitMutation(nextSelectedPath) {
        applyPageState();
        refreshSelectionByPath(nextSelectedPath || state.selectedPath);
        queueSaveDraft();
    }

    function refreshRenderedState(nextSelectedPath) {
        applyPageState();
        initSimpleCarousels(document);
        refreshSelectionByPath(nextSelectedPath || state.selectedPath);
    }

    function togglePathValue(list, path) {
        var index = list.indexOf(path);
        if (index === -1) {
            list.push(path);
        } else {
            list.splice(index, 1);
        }
        return list;
    }

    function currentBlockNode() {
        if (!state.selectedNode) {
            return null;
        }
        return state.selectedNode.closest('[data-builder-block-path]') || state.selectedNode.closest('[data-builder-extra-id]') || state.selectedNode;
    }

    function updateParentOrder(parent, selectedPath) {
        if (!parent) {
            return;
        }
        var parentRef = nodeRef(parent);
        var paths = Array.prototype.map.call(parent.children || [], function (child) {
            var ref = nodeRef(child);
            return ref.path || ref.key ? ref : null;
        }).filter(Boolean);
        state.page.frontend_block_orders = (state.page.frontend_block_orders || []).filter(function (entry) {
            return !refsEqual(normalizeStoredRef(entry.parent), parentRef);
        });
        state.page.frontend_block_orders.push({
            parent: parentRef,
            paths: paths
        });
        state.selectedPath = selectedPath || state.selectedPath;
    }

    function setExtraPlacement(extraId, anchorPath, insertMode, anchorKey) {
        state.page.frontend_extra_sections = state.page.frontend_extra_sections.map(function (item) {
            if (String(item.id) === String(extraId)) {
                item.anchor_path = anchorPath || '';
                item.anchor_key = anchorKey || '';
                item.insert_mode = insertMode === 'before' ? 'before' : 'after';
            }
            return item;
        });
    }

    function defaultExtraAnchorMeta() {
        var current = resolveActionBlockNode(currentBlockNode());
        if (current && !isProtectedEditableNode(current)) {
            return {
                path: getDomPath(current),
                key: nodeStableKey(current)
            };
        }
        var blocks = (state.blocks || []).filter(function (item) {
            if (!item || !item.path) {
                return false;
            }
            var node = queryByPath(item.path);
            return !!(node && !isProtectedEditableNode(node));
        });
        if (blocks.length) {
            return {
                path: blocks[blocks.length - 1].path,
                key: blocks[blocks.length - 1].key || ''
            };
        }
        var mainNode = qs('main');
        if (mainNode) {
            return {
                path: getDomPath(mainNode),
                key: nodeStableKey(mainNode)
            };
        }
        return {path: '', key: ''};
    }

    function addExtraSectionAtCurrentPosition(type, presetOptions) {
        var anchorMeta = defaultExtraAnchorMeta();
        var anchorPath = anchorMeta.path || '';
        var insertMode = anchorPath ? 'after' : 'after';
        var extraId = addExtraSection(type, anchorPath, insertMode, presetOptions, anchorMeta.key || '');
        applyPageState();
        var extraNode = qs('[data-builder-extra-id="' + String(extraId).replace(/"/g, '&quot;') + '"]');
        if (extraNode) {
            selectNode(extraNode, true, false);
            setStatus('新模块已添加到当前页面。');
        } else {
            setStatus('新模块已添加到当前页面。');
        }
        return extraId;
    }

    function addExtraSection(type, anchorPath, insertMode, presetOptions, anchorKey) {
        pushHistory();
        var html = '';
        var config = {};
        var staticHtml = '';
        presetOptions = presetOptions && typeof presetOptions === 'object' ? presetOptions : {};
        if (presetOptions.type) {
            type = presetOptions.type;
        }
        if (type === 'text') {
            config = {
                mode: 'paragraph',
                text: '请输入文本内容',
                html: '<p>请输入富文本内容</p>',
                align: 'left',
                color: '#0f172a',
                font_size: 16
            };
            html = '<div class="builder-extra-business"><h3>文本模块加载中...</h3></div>';
        } else if (type === 'image') {
            config = {
                image_url: '',
                alt: '图片',
                link_url: '',
                target: '_self',
                width_mode: 'full',
                width: 480,
                height: 320,
                fit: 'cover',
                align: 'center',
                radius: 16
            };
            html = '<div class="builder-extra-business"><h3>图片模块加载中...</h3></div>';
        } else if (type === 'button') {
            config = {
                text: '立即咨询',
                action_type: 'link',
                action_value: '',
                size: 'md',
                variant: 'solid',
                icon: '',
                icon_position: 'left',
                bg_color: '#111827',
                text_color: '#ffffff',
                radius_mode: 'pill',
                align: 'center'
            };
            html = '<div class="builder-extra-business"><h3>按钮模块加载中...</h3></div>';
        } else if (type === 'product_showcase') {
            type = 'product_list';
            config = { title: '产品橱窗', channel_id: 0, limit: 4, variant: 'showcase', show_price: 1 };
            html = '<div class="builder-extra-business"><h3>产品橱窗加载中...</h3></div>';
        } else if (type === 'article_list') {
            config = { title: '文章列表', channel_id: 0, limit: 6, tag: '', keyword: '', time_from: '', time_to: '', order_by: 'id_desc', variant: 'default' };
            html = '<div class="builder-extra-business"><h3>文章列表加载中...</h3></div>';
        } else if (type === 'product_list') {
            config = { title: '产品展示', channel_id: 0, limit: 6, tag: '', keyword: '', time_from: '', time_to: '', order_by: 'id_desc', variant: 'default', show_price: 1 };
            html = '<div class="builder-extra-business"><h3>产品展示加载中...</h3></div>';
        } else if (type === 'carousel') {
            config = {
                title: '轮播图',
                autoplay_seconds: 4,
                height: 420,
                variant: 'banner',
                show_dots: 1,
                data_source: 'slides',
                items: ''
            };
            html = '<div class="builder-extra-business"><h3>轮播图加载中...</h3></div>';
        } else if (type === 'image_text') {
            config = {
                title: '图文展示',
                subtitle: '支持左图右文、右图左文、上图下文等排版',
                layout: 'left-image',
                image_url: '',
                heading: '模块主标题',
                content: '这里填写图文模块的介绍内容，适合品牌介绍、优势说明、服务介绍等。',
                button_text: '了解更多',
                button_url: '',
                gap: 24
            };
            html = '<div class="builder-extra-business"><h3>图文展示加载中...</h3></div>';
        } else if (type === 'multi_image') {
            config = {
                title: '列表多图',
                columns: 4,
                image_height: 180,
                gap: 16,
                radius: 18,
                items: ''
            };
            html = '<div class="builder-extra-business"><h3>列表多图加载中...</h3></div>';
        } else if (type === 'gallery') {
            config = {
                title: '图册目录',
                subtitle: '点击缩略图可查看大图',
                thumb_height: 160,
                columns: 4,
                items: ''
            };
            html = '<div class="builder-extra-business"><h3>图册目录加载中...</h3></div>';
        } else if (type === 'video_embed') {
            config = {
                title: '在线视频',
                subtitle: '支持本地视频上传或外链嵌入',
                mode: 'upload',
                video_url: '',
                poster_url: '',
                embed_html: '',
                height: 420,
                autoplay: 0,
                loop: 0
            };
            html = '<div class="builder-extra-business"><h3>在线视频加载中...</h3></div>';
        } else if (type === 'channel_nav') {
            config = {
                title: '栏目导航',
                variant: 'pill',
                active_url: 'auto',
                items: ''
            };
            html = '<div class="builder-extra-business"><h3>栏目导航加载中...</h3></div>';
        } else if (type === 'guestbook_form') {
            config = {
                title: '在线留言',
                subtitle: '留下您的联系方式和需求，我们会尽快与您联系。',
                button_text: '提交留言',
                variant: 'default'
            };
            html = '<div class="builder-extra-business"><h3>留言表单加载中...</h3></div>';
        } else if (type === 'qrcode') {
            config = {
                title: '扫码关注',
                subtitle: '微信扫码获取更多信息',
                image_url: ''
            };
            html = '<div class="builder-extra-business"><h3>二维码模块加载中...</h3></div>';
        } else if (type === 'friend_links') {
            config = {
                title: '友情链接',
                variant: 'text',
                columns: 4,
                gap: 14,
                data_source: 'links',
                items: ''
            };
            html = '<div class="builder-extra-business"><h3>友情链接加载中...</h3></div>';
        } else if (type === 'service_panel') {
            config = {
                title: '在线客服',
                subtitle: '支持电话、QQ、微信和企业邮箱展示',
                phone: '',
                qq: '',
                wechat: '',
                email: '',
                variant: 'cards'
            };
            html = '<div class="builder-extra-business"><h3>在线客服加载中...</h3></div>';
        } else if (type === 'member_portal') {
            config = {
                title: '会员中心',
                login_text: '会员登录',
                register_text: '立即注册',
                center_text: '进入会员中心',
                variant: 'card'
            };
            html = '<div class="builder-extra-business"><h3>会员入口加载中...</h3></div>';
        } else if (type === 'vote_form') {
            config = {
                title: '在线投票',
                vote_id: 0,
                status: '',
                keyword: '',
                limit: 10,
                order_by: 'id_desc',
                button_text: '立即投票',
                show_result: 1
            };
            html = '<div class="builder-extra-business"><h3>投票模块加载中...</h3></div>';
        } else {
            staticHtml = buildStaticModuleHtml(type);
            html = staticHtml || '<div class="builder-extra-text"><h3>新模块</h3></div>';
        }
        if (presetOptions.config && typeof presetOptions.config === 'object') {
            config = Object.assign({}, config, presetOptions.config);
        }
        if (typeof presetOptions.html === 'string' && presetOptions.html) {
            html = presetOptions.html;
        }
        var item = {
            id: 'extra_' + Math.random().toString(16).slice(2, 10),
            type: type,
            html: html,
            config: config,
            anchor_path: anchorPath || '',
            anchor_key: anchorKey || '',
            insert_mode: insertMode === 'before' ? 'before' : 'after'
        };
        state.page.frontend_extra_sections.push(item);
        state.recentModules=[type].concat(state.recentModules.filter(function(moduleType){return moduleType!==type;})).slice(0,6);syncEditorStateToPage();
        commitMutation('');
        if (type === 'text' || type === 'image' || type === 'button' || type === 'article_list' || type === 'product_list' || type === 'carousel' || type === 'image_text' || type === 'multi_image' || type === 'gallery' || type === 'video_embed' || type === 'channel_nav' || type === 'guestbook_form' || type === 'qrcode' || type === 'friend_links' || type === 'service_panel' || type === 'member_portal' || type === 'vote_form') {
            rebuildBusinessExtra(item.id);
        }
        return item.id;
    }

    function updateExtraConfig(extraId, key, value) {
        pushHistory();
        state.page.frontend_extra_sections = state.page.frontend_extra_sections.map(function (item) {
            if (String(item.id) === String(extraId)) {
                if (!item.config || typeof item.config !== 'object') {
                    item.config = {};
                }
                item.config[key] = value;
            }
            return item;
        });
        commitMutation(state.selectedPath);
        rebuildBusinessExtra(extraId);
    }

    function openRichTextEditor() {
        var extra = selectedExtraConfig();
        var modal = qs('#cms_builder_rich_editor_modal');
        var field = qs('#cms_builder_rich_text_source');
        if (!extra || extra.type !== 'text' || !modal || !field) {
            setStatus('请先选中富文本模块。');
            return;
        }
        state.richTextEditorExtraId = extra.id;
        field.value = String((extra.config && extra.config.html) || '<p>请输入富文本内容</p>');
        field.hidden = false;
        modal.classList.remove('is-hidden');
        if (window.CMSUeditor) {
            window.CMSUeditor.init(field).then(function (editor) {
                if (editor) editor.setContent(field.value || '');
            });
        }
        setStatus('新版 UEditor 已打开，保存后会立即更新当前富文本模块。');
    }

    function closeRichTextEditor(saveChanges) {
        var modal = qs('#cms_builder_rich_editor_modal');
        var field = qs('#cms_builder_rich_text_source');
        if (!modal || !field) return;
        if (window.CMSUeditor) window.CMSUeditor.sync(field.id);
        var html = field.value || '';
        var extraId = state.richTextEditorExtraId;
        if (window.CMSUeditor) window.CMSUeditor.destroy(field.id);
        modal.classList.add('is-hidden');
        state.richTextEditorExtraId = '';
        if (saveChanges && extraId) {
            updateExtraConfig(extraId, 'html', html);
            renderSettings();
            setStatus('富文本内容已更新并进入草稿保存队列。');
        }
    }

    function updateExtraConfigLive(extraId, key, value) {
        var extraType = '';
        state.page.frontend_extra_sections = state.page.frontend_extra_sections.map(function (item) {
            if (String(item.id) === String(extraId)) {
                if (!item.config || typeof item.config !== 'object') {
                    item.config = {};
                }
                item.config[key] = value;
                extraType = item.type || '';
            }
            return item;
        });
        if (extraType === 'text' || extraType === 'image' || extraType === 'button' || extraType === 'image_text' || extraType === 'multi_image' || extraType === 'gallery' || extraType === 'video_embed' || extraType === 'channel_nav') {
            rebuildBusinessExtra(extraId, {live: true});
        }
        queueSaveDraft();
    }

    function finalizeExtraConfigEdit(extraId) {
        endLiveEdit();
        commitMutation(state.selectedPath);
        rebuildBusinessExtra(extraId);
    }

    function businessItemAttrs(type, item, extraId) {
        var attrs = [
            'data-business-item-type="' + escapeHtml(type) + '"',
            'data-business-item-id="' + escapeHtml(item && item.id != null ? item.id : '') + '"',
            'data-business-extra-id="' + escapeHtml(extraId || '') + '"',
            'data-business-title="' + escapeHtml(item && item.title != null ? item.title : '') + '"',
            'data-business-summary="' + escapeHtml(item && item.summary != null ? item.summary : '') + '"'
        ];
        if (type === 'product') {
            attrs.push('data-business-price="' + escapeHtml(item && item.price != null ? item.price : '') + '"');
            attrs.push('data-business-litpic="' + escapeHtml(item && item.litpic != null ? item.litpic : '') + '"');
        }
        return attrs.join(' ');
    }

    function applyExtraRenderedHtml(target, options) {
        options = options && typeof options === 'object' ? options : {};
        var wrapper = qs('[data-builder-extra-id="' + String(target.id || '').replace(/"/g, '&quot;') + '"]');
        if (wrapper) {
            wrapper.innerHTML = target.html || '';
            applyExtraVisualStyle(target, wrapper);
            initSimpleCarousels(wrapper);
            if (options.live) {
                state.selectedNode = wrapper;
                state.selectedPath = getDomPath(wrapper);
                renderFloatingOverlays();
                return;
            }
        }
        refreshRenderedState(state.selectedPath);
    }

    function buildExtraCssText(extra) {
        if (!extra || !extra.config || typeof extra.config !== 'object') {
            return '';
        }
        var style = selectedExtraStyleState(extra);
        var rules = [];
        var backgroundType = style.background_type || 'solid';
        if (style.width) rules.push('width:' + style.width);
        if (style['min-height']) rules.push('min-height:' + style['min-height']);
        if (style.margin) rules.push('margin:' + style.margin);
        if (style.padding) rules.push('padding:' + style.padding);
        if (style['border-radius']) rules.push('border-radius:' + style['border-radius']);
        if (style.border) rules.push('border:' + style.border);
        if (style['box-shadow']) rules.push('box-shadow:' + style['box-shadow']);
        if (style['font-family']) rules.push('font-family:' + style['font-family']);
        if (style['font-size']) rules.push('font-size:' + style['font-size']);
        if (style.color) rules.push('color:' + style.color);
        if (style['font-weight']) rules.push('font-weight:' + style['font-weight']);
        if (style['font-style']) rules.push('font-style:' + style['font-style']);
        if (style['text-align']) rules.push('text-align:' + style['text-align']);
        if (style['line-height']) rules.push('line-height:' + style['line-height']);
        if (backgroundType === 'gradient' && style.gradient_from && style.gradient_to) {
            rules.push('background:linear-gradient(135deg,' + style.gradient_from + ',' + style.gradient_to + ')');
        } else if (backgroundType === 'image' && style.background_image) {
            rules.push('background-image:url("' + style.background_image.replace(/"/g, '\\"') + '")');
            rules.push('background-repeat:' + (style.background_repeat || 'no-repeat'));
            rules.push('background-position:' + (style.background_position || 'center center'));
            rules.push('background-size:' + (style.background_size || 'cover'));
        } else if (style.background_color) {
            rules.push('background-color:' + style.background_color);
        }
        if (style.hover_color) {
            rules.push('--cms-extra-hover-color:' + style.hover_color);
        }
        if (style.click_effect) {
            rules.push('--cms-extra-click-effect:' + style.click_effect);
        }
        if (style.enter_animation) {
            rules.push('--cms-extra-enter-animation:' + style.enter_animation);
            rules.push('--cms-extra-enter-duration:' + (style.animation_duration || '0.3s'));
        }
        return rules.join(';');
    }

    function extraStyleDurationMs(value) {
        var text = String(value || '0.3s').trim();
        if (!text) {
            return 300;
        }
        if (/ms$/i.test(text)) {
            return Math.max(parseInt(text, 10) || 300, 0);
        }
        if (/s$/i.test(text)) {
            return Math.max(Math.round((parseFloat(text) || 0.3) * 1000), 0);
        }
        return Math.max(parseInt(text, 10) || 300, 0);
    }

    function runExtraEnterAnimation(style, element) {
        if (!style || !style.enter_animation || !element || typeof element.animate !== 'function') {
            return;
        }
        var keyframes = null;
        if (style.enter_animation === 'fade-up') {
            keyframes = [{opacity: 0, transform: 'translate3d(0,24px,0)'}, {opacity: 1, transform: 'translate3d(0,0,0)'}];
        } else if (style.enter_animation === 'zoom-in') {
            keyframes = [{opacity: 0, transform: 'scale(0.94)'}, {opacity: 1, transform: 'scale(1)'}];
        } else if (style.enter_animation === 'fade') {
            keyframes = [{opacity: 0}, {opacity: 1}];
        }
        if (!keyframes) {
            return;
        }
        element.animate(keyframes, {
            duration: extraStyleDurationMs(style.animation_duration || '0.3s'),
            easing: 'cubic-bezier(.22,1,.36,1)',
            fill: 'both'
        });
    }

    function bindExtraInteractiveBehavior(extra, wrapper) {
        if (!extra || !wrapper) {
            return;
        }
        var style = selectedExtraStyleState(extra);
        var surface = wrapper.firstElementChild && wrapper.firstElementChild.nodeType === 1 ? wrapper.firstElementChild : wrapper;
        var baseBg = surface.style.backgroundColor || '';
        var baseColor = surface.style.color || '';
        var baseTransform = surface.style.transform || '';
        var baseBoxShadow = surface.style.boxShadow || '';
        wrapper.onmouseenter = null;
        wrapper.onmouseleave = null;
        wrapper.onmousedown = null;
        wrapper.onmouseup = null;
        if (style.hover_color || style.click_effect) {
            surface.style.transition = 'background-color .2s ease,color .2s ease,transform .2s ease,box-shadow .2s ease,opacity .2s ease';
        }
        if (style.hover_color) {
            wrapper.onmouseenter = function () {
                var computedBg = window.getComputedStyle(surface).backgroundColor || '';
                var useBackground = computedBg && computedBg !== 'rgba(0, 0, 0, 0)' && computedBg !== 'transparent';
                if (useBackground) {
                    surface.style.backgroundColor = style.hover_color;
                } else {
                    surface.style.color = style.hover_color;
                }
            };
            wrapper.onmouseleave = function () {
                surface.style.backgroundColor = baseBg;
                surface.style.color = baseColor;
                surface.style.transform = baseTransform;
                surface.style.boxShadow = baseBoxShadow;
            };
        }
        if (style.click_effect === 'press') {
            wrapper.onmousedown = function () {
                surface.style.transform = baseTransform ? (baseTransform + ' scale(0.98)') : 'scale(0.98)';
            };
            wrapper.onmouseup = function () {
                surface.style.transform = baseTransform;
            };
            if (!wrapper.onmouseleave) {
                wrapper.onmouseleave = function () {
                    surface.style.transform = baseTransform;
                };
            }
        } else if (style.click_effect === 'glow') {
            wrapper.onmousedown = function () {
                surface.style.boxShadow = baseBoxShadow ? (baseBoxShadow + ', 0 0 0 4px rgba(37,99,235,.18), 0 18px 40px rgba(37,99,235,.22)') : '0 0 0 4px rgba(37,99,235,.18), 0 18px 40px rgba(37,99,235,.22)';
            };
            wrapper.onmouseup = function () {
                surface.style.boxShadow = baseBoxShadow;
            };
            if (!wrapper.onmouseleave) {
                wrapper.onmouseleave = function () {
                    surface.style.boxShadow = baseBoxShadow;
                };
            }
        }
        runExtraEnterAnimation(style, surface);
    }

    function applyExtraVisualStyle(extra, wrapper) {
        if (!extra || !wrapper) {
            return;
        }
        var cssText = buildExtraCssText(extra);
        var baseCssText = wrapper.getAttribute('data-builder-extra-base-style');
        if (baseCssText == null) {
            baseCssText = wrapper.getAttribute('style') || '';
            wrapper.setAttribute('data-builder-extra-base-style', baseCssText);
        }
        wrapper.setAttribute('style', baseCssText + (baseCssText && cssText ? ';' : '') + cssText);
        var mobileStyle = extra.config && extra.config.mobile_style && typeof extra.config.mobile_style === 'object' ? extra.config.mobile_style : {};
        if (String(mobileStyle.hide_mobile || '0') === '1' && currentDevice() === 'mobile') {
            wrapper.style.display = 'none';
        } else if (String(mobileStyle.show_mobile_only || '0') === '1' && currentDevice() !== 'mobile') {
            wrapper.style.display = 'none';
        } else {
            wrapper.style.display = '';
        }
        bindExtraInteractiveBehavior(extra, wrapper);
    }

    function rebuildBusinessExtra(extraId, options) {
        options = options && typeof options === 'object' ? options : {};
        var target = null;
        state.page.frontend_extra_sections.forEach(function (item) {
            if (String(item.id) === String(extraId)) {
                target = item;
            }
        });
        if (!target) {
            return;
        }
        if (target.type === 'text') {
            var textMode = (target.config && target.config.mode) || 'paragraph';
            var textAlign = (target.config && target.config.align) || 'left';
            var textColor = (target.config && target.config.color) || '#0f172a';
            var textFontSize = parseInt(target.config && target.config.font_size ? target.config.font_size : 16, 10) || 16;
            var textValue = String((target.config && target.config.text) || '请输入文本内容');
            var richHtml = String((target.config && target.config.html) || '<p>请输入富文本内容</p>');
            var shellStyle = 'padding:24px;border:1px solid #e2e8f0;border-radius:18px;background:#fff;';
            var contentStyle = 'margin:0;text-align:' + escapeHtml(textAlign) + ';color:' + escapeHtml(textColor) + ';';
            if (textMode === 'title') {
                target.html = '<div class="builder-card" style="' + shellStyle + '"><h2 style="' + contentStyle + 'font-size:' + escapeHtml(Math.max(textFontSize, 22)) + 'px;line-height:1.35;font-weight:800;">' + escapeHtml(textValue) + '</h2></div>';
            } else if (textMode === 'single') {
                target.html = '<div class="builder-card" style="' + shellStyle + '"><div style="' + contentStyle + 'font-size:' + escapeHtml(textFontSize) + 'px;line-height:1.7;font-weight:600;">' + escapeHtml(textValue.replace(/\r?\n/g, ' ')) + '</div></div>';
            } else if (textMode === 'multiline') {
                target.html = '<div class="builder-card" style="' + shellStyle + '"><div style="' + contentStyle + 'font-size:' + escapeHtml(textFontSize) + 'px;line-height:1.9;white-space:pre-line;">' + escapeHtml(textValue) + '</div></div>';
            } else if (textMode === 'rich') {
                target.html = '<div class="builder-card" style="' + shellStyle + '"><div style="text-align:' + escapeHtml(textAlign) + ';color:' + escapeHtml(textColor) + ';font-size:' + escapeHtml(textFontSize) + 'px;line-height:1.9;">' + richHtml + '</div></div>';
            } else {
                target.html = '<div class="builder-card" style="' + shellStyle + '"><p style="' + contentStyle + 'font-size:' + escapeHtml(textFontSize) + 'px;line-height:1.9;">' + escapeHtml(textValue) + '</p></div>';
            }
            applyExtraRenderedHtml(target, options);
            return;
        }
        if (target.type === 'image') {
            var imageUrl = (target.config && target.config.image_url) || 'https://via.placeholder.com/960x320?text=Image';
            var imageAlt = (target.config && target.config.alt) || '图片';
            var imageLink = (target.config && target.config.link_url) || '';
            var imageTarget = (target.config && target.config.target) || '_self';
            var imageWidthMode = (target.config && target.config.width_mode) || 'full';
            var imageWidth = parseInt(target.config && target.config.width ? target.config.width : 480, 10) || 480;
            var imageHeight = parseInt(target.config && target.config.height ? target.config.height : 320, 10) || 320;
            var imageFit = (target.config && target.config.fit) || 'cover';
            var imageAlign = (target.config && target.config.align) || 'center';
            var imageRadius = parseInt(target.config && target.config.radius ? target.config.radius : 16, 10) || 16;
            var imageStyle = 'display:block;' +
                (imageWidthMode === 'custom' ? 'width:' + imageWidth + 'px;max-width:100%;' : (imageWidthMode === 'auto' ? 'width:auto;max-width:100%;' : 'width:100%;')) +
                'height:' + imageHeight + 'px;object-fit:' + escapeHtml(imageFit) + ';border-radius:' + imageRadius + 'px;';
            var wrapperStyle = 'padding:24px;border:1px solid #e2e8f0;border-radius:18px;background:#fff;text-align:' + escapeHtml(imageAlign) + ';';
            var imageHtml = '<img src="' + escapeHtml(imageUrl) + '" alt="' + escapeHtml(imageAlt) + '" style="' + imageStyle + '">';
            if (imageLink) {
                imageHtml = '<a href="' + escapeHtml(imageLink) + '" target="' + escapeHtml(imageTarget) + '" style="display:inline-block;text-decoration:none;">' + imageHtml + '</a>';
            }
            target.html = '<div class="builder-card" style="' + wrapperStyle + '">' + imageHtml + '</div>';
            applyExtraRenderedHtml(target, options);
            return;
        }
        if (target.type === 'button') {
            var buttonText = (target.config && target.config.text) || '立即咨询';
            var actionType = (target.config && target.config.action_type) || 'link';
            var actionValue = (target.config && target.config.action_value) || '';
            var buttonSize = (target.config && target.config.size) || 'md';
            var buttonVariant = (target.config && target.config.variant) || 'solid';
            var buttonIcon = (target.config && target.config.icon) || '';
            var buttonIconPosition = (target.config && target.config.icon_position) || 'left';
            var buttonBgColor = (target.config && target.config.bg_color) || '#111827';
            var buttonTextColor = (target.config && target.config.text_color) || '#ffffff';
            var buttonRadiusMode = (target.config && target.config.radius_mode) || 'pill';
            var buttonAlign = (target.config && target.config.align) || 'center';
            var href = 'javascript:void(0)';
            var targetAttr = '_self';
            if (actionType === 'phone') {
                href = actionValue ? ('tel:' + actionValue) : 'javascript:void(0)';
            } else if (actionType === 'email') {
                href = actionValue ? ('mailto:' + actionValue) : 'javascript:void(0)';
            } else if (actionType === 'anchor') {
                href = actionValue || '#';
            } else {
                href = actionValue || 'javascript:void(0)';
                targetAttr = actionType === 'new_window' ? '_blank' : '_self';
            }
            var iconMap = {arrow: '&rarr;', phone: '&#9742;', mail: '@', star: '&#9733;', plus: '+'};
            var iconHtml = buttonIcon && iconMap[buttonIcon]
                ? '<span style="display:inline-flex;align-items:center;justify-content:center;min-width:14px;">' + iconMap[buttonIcon] + '</span>'
                : '';
            var sizeStyle = 'padding:12px 22px;font-size:14px;';
            if (buttonSize === 'sm') {
                sizeStyle = 'padding:10px 18px;font-size:13px;';
            } else if (buttonSize === 'lg') {
                sizeStyle = 'padding:15px 28px;font-size:16px;';
            }
            var radiusStyle = buttonRadiusMode === 'square' ? 'border-radius:0;' : (buttonRadiusMode === 'rounded' ? 'border-radius:14px;' : 'border-radius:999px;');
            var buttonStyle = 'display:inline-flex;align-items:center;justify-content:center;gap:8px;text-decoration:none;font-weight:700;' + sizeStyle + radiusStyle;
            if (buttonVariant === 'outline') {
                buttonStyle += 'background:transparent;color:' + escapeHtml(buttonBgColor) + ';border:1px solid ' + escapeHtml(buttonBgColor) + ';';
            } else if (buttonVariant === 'ghost') {
                buttonStyle += 'background:rgba(15,23,42,.06);color:' + escapeHtml(buttonBgColor) + ';border:1px solid transparent;';
            } else {
                buttonStyle += 'background:' + escapeHtml(buttonBgColor) + ';color:' + escapeHtml(buttonTextColor) + ';border:1px solid ' + escapeHtml(buttonBgColor) + ';';
            }
            var buttonInner = buttonIconPosition === 'right'
                ? '<span>' + escapeHtml(buttonText) + '</span>' + iconHtml
                : iconHtml + '<span>' + escapeHtml(buttonText) + '</span>';
            target.html = '<div class="builder-card" style="padding:24px;border:1px solid #e2e8f0;border-radius:18px;background:#fff;text-align:' + escapeHtml(buttonAlign) + ';">' +
                '<a href="' + escapeHtml(href) + '" target="' + escapeHtml(targetAttr) + '" style="' + buttonStyle + '">' + buttonInner + '</a>' +
                '</div>';
            applyExtraRenderedHtml(target, options);
            return;
        }
        if (target.type === 'article_list') {
            fetchBusinessData('articles', {
                channel_id: target.config && target.config.channel_id ? target.config.channel_id : 0,
                limit: target.config && target.config.limit ? target.config.limit : 6,
                tag: target.config && target.config.tag ? target.config.tag : '',
                keyword: target.config && target.config.keyword ? target.config.keyword : '',
                time_from: target.config && target.config.time_from ? target.config.time_from : '',
                time_to: target.config && target.config.time_to ? target.config.time_to : '',
                order_by: target.config && target.config.order_by ? target.config.order_by : 'id_desc'
            }).then(function (result) {
                var items = result && result.data ? result.data : [];
                var title = escapeHtml((target.config && target.config.title) || '文章列表');
                var variant = (target.config && target.config.variant) || 'default';
                var html = '<div class="builder-card"><h2 class="builder-title" style="font-size:28px;">' + title + '</h2>';
                if (!items.length) {
                    html += '<div style="margin-top:16px;padding:24px;border:1px dashed #cbd5e1;border-radius:16px;color:#64748b;">当前分类下暂无文章，请检查分类或先发布文章。</div>';
                } else {
                    if (variant === 'timeline') {
                        html += '<div style="display:grid;gap:16px;margin-top:18px;position:relative;">';
                        items.forEach(function (item, index) {
                            html += '<a ' + businessItemAttrs('article', item, target.id) + ' href="' + escapeHtml('/public/index.php?m=article&id=' + item.id) + '" style="display:grid;grid-template-columns:88px 1fr;gap:18px;align-items:start;text-decoration:none;color:inherit;">' +
                                '<div style="padding-top:2px;text-align:center;"><div style="font-size:12px;color:#94a3b8;">2026</div><div style="margin-top:4px;font-size:24px;font-weight:800;color:#2563eb;">' + escapeHtml(String((index + 10)).slice(-2)) + '</div><div style="width:2px;height:54px;background:#dbeafe;margin:10px auto 0;"></div></div>' +
                                '<div style="padding:18px;border:1px solid #e2e8f0;border-radius:16px;background:#fff;"><div style="font-size:20px;font-weight:700;line-height:1.5;">' + escapeHtml(item.title || '') + '</div><div style="margin-top:10px;font-size:14px;line-height:1.8;color:#64748b;">' + escapeHtml((item.summary || '').slice(0, 96)) + '</div></div>' +
                            '</a>';
                        });
                        html += '</div>';
                    } else if (variant === 'magazine') {
                        html += '<div style="display:grid;grid-template-columns:1.1fr .9fr;gap:18px;margin-top:18px;">';
                        items.slice(0, 1).forEach(function (item) {
                            html += '<a ' + businessItemAttrs('article', item, target.id) + ' href="' + escapeHtml('/public/index.php?m=article&id=' + item.id) + '" style="display:block;padding:22px;border-radius:20px;background:linear-gradient(135deg,#eff6ff,#fff);border:1px solid #dbeafe;text-decoration:none;color:inherit;"><div style="font-size:13px;font-weight:700;color:#5b74e8;">精选文章</div><div style="margin-top:12px;font-size:28px;font-weight:800;line-height:1.4;">' + escapeHtml(item.title || '') + '</div><div style="margin-top:12px;font-size:14px;line-height:1.9;color:#64748b;">' + escapeHtml((item.summary || '').slice(0, 120)) + '</div></a>';
                        });
                        html += '<div style="display:grid;gap:12px;">';
                        items.slice(1).forEach(function (item) {
                            html += '<a ' + businessItemAttrs('article', item, target.id) + ' href="' + escapeHtml('/public/index.php?m=article&id=' + item.id) + '" style="display:block;padding:16px;border:1px solid #e2e8f0;border-radius:14px;background:#fff;text-decoration:none;color:inherit;"><div style="font-size:18px;font-weight:700;line-height:1.5;">' + escapeHtml(item.title || '') + '</div><div style="margin-top:8px;font-size:13px;line-height:1.8;color:#64748b;">' + escapeHtml((item.summary || '').slice(0, 66)) + '</div></a>';
                        });
                        html += '</div></div>';
                    } else if (variant === 'compact') {
                        html += '<div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-top:16px;">';
                        items.forEach(function (item) {
                            html += '<a ' + businessItemAttrs('article', item, target.id) + ' href="' + escapeHtml('/public/index.php?m=article&id=' + item.id) + '" style="display:block;padding:14px 16px;border:1px solid #e2e8f0;border-radius:14px;background:#fff;text-decoration:none;color:inherit;"><div style="font-size:16px;font-weight:700;line-height:1.5;">' + escapeHtml(item.title || '') + '</div><div style="margin-top:8px;font-size:12px;line-height:1.8;color:#64748b;">' + escapeHtml((item.summary || '').slice(0, 54)) + '</div></a>';
                        });
                        html += '</div>';
                    } else if (variant === 'highlight') {
                        html += '<div style="display:grid;gap:14px;margin-top:18px;">';
                        items.forEach(function (item, index) {
                            if (index === 0) {
                                html += '<a ' + businessItemAttrs('article', item, target.id) + ' href="' + escapeHtml('/public/index.php?m=article&id=' + item.id) + '" style="display:block;padding:22px;border-radius:20px;background:#0f172a;color:#fff;text-decoration:none;"><div style="font-size:13px;font-weight:700;color:#93c5fd;">热点资讯</div><div style="margin-top:12px;font-size:30px;font-weight:800;line-height:1.4;">' + escapeHtml(item.title || '') + '</div><div style="margin-top:12px;font-size:14px;line-height:1.9;color:rgba(255,255,255,.76);">' + escapeHtml((item.summary || '').slice(0, 120)) + '</div></a>';
                            } else {
                                html += '<a ' + businessItemAttrs('article', item, target.id) + ' href="' + escapeHtml('/public/index.php?m=article&id=' + item.id) + '" style="display:block;padding:16px 18px;border:1px solid #e2e8f0;border-radius:14px;background:#fff;text-decoration:none;color:inherit;"><div style="font-size:18px;font-weight:700;line-height:1.5;">' + escapeHtml(item.title || '') + '</div></a>';
                            }
                        });
                        html += '</div>';
                    } else {
                        html += '<div style="display:grid;gap:12px;margin-top:16px;">';
                        items.forEach(function (item) {
                            html += '<a ' + businessItemAttrs('article', item, target.id) + ' href="' + escapeHtml('/public/index.php?m=article&id=' + item.id) + '" style="display:block;padding:16px;border:1px solid #e2e8f0;border-radius:14px;text-decoration:none;color:inherit;">' +
                                '<div style="font-size:18px;font-weight:700;line-height:1.5;">' + escapeHtml(item.title || '') + '</div>' +
                                '<div style="margin-top:10px;font-size:14px;opacity:.78;line-height:1.8;">' + escapeHtml((item.summary || '').slice(0, 80)) + '</div>' +
                            '</a>';
                        });
                        html += '</div>';
                    }
                }
                html += '</div>';
                target.html = html;
                applyExtraRenderedHtml(target, options);
            });
            return;
        }
        if (target.type === 'product_list') {
            fetchBusinessData('products', {
                channel_id: target.config && target.config.channel_id ? target.config.channel_id : 0,
                limit: target.config && target.config.limit ? target.config.limit : 6,
                tag: target.config && target.config.tag ? target.config.tag : '',
                keyword: target.config && target.config.keyword ? target.config.keyword : '',
                time_from: target.config && target.config.time_from ? target.config.time_from : '',
                time_to: target.config && target.config.time_to ? target.config.time_to : '',
                order_by: target.config && target.config.order_by ? target.config.order_by : 'id_desc'
            }).then(function (result) {
                var items = result && result.data ? result.data : [];
                var title = escapeHtml((target.config && target.config.title) || '产品展示');
                var variant = (target.config && target.config.variant) || 'default';
                var showPrice = !(target.config && String(target.config.show_price) === '0');
                var html = '<div class="builder-card"><h2 class="builder-title" style="font-size:28px;">' + title + '</h2>';
                if (!items.length) {
                    html += '<div style="margin-top:16px;padding:24px;border:1px dashed #cbd5e1;border-radius:16px;color:#64748b;">当前分类下暂无产品，请检查分类或先发布产品。</div>';
                } else {
                    if (variant === 'showcase') {
                        html += '<div style="display:grid;grid-template-columns:1.1fr .9fr;gap:18px;margin-top:18px;">';
                        items.slice(0, 1).forEach(function (item) {
                            html += '<a ' + businessItemAttrs('product', item, target.id) + ' href="' + escapeHtml('/public/index.php?m=product&id=' + item.id) + '" style="display:block;padding:18px;border-radius:22px;background:linear-gradient(135deg,#eff6ff,#fff);border:1px solid #dbeafe;text-decoration:none;color:inherit;">' +
                                (item.litpic ? '<img src="' + escapeHtml(item.litpic) + '" alt="' + escapeHtml(item.title || '') + '" style="width:100%;height:300px;object-fit:cover;border-radius:18px;">' : '<div style="width:100%;height:300px;border-radius:18px;background:linear-gradient(135deg,#dbeafe,#eff6ff);"></div>') +
                                '<div style="margin-top:16px;font-size:28px;font-weight:800;line-height:1.4;">' + escapeHtml(item.title || '') + '</div>' +
                                (showPrice ? '<div style="margin-top:8px;font-size:22px;font-weight:800;color:#2563eb;">' + escapeHtml(item.price || '') + '</div>' : '') +
                            '</a>';
                        });
                        html += '<div style="display:grid;gap:14px;">';
                        items.slice(1).forEach(function (item) {
                            html += '<a ' + businessItemAttrs('product', item, target.id) + ' href="' + escapeHtml('/public/index.php?m=product&id=' + item.id) + '" style="display:grid;grid-template-columns:120px 1fr;gap:14px;align-items:center;padding:14px;border:1px solid #e2e8f0;border-radius:16px;background:#fff;text-decoration:none;color:inherit;">' +
                                (item.litpic ? '<img src="' + escapeHtml(item.litpic) + '" alt="' + escapeHtml(item.title || '') + '" style="width:120px;height:100px;object-fit:cover;border-radius:12px;">' : '<div style="width:120px;height:100px;border-radius:12px;background:linear-gradient(135deg,#dbeafe,#eff6ff);"></div>') +
                                '<div><div style="font-size:18px;font-weight:700;line-height:1.5;">' + escapeHtml(item.title || '') + '</div>' + (showPrice ? '<div style="margin-top:8px;font-size:18px;font-weight:800;color:#2563eb;">' + escapeHtml(item.price || '') + '</div>' : '') + '</div>' +
                            '</a>';
                        });
                        html += '</div></div>';
                    } else if (variant === 'compact') {
                        html += '<div style="display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-top:16px;">';
                        items.forEach(function (item) {
                            html += '<a ' + businessItemAttrs('product', item, target.id) + ' href="' + escapeHtml('/public/index.php?m=product&id=' + item.id) + '" style="display:grid;grid-template-columns:88px 1fr;gap:12px;align-items:center;padding:12px 14px;border:1px solid #e2e8f0;border-radius:14px;background:#fff;text-decoration:none;color:inherit;">' +
                                (item.litpic ? '<img src="' + escapeHtml(item.litpic) + '" alt="' + escapeHtml(item.title || '') + '" style="width:88px;height:72px;object-fit:cover;border-radius:10px;">' : '<div style="width:88px;height:72px;border-radius:10px;background:linear-gradient(135deg,#dbeafe,#eff6ff);"></div>') +
                                '<div><div style="font-size:16px;font-weight:700;line-height:1.5;">' + escapeHtml(item.title || '') + '</div>' + (showPrice ? '<div style="margin-top:6px;font-size:16px;font-weight:800;color:#2563eb;">' + escapeHtml(item.price || '') + '</div>' : '') + '</div>' +
                            '</a>';
                        });
                        html += '</div>';
                    } else if (variant === 'cards') {
                        html += '<div style="display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;margin-top:18px;">';
                        items.forEach(function (item) {
                            html += '<a ' + businessItemAttrs('product', item, target.id) + ' href="' + escapeHtml('/public/index.php?m=product&id=' + item.id) + '" style="display:block;padding:16px;border:1px solid #e2e8f0;border-radius:18px;background:#fff;box-shadow:0 10px 22px rgba(15,23,42,.05);text-decoration:none;color:inherit;">' +
                                (item.litpic ? '<img src="' + escapeHtml(item.litpic) + '" alt="' + escapeHtml(item.title || '') + '" style="width:100%;height:160px;object-fit:cover;border-radius:14px;">' : '<div style="width:100%;height:160px;border-radius:14px;background:linear-gradient(135deg,#dbeafe,#eff6ff);"></div>') +
                                '<div style="margin-top:14px;font-size:18px;font-weight:700;line-height:1.5;">' + escapeHtml(item.title || '') + '</div>' +
                                (showPrice ? '<div style="margin-top:8px;font-size:18px;font-weight:800;color:#2563eb;">' + escapeHtml(item.price || '') + '</div>' : '') +
                            '</a>';
                        });
                        html += '</div>';
                    } else if (variant === 'minimal') {
                        html += '<div style="display:grid;gap:10px;margin-top:16px;">';
                        items.forEach(function (item) {
                            html += '<a ' + businessItemAttrs('product', item, target.id) + ' href="' + escapeHtml('/public/index.php?m=product&id=' + item.id) + '" style="display:flex;align-items:center;justify-content:space-between;gap:14px;padding:16px 0;border-bottom:1px solid #e2e8f0;text-decoration:none;color:inherit;">' +
                                '<div><div style="font-size:18px;font-weight:700;line-height:1.5;">' + escapeHtml(item.title || '') + '</div><div style="margin-top:6px;font-size:13px;color:#64748b;">产品详情 / 立即查看</div></div>' +
                                (showPrice ? '<div style="font-size:18px;font-weight:800;color:#2563eb;white-space:nowrap;">' + escapeHtml(item.price || '') + '</div>' : '<div style="font-size:13px;color:#94a3b8;">查看</div>') +
                            '</a>';
                        });
                        html += '</div>';
                    } else {
                        html += '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;margin-top:16px;">';
                        items.forEach(function (item) {
                            html += '<a ' + businessItemAttrs('product', item, target.id) + ' href="' + escapeHtml('/public/index.php?m=product&id=' + item.id) + '" style="display:block;padding:16px;border:1px solid #e2e8f0;border-radius:14px;text-decoration:none;color:inherit;">' +
                                (item.litpic ? '<img src="' + escapeHtml(item.litpic) + '" alt="' + escapeHtml(item.title || '') + '" style="width:100%;height:180px;object-fit:cover;border-radius:12px;">' : '<div style="width:100%;height:180px;border-radius:12px;background:linear-gradient(135deg,#dbeafe,#eff6ff);"></div>') +
                                '<div style="margin-top:12px;font-size:18px;font-weight:700;line-height:1.5;">' + escapeHtml(item.title || '') + '</div>' +
                                (showPrice ? '<div style="margin-top:8px;font-size:18px;font-weight:800;color:#2563eb;">' + escapeHtml(item.price || '') + '</div>' : '') +
                            '</a>';
                        });
                        html += '</div>';
                    }
                }
                html += '</div>';
                target.html = html;
                applyExtraRenderedHtml(target, options);
            });
            return;
        }
        if (target.type === 'carousel') {
            fetchBusinessData('slides').then(function (result) {
                var dataSource = (target.config && target.config.data_source) || 'slides';
                var items = result && result.data ? result.data : [];
                var customItems = parseDelimitedCollection(target.config && target.config.items ? target.config.items : '', 3).map(function (parts) {
                    return {
                        pic_path: extraLineValue(parts, 0, ''),
                        title: extraLineValue(parts, 1, ''),
                        link_url: extraLineValue(parts, 2, '')
                    };
                });
                if (dataSource === 'custom') {
                    items = customItems;
                }
                var height = clampNumber(target.config && target.config.height ? target.config.height : 420, 420, 160, 920);
                var title = (target.config && target.config.title) || '轮播图';
                var variant = (target.config && target.config.variant) || 'banner';
                var showDots = normalizeToggleValue(target.config && target.config.show_dots != null ? target.config.show_dots : 1, true) === '1';
                var autoplaySeconds = clampNumber(target.config && target.config.autoplay_seconds ? target.config.autoplay_seconds : 4, 4, 1, 20);
                var html = '<div class="builder-card"><h2 class="builder-title" style="font-size:28px;">' + escapeHtml(title) + '</h2>';
                if (!items.length) {
                    html += '<div style="padding:24px;border:1px dashed #cbd5e1;border-radius:16px;color:#64748b;">当前没有可用轮播图，请先在后台维护幻灯片数据，或切换为自定义轮播图。</div>';
                } else {
                    html += '<div data-front-carousel data-carousel-delay="' + escapeHtml(autoplaySeconds * 1000) + '" style="position:relative;margin-top:16px;">' +
                        '<div class="cms-front-carousel-track" style="position:relative;overflow:hidden;border-radius:20px;">';
                    items.slice(0, 8).forEach(function (item, index) {
                        var itemDisplay = index === 0 ? 'block' : 'none';
                        var cardPadding = variant === 'cards' ? 'padding:14px;background:#fff;border:1px solid #e2e8f0;border-radius:20px;' : '';
                        var imageRadius = variant === 'cards' ? 16 : 20;
                        html += '<div class="cms-front-carousel-slide" data-carousel-slide="' + index + '" style="display:' + itemDisplay + ';">' +
                            '<a href="' + escapeHtml(item.link_url || 'javascript:void(0)') + '" style="display:block;text-decoration:none;color:inherit;' + cardPadding + '">' +
                                '<img src="' + escapeHtml(item.pic_path || '') + '" alt="' + escapeHtml(item.title || '') + '" style="width:100%;height:' + escapeHtml(height) + 'px;object-fit:cover;border-radius:' + imageRadius + 'px;background:#f1f5f9;' + (variant === 'fade' ? 'box-shadow:0 22px 54px rgba(15,23,42,.12);' : '') + '">' +
                                (item.title ? '<div style="margin-top:14px;font-size:18px;font-weight:700;line-height:1.6;">' + escapeHtml(item.title) + '</div>' : '') +
                            '</a>' +
                        '</div>';
                    });
                    html += '</div>';
                    if (showDots && items.length > 1) {
                        html += '<div class="cms-front-carousel-dots" style="display:flex;justify-content:center;gap:8px;margin-top:14px;">';
                        items.slice(0, 8).forEach(function (item, index) {
                            html += '<button type="button" data-carousel-dot="' + index + '" style="width:12px;height:12px;border-radius:999px;border:0;cursor:pointer;' + (index === 0 ? 'background:#2563eb;' : 'background:#cbd5e1;') + '"></button>';
                        });
                        html += '</div>';
                    }
                    html += '</div>';
                }
                html += '</div>';
                target.html = html;
                applyExtraRenderedHtml(target, options);
            });
            return;
        }
        if (target.type === 'image_text') {
            var imageTextLayout = (target.config && target.config.layout) || 'left-image';
            var imageTextGap = clampNumber(target.config && target.config.gap ? target.config.gap : 24, 24, 0, 80);
            var imageTextImage = (target.config && target.config.image_url) || materialMockImage('图文展示', '内容预览', '#dbeafe', '#eff6ff', false);
            var imageTextButtonText = (target.config && target.config.button_text) || '了解更多';
            var imageTextButtonUrl = (target.config && target.config.button_url) || 'javascript:void(0)';
            var imageTextIntro = '<div><div style="font-size:12px;font-weight:700;color:#5b74e8;">' + escapeHtml((target.config && target.config.title) || '图文展示') + '</div>' +
                '<h2 style="margin:14px 0 0;font-size:32px;line-height:1.35;color:#0f172a;">' + escapeHtml((target.config && target.config.heading) || '模块主标题') + '</h2>' +
                '<p style="margin:14px 0 0;color:#64748b;line-height:1.9;">' + escapeHtml((target.config && target.config.content) || '这里填写图文模块的介绍内容，适合品牌介绍、优势说明、服务介绍等。') + '</p>' +
                (target.config && target.config.subtitle ? '<div style="margin-top:10px;font-size:13px;color:#94a3b8;">' + escapeHtml(target.config.subtitle) + '</div>' : '') +
                '<a href="' + escapeHtml(imageTextButtonUrl) + '" style="display:inline-flex;margin-top:18px;padding:12px 18px;border-radius:999px;background:#111827;color:#fff;text-decoration:none;font-weight:700;">' + escapeHtml(imageTextButtonText) + '</a></div>';
            var imageTextMedia = '<div><img src="' + escapeHtml(imageTextImage) + '" alt="' + escapeHtml((target.config && target.config.heading) || '图文展示') + '" style="display:block;width:100%;height:320px;object-fit:cover;border-radius:22px;background:#f1f5f9;"></div>';
            var imageTextBody = '';
            if (imageTextLayout === 'right-image') {
                imageTextBody = imageTextIntro + imageTextMedia;
            } else if (imageTextLayout === 'top-image') {
                imageTextBody = imageTextMedia + imageTextIntro;
            } else if (imageTextLayout === 'bottom-image') {
                imageTextBody = imageTextIntro + imageTextMedia;
            } else {
                imageTextBody = imageTextMedia + imageTextIntro;
            }
            var imageTextGrid = (imageTextLayout === 'top-image' || imageTextLayout === 'bottom-image')
                ? 'display:grid;gap:' + imageTextGap + 'px;'
                : 'display:grid;grid-template-columns:1fr 1fr;align-items:center;gap:' + imageTextGap + 'px;';
            target.html = '<section class="builder-card" style="padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="' + imageTextGrid + '">' + imageTextBody + '</div></section>';
            applyExtraRenderedHtml(target, options);
            return;
        }
        if (target.type === 'multi_image') {
            var multiColumns = clampNumber(target.config && target.config.columns ? target.config.columns : 4, 4, 1, 6);
            var multiGap = clampNumber(target.config && target.config.gap ? target.config.gap : 16, 16, 0, 48);
            var multiHeight = clampNumber(target.config && target.config.image_height ? target.config.image_height : 180, 180, 80, 480);
            var multiRadius = clampNumber(target.config && target.config.radius ? target.config.radius : 18, 18, 0, 48);
            var multiItems = parseDelimitedCollection(target.config && target.config.items ? target.config.items : '', 4);
            if (!multiItems.length) {
                multiItems = [
                    [materialMockImage('案例 A', '主推内容', '#dbeafe', '#eff6ff', false), '展示图 A', '简洁说明文字，可直接编辑。', ''],
                    [materialMockImage('案例 B', '空间实景', '#fde68a', '#fff7ed', false), '展示图 B', '支持替换成真实图片。', ''],
                    [materialMockImage('案例 C', '热门产品', '#c7d2fe', '#eef2ff', false), '展示图 C', '适合首页主推区。', ''],
                    [materialMockImage('案例 D', '品牌展示', '#bbf7d0', '#ecfccb', false), '展示图 D', '新增后可继续拖拽调整。', '']
                ];
            }
            var multiHtml = '<section class="builder-card" style="padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div><div style="font-size:12px;font-weight:700;color:#5b74e8;">' + escapeHtml((target.config && target.config.title) || '列表多图') + '</div></div><div style="display:grid;grid-template-columns:repeat(' + multiColumns + ',minmax(0,1fr));gap:' + multiGap + 'px;margin-top:22px;">';
            multiItems.forEach(function (parts) {
                var imageUrl = extraLineValue(parts, 0, materialMockImage('图片', '展示', '#dbeafe', '#eff6ff', false));
                var label = extraLineValue(parts, 1, '展示项');
                var desc = extraLineValue(parts, 2, '');
                var link = extraLineValue(parts, 3, 'javascript:void(0)');
                multiHtml += '<a href="' + escapeHtml(link || 'javascript:void(0)') + '" style="display:block;text-decoration:none;color:inherit;">' +
                    '<img src="' + escapeHtml(imageUrl) + '" alt="' + escapeHtml(label) + '" style="display:block;width:100%;height:' + multiHeight + 'px;object-fit:cover;border-radius:' + multiRadius + 'px;background:#f1f5f9;">' +
                    '<h3 style="margin:14px 0 0;font-size:18px;color:#0f172a;">' + escapeHtml(label) + '</h3>' +
                    (desc ? '<p style="margin:8px 0 0;color:#64748b;line-height:1.8;">' + escapeHtml(desc) + '</p>' : '') +
                '</a>';
            });
            multiHtml += '</div></section>';
            target.html = multiHtml;
            applyExtraRenderedHtml(target, options);
            return;
        }
        if (target.type === 'gallery') {
            var galleryColumns = clampNumber(target.config && target.config.columns ? target.config.columns : 4, 4, 1, 6);
            var galleryHeight = clampNumber(target.config && target.config.thumb_height ? target.config.thumb_height : 160, 160, 80, 420);
            var galleryItems = parseDelimitedCollection(target.config && target.config.items ? target.config.items : '', 3);
            if (!galleryItems.length) {
                galleryItems = [
                    [materialMockImage('图册封面', '精选案例集', '#dbeafe', '#eff6ff', false), materialMockImage('图册封面', '精选案例集', '#dbeafe', '#eff6ff', false), '精选案例集'],
                    [materialMockImage('工厂实拍', '生产环境', '#c7d2fe', '#eef2ff', false), materialMockImage('工厂实拍', '生产环境', '#c7d2fe', '#eef2ff', false), '生产环境'],
                    [materialMockImage('项目落地', '空间效果', '#bfdbfe', '#e0f2fe', false), materialMockImage('项目落地', '空间效果', '#bfdbfe', '#e0f2fe', false), '空间效果']
                ];
            }
            var galleryHtml = '<section class="builder-card" style="padding:28px;border-radius:24px;background:linear-gradient(180deg,#fff,#fbfcff);border:1px solid #e7edf7;"><div><div style="font-size:12px;font-weight:700;color:#5b74e8;">' + escapeHtml((target.config && target.config.title) || '图册目录') + '</div><h2 style="margin:12px 0 0;font-size:32px;color:#0f172a;">' + escapeHtml((target.config && target.config.subtitle) || '点击缩略图可查看大图') + '</h2></div><div style="display:grid;grid-template-columns:repeat(' + galleryColumns + ',minmax(0,1fr));gap:16px;margin-top:22px;">';
            galleryItems.forEach(function (parts, index) {
                var thumb = extraLineValue(parts, 0, '');
                var full = extraLineValue(parts, 1, thumb);
                var label = extraLineValue(parts, 2, '图册 ' + (index + 1));
                galleryHtml += '<button type="button" data-front-gallery-open="' + escapeHtml(full) + '" data-front-gallery-title="' + escapeHtml(label) + '" style="padding:0;border:0;background:transparent;cursor:pointer;text-align:left;">' +
                    '<img src="' + escapeHtml(thumb) + '" alt="' + escapeHtml(label) + '" style="display:block;width:100%;height:' + galleryHeight + 'px;object-fit:cover;border-radius:18px;background:#f1f5f9;">' +
                    '<div style="margin-top:10px;font-size:15px;font-weight:700;color:#0f172a;">' + escapeHtml(label) + '</div>' +
                '</button>';
            });
            galleryHtml += '</div></section>';
            target.html = galleryHtml;
            applyExtraRenderedHtml(target, options);
            return;
        }
        if (target.type === 'video_embed') {
            var videoMode = (target.config && target.config.mode) || 'upload';
            var videoHeight = clampNumber(target.config && target.config.height ? target.config.height : 420, 420, 180, 920);
            var videoUrl = (target.config && target.config.video_url) || '';
            var posterUrl = (target.config && target.config.poster_url) || '';
            var videoAutoplay = normalizeToggleValue(target.config && target.config.autoplay ? target.config.autoplay : 0, false) === '1';
            var videoLoop = normalizeToggleValue(target.config && target.config.loop ? target.config.loop : 0, false) === '1';
            var videoHtml = '<div style="margin-top:18px;padding:18px;border-radius:20px;background:#0f172a;">';
            if (videoMode === 'embed' && String(target.config && target.config.embed_html ? target.config.embed_html : '').trim()) {
                videoHtml += '<div style="min-height:' + videoHeight + 'px;border-radius:16px;overflow:hidden;background:#000;">' + String(target.config.embed_html || '') + '</div>';
            } else if (videoUrl) {
                videoHtml += '<video controls ' + (videoAutoplay ? 'autoplay muted playsinline ' : '') + (videoLoop ? 'loop ' : '') + 'poster="' + escapeHtml(posterUrl) + '" style="display:block;width:100%;height:' + videoHeight + 'px;border-radius:16px;background:#000;object-fit:cover;"><source src="' + escapeHtml(videoUrl) + '"></video>';
            } else {
                videoHtml += '<div style="display:flex;align-items:center;justify-content:center;height:' + videoHeight + 'px;border-radius:16px;background:linear-gradient(135deg,#1e293b,#0f172a);color:#fff;">请先上传视频或粘贴嵌入代码</div>';
            }
            videoHtml += '</div>';
            target.html = '<section class="builder-card" style="padding:28px;border-radius:24px;background:#fff;border:1px solid #e7edf7;"><div style="font-size:12px;font-weight:700;color:#5b74e8;">' + escapeHtml((target.config && target.config.title) || '在线视频') + '</div><h2 style="margin:12px 0 0;font-size:32px;color:#0f172a;">' + escapeHtml((target.config && target.config.subtitle) || '支持本地视频上传或外链嵌入') + '</h2>' + videoHtml + '</section>';
            applyExtraRenderedHtml(target, options);
            return;
        }
        if (target.type === 'channel_nav') {
            var navVariant = (target.config && target.config.variant) || 'pill';
            var navItems = parseDelimitedCollection(target.config && target.config.items ? target.config.items : '', 2).map(function (parts) {
                return {
                    label: extraLineValue(parts, 0, ''),
                    url: extraLineValue(parts, 1, '')
                };
            });
            if (!navItems.length) {
                navItems = currentColumnOptions().map(function (item) {
                    return {
                        label: item.label || item.value || '',
                        url: item.value || ''
                    };
                });
            }
            var activeUrl = (target.config && target.config.active_url) || 'auto';
            var currentUrl = normalizeColumnUrl(window.location.href);
            var navHtml = '<section class="builder-card" style="padding:18px 22px;border-radius:22px;background:#fff;border:1px solid #e7edf7;"><div style="font-size:12px;font-weight:700;color:#5b74e8;">' + escapeHtml((target.config && target.config.title) || '栏目导航') + '</div><div style="display:flex;flex-wrap:wrap;gap:12px;margin-top:14px;">';
            navItems.forEach(function (item) {
                var itemUrl = normalizeColumnUrl(item.url || '');
                var isActive = activeUrl === 'auto' ? (itemUrl && itemUrl === currentUrl) : (String(activeUrl) === String(item.url || ''));
                var style = 'display:inline-flex;align-items:center;justify-content:center;text-decoration:none;font-size:14px;font-weight:700;';
                if (navVariant === 'grid') {
                    style += 'min-width:140px;padding:14px 18px;border-radius:16px;';
                } else if (navVariant === 'line') {
                    style += 'padding:10px 2px;border-radius:0;border-bottom:2px solid ' + (isActive ? '#111827' : 'transparent') + ';';
                } else {
                    style += 'padding:11px 18px;border-radius:999px;';
                }
                style += isActive ? 'background:#111827;color:#fff;' : 'background:#fff;color:#334155;border:1px solid #dbe3f4;';
                navHtml += '<a href="' + escapeHtml(item.url || 'javascript:void(0)') + '" style="' + style + '">' + escapeHtml(item.label || item.url || '导航') + '</a>';
            });
            navHtml += '</div></section>';
            target.html = navHtml;
            applyExtraRenderedHtml(target, options);
            return;
        }
        if (target.type === 'guestbook_form') {
            var title = (target.config && target.config.title) || '留下需求，我们尽快联系您';
            var subtitle = (target.config && target.config.subtitle) || '适合展示在联系页、首页底部或专题页，引导用户提交咨询。';
            var buttonText = (target.config && target.config.button_text) || '提交留言';
            var variant = (target.config && target.config.variant) || 'default';
            var shellStyle = 'padding:24px;border:1px solid #e2e8f0;border-radius:18px;background:#fff;';
            var introHtml = '<h2 class="builder-title" style="font-size:28px;">' + escapeHtml(title) + '</h2><p style="margin-top:10px;font-size:14px;line-height:1.8;color:#64748b;">' + escapeHtml(subtitle) + '</p>';
            var formHtml = '<form data-front-guestbook-form style="display:grid;gap:12px;margin-top:18px;">' +
                '<input type="text" name="name" placeholder="姓名" style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:12px;">' +
                '<input type="text" name="phone" placeholder="电话" style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:12px;">' +
                '<input type="text" name="title" placeholder="标题" style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:12px;">' +
                '<textarea name="content" placeholder="留言内容" style="width:100%;min-height:110px;padding:12px 14px;border:1px solid #cbd5e1;border-radius:12px;resize:vertical;"></textarea>' +
                '<button type="submit" style="padding:12px 18px;border:0;border-radius:12px;background:#111827;color:#fff;cursor:pointer;">' + escapeHtml(buttonText) + '</button>' +
                '<div data-front-guestbook-message style="font-size:13px;color:#64748b;"></div>' +
            '</form>';
            if (variant === 'centered') {
                shellStyle = 'padding:32px;border:1px solid #dbeafe;border-radius:24px;background:linear-gradient(135deg,#eff6ff,#fff);max-width:860px;margin:0 auto;text-align:center;';
                formHtml = '<form data-front-guestbook-form style="display:grid;gap:12px;margin:22px auto 0;max-width:620px;text-align:left;">' +
                    '<input type="text" name="name" placeholder="姓名" style="width:100%;padding:14px 16px;border:1px solid #cbd5e1;border-radius:14px;">' +
                    '<input type="text" name="phone" placeholder="电话" style="width:100%;padding:14px 16px;border:1px solid #cbd5e1;border-radius:14px;">' +
                    '<input type="text" name="title" placeholder="标题" style="width:100%;padding:14px 16px;border:1px solid #cbd5e1;border-radius:14px;">' +
                    '<textarea name="content" placeholder="留言内容" style="width:100%;min-height:130px;padding:14px 16px;border:1px solid #cbd5e1;border-radius:14px;resize:vertical;"></textarea>' +
                    '<button type="submit" style="padding:14px 20px;border:0;border-radius:14px;background:#2563eb;color:#fff;cursor:pointer;">' + escapeHtml(buttonText) + '</button>' +
                    '<div data-front-guestbook-message style="font-size:13px;color:#64748b;"></div>' +
                '</form>';
            } else if (variant === 'split') {
                shellStyle = 'padding:28px;border:1px solid #e2e8f0;border-radius:22px;background:#fff;';
                introHtml = '<div style="padding:10px 8px 10px 0;"><div style="font-size:13px;font-weight:700;color:#5b74e8;">服务咨询</div><h2 class="builder-title" style="margin-top:12px;font-size:32px;line-height:1.4;">' + escapeHtml(title) + '</h2><p style="margin-top:12px;font-size:14px;line-height:1.9;color:#64748b;">' + escapeHtml(subtitle) + '</p><div style="display:grid;gap:10px;margin-top:18px;"><div style="padding:14px 16px;border-radius:14px;background:#f8fbff;">7*12 小时在线回复</div><div style="padding:14px 16px;border-radius:14px;background:#f8fbff;">支持报价 / 打样 / 定制咨询</div></div></div>';
                formHtml = '<form data-front-guestbook-form style="display:grid;gap:12px;padding:20px;border-radius:18px;background:#f8fbff;">' +
                    '<input type="text" name="name" placeholder="姓名" style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:12px;background:#fff;">' +
                    '<input type="text" name="phone" placeholder="电话" style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:12px;background:#fff;">' +
                    '<input type="text" name="title" placeholder="标题" style="width:100%;padding:12px 14px;border:1px solid #cbd5e1;border-radius:12px;background:#fff;">' +
                    '<textarea name="content" placeholder="留言内容" style="width:100%;min-height:140px;padding:12px 14px;border:1px solid #cbd5e1;border-radius:12px;background:#fff;resize:vertical;"></textarea>' +
                    '<button type="submit" style="padding:12px 18px;border:0;border-radius:12px;background:#111827;color:#fff;cursor:pointer;">' + escapeHtml(buttonText) + '</button>' +
                    '<div data-front-guestbook-message style="font-size:13px;color:#64748b;"></div>' +
                '</form>';
                target.html = '<div class="builder-card" style="' + shellStyle + '"><div style="display:grid;grid-template-columns:1fr 1fr;gap:28px;align-items:start;">' + introHtml + formHtml + '</div></div>';
                refreshRenderedState(state.selectedPath);
                return;
            } else if (variant === 'banner') {
                shellStyle = 'padding:28px;border-radius:24px;background:linear-gradient(135deg,#0f172a,#1e3a8a);color:#fff;';
                introHtml = '<div style="display:flex;align-items:center;justify-content:space-between;gap:20px;flex-wrap:wrap;"><div><div style="font-size:13px;font-weight:700;color:#93c5fd;">在线咨询</div><h2 class="builder-title" style="margin-top:12px;font-size:34px;line-height:1.4;color:#fff;">' + escapeHtml(title) + '</h2><p style="margin-top:12px;font-size:14px;line-height:1.9;color:rgba(255,255,255,.78);">' + escapeHtml(subtitle) + '</p></div><div style="display:flex;gap:12px;flex-wrap:wrap;"><span style="padding:10px 14px;border-radius:999px;background:rgba(255,255,255,.12);">免费报价</span><span style="padding:10px 14px;border-radius:999px;background:rgba(255,255,255,.12);">专属顾问</span></div></div>';
                formHtml = '<form data-front-guestbook-form style="display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-top:24px;">' +
                    '<input type="text" name="name" placeholder="姓名" style="padding:12px 14px;border:1px solid rgba(255,255,255,.18);border-radius:12px;background:rgba(255,255,255,.08);color:#fff;">' +
                    '<input type="text" name="phone" placeholder="电话" style="padding:12px 14px;border:1px solid rgba(255,255,255,.18);border-radius:12px;background:rgba(255,255,255,.08);color:#fff;">' +
                    '<input type="text" name="title" placeholder="标题" style="padding:12px 14px;border:1px solid rgba(255,255,255,.18);border-radius:12px;background:rgba(255,255,255,.08);color:#fff;">' +
                    '<button type="submit" style="padding:12px 18px;border:0;border-radius:12px;background:#fff;color:#0f172a;cursor:pointer;font-weight:700;">' + escapeHtml(buttonText) + '</button>' +
                    '<textarea name="content" placeholder="留言内容" style="grid-column:1 / -1;min-height:110px;padding:12px 14px;border:1px solid rgba(255,255,255,.18);border-radius:12px;background:rgba(255,255,255,.08);color:#fff;resize:vertical;"></textarea>' +
                    '<div data-front-guestbook-message style="grid-column:1 / -1;font-size:13px;color:rgba(255,255,255,.72);"></div>' +
                '</form>';
            } else if (variant === 'minimal') {
                shellStyle = 'padding:0;border-radius:0;background:transparent;';
                introHtml = '<div style="display:flex;align-items:end;justify-content:space-between;gap:18px;flex-wrap:wrap;padding-bottom:16px;border-bottom:1px solid #e2e8f0;"><div><h2 class="builder-title" style="font-size:28px;">' + escapeHtml(title) + '</h2><p style="margin-top:10px;font-size:14px;line-height:1.8;color:#64748b;">' + escapeHtml(subtitle) + '</p></div><span style="font-size:13px;color:#94a3b8;">快速预约 / 免费咨询</span></div>';
                formHtml = '<form data-front-guestbook-form style="display:grid;grid-template-columns:1fr 1fr 1fr auto;gap:12px;margin-top:18px;">' +
                    '<input type="text" name="name" placeholder="姓名" style="padding:12px 14px;border:1px solid #cbd5e1;border-radius:999px;">' +
                    '<input type="text" name="phone" placeholder="电话" style="padding:12px 14px;border:1px solid #cbd5e1;border-radius:999px;">' +
                    '<input type="text" name="title" placeholder="标题" style="padding:12px 14px;border:1px solid #cbd5e1;border-radius:999px;">' +
                    '<button type="submit" style="padding:12px 22px;border:0;border-radius:999px;background:#111827;color:#fff;cursor:pointer;">' + escapeHtml(buttonText) + '</button>' +
                    '<textarea name="content" placeholder="留言内容" style="grid-column:1 / -1;min-height:92px;padding:12px 14px;border:1px solid #cbd5e1;border-radius:18px;resize:vertical;"></textarea>' +
                    '<div data-front-guestbook-message style="grid-column:1 / -1;font-size:13px;color:#64748b;"></div>' +
                '</form>';
            }
            target.html = '' +
                '<div class="builder-card" style="' + shellStyle + '">' +
                    introHtml +
                    formHtml +
                '</div>';
            applyExtraRenderedHtml(target, options);
            return;
        }
        if (target.type === 'qrcode') {
        var qrTitle = (target.config && target.config.title) || '微信二维码';
            var qrSubtitle = (target.config && target.config.subtitle) || '瀵邦喕淇婇幍顐ょ垳缁斿宓嗛崪銊嚄';
            var qrImage = (target.config && target.config.image_url) || '';
            target.html = '' +
                '<div class="builder-card" style="padding:24px;border:1px solid #e2e8f0;border-radius:18px;background:#fff;text-align:center;">' +
                    '<h2 class="builder-title" style="font-size:28px;">' + escapeHtml(qrTitle) + '</h2>' +
                    '<p style="margin-top:10px;font-size:14px;line-height:1.8;color:#64748b;">' + escapeHtml(qrSubtitle) + '</p>' +
                    (qrImage ? '<img src="' + escapeHtml(qrImage) + '" alt="' + escapeHtml(qrTitle) + '" style="width:220px;height:220px;object-fit:cover;border-radius:16px;margin-top:18px;">' : '<div style="width:220px;height:220px;border-radius:16px;background:linear-gradient(135deg,#dbeafe,#eff6ff);margin:18px auto 0 auto;"></div>') +
                '</div>';
            applyExtraRenderedHtml(target, options);
            return;
        }
        if (target.type === 'friend_links') {
            fetchBusinessData('links').then(function (result) {
                var items = result && result.data ? result.data : [];
                var dataSource = (target.config && target.config.data_source) || 'links';
                if (dataSource === 'custom') {
                    items = parseDelimitedCollection(target.config && target.config.items ? target.config.items : '', 3).map(function (parts) {
                        return {
                            webname: extraLineValue(parts, 0, ''),
                            url: extraLineValue(parts, 1, ''),
                            logo: extraLineValue(parts, 2, '')
                        };
                    });
                }
                var title = (target.config && target.config.title) || '友情链接';
                var variant = (target.config && target.config.variant) || 'text';
                var columns = clampNumber(target.config && target.config.columns ? target.config.columns : 4, 4, 1, 6);
                var gap = clampNumber(target.config && target.config.gap ? target.config.gap : 14, 14, 0, 48);
                var html = '<div class="builder-card" style="padding:24px;border:1px solid #e2e8f0;border-radius:18px;background:#fff;"><h2 class="builder-title" style="font-size:28px;">' + escapeHtml(title) + '</h2>';
                if (!items.length) {
                    html += '<div style="margin-top:16px;padding:24px;border:1px dashed #cbd5e1;border-radius:16px;color:#64748b;">请先在后台维护友情链接数据，或切换为自定义链接。</div>';
                } else if (variant === 'logo') {
                    html += '<div style="display:grid;grid-template-columns:repeat(' + columns + ',minmax(0,1fr));gap:' + gap + 'px;margin-top:16px;">';
                    items.forEach(function (item) {
                        html += '<a href="' + escapeHtml(item.url || 'javascript:void(0)') + '" style="display:flex;align-items:center;justify-content:center;padding:16px;border:1px solid #e2e8f0;border-radius:14px;text-decoration:none;color:inherit;">' +
                            (item.logo ? '<img src="' + escapeHtml(item.logo) + '" alt="' + escapeHtml(item.webname || '') + '" style="max-width:100%;max-height:48px;object-fit:contain;">' : '<span style="font-size:14px;font-weight:700;">' + escapeHtml(item.webname || '') + '</span>') +
                        '</a>';
                    });
                    html += '</div>';
                } else if (variant === 'icon') {
                    html += '<div style="display:grid;grid-template-columns:repeat(' + columns + ',minmax(0,1fr));gap:' + gap + 'px;margin-top:16px;">';
                    items.forEach(function (item) {
                        html += '<a href="' + escapeHtml(item.url || 'javascript:void(0)') + '" style="display:flex;align-items:center;gap:12px;padding:14px 16px;border:1px solid #e2e8f0;border-radius:16px;text-decoration:none;color:inherit;background:#fff;">' +
                            (item.logo ? '<img src="' + escapeHtml(item.logo) + '" alt="' + escapeHtml(item.webname || '') + '" style="width:28px;height:28px;border-radius:999px;object-fit:cover;">' : '<span style="display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:999px;background:#eff6ff;color:#2563eb;font-weight:800;">' + escapeHtml((item.webname || '链').slice(0, 1)) + '</span>') +
                            '<span style="font-size:14px;font-weight:700;">' + escapeHtml(item.webname || '') + '</span>' +
                        '</a>';
                    });
                    html += '</div>';
                } else {
                    html += '<div style="display:flex;flex-wrap:wrap;gap:' + gap + 'px;margin-top:16px;">';
                    items.forEach(function (item) {
                        html += '<a href="' + escapeHtml(item.url || 'javascript:void(0)') + '" style="padding:10px 14px;border:1px solid #e2e8f0;border-radius:999px;text-decoration:none;color:inherit;font-size:14px;font-weight:600;">' + escapeHtml(item.webname || '') + '</a>';
                    });
                    html += '</div>';
                }
                html += '</div>';
                target.html = html;
                applyExtraRenderedHtml(target, options);
            });
            return;
        }
        if (target.type === 'service_panel') {
            var contact = state.businessOptions.site_contact || {};
            var title = (target.config && target.config.title) || '在线客服';
            var subtitle = (target.config && target.config.subtitle) || '支持电话、QQ、微信和企业邮箱展示';
            var phone = (target.config && target.config.phone) || contact.site_phone || contact.site_tel || '';
            var qq = (target.config && target.config.qq) || contact.site_qq || '';
            var wechat = (target.config && target.config.wechat) || contact.site_wechat || '';
            var email = (target.config && target.config.email) || contact.site_email || '';
            var variant = (target.config && target.config.variant) || 'cards';
            var serviceItems = [];
            if (phone) {
                serviceItems.push('<a href="tel:' + escapeHtml(phone) + '" style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border:1px solid #e2e8f0;border-radius:14px;text-decoration:none;color:inherit;"><span>电话</span><strong>' + escapeHtml(phone) + '</strong></a>');
            }
            if (qq) {
                serviceItems.push('<a href="https://wpa.qq.com/msgrd?v=3&uin=' + escapeHtml(qq) + '&site=qq&menu=yes" target="_blank" style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border:1px solid #e2e8f0;border-radius:14px;text-decoration:none;color:inherit;"><span>QQ</span><strong>' + escapeHtml(qq) + '</strong></a>');
            }
            if (wechat) {
                serviceItems.push('<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border:1px solid #e2e8f0;border-radius:14px;"><span>微信</span><strong>' + escapeHtml(wechat) + '</strong></div>');
            }
            if (email) {
                serviceItems.push('<a href="mailto:' + escapeHtml(email) + '" style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px;border:1px solid #e2e8f0;border-radius:14px;text-decoration:none;color:inherit;"><span>邮箱</span><strong>' + escapeHtml(email) + '</strong></a>');
            }
            var serviceHtml = '<div class="builder-card" style="padding:24px;border:1px solid #e2e8f0;border-radius:18px;background:#fff;">' +
                '<h2 class="builder-title" style="font-size:28px;">' + escapeHtml(title) + '</h2>' +
                '<p style="margin-top:10px;font-size:14px;line-height:1.8;color:#64748b;">' + escapeHtml(subtitle) + '</p>';
            if (!serviceItems.length) {
                serviceHtml += '<div style="margin-top:16px;padding:20px;border:1px dashed #cbd5e1;border-radius:16px;color:#64748b;">请先在右侧填写客服联系方式。</div>';
            } else if (variant === 'floating') {
                serviceHtml += '<div style="display:grid;gap:12px;margin-top:16px;">' + serviceItems.join('') + '</div>';
            } else if (variant === 'compact') {
                serviceHtml += '<div style="display:flex;flex-wrap:wrap;gap:12px;margin-top:16px;">' + serviceItems.map(function (item) {
                    return item.replace('display:flex;align-items:center;justify-content:space-between;', 'display:inline-flex;align-items:center;gap:8px;').replace('border-radius:14px;', 'border-radius:999px;');
                }).join('') + '</div>';
            } else {
                serviceHtml += '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px;margin-top:16px;">' + serviceItems.join('') + '</div>';
            }
            serviceHtml += '</div>';
            target.html = serviceHtml;
            refreshRenderedState(state.selectedPath);
            return;
        }
        if (target.type === 'member_portal') {
            fetchBusinessData('member_state').then(function (result) {
                var memberState = result && result.data ? result.data : (state.businessOptions.member_state || {});
                state.businessOptions.member_state = memberState;
                var title = (target.config && target.config.title) || '会员中心';
                var loginText = (target.config && target.config.login_text) || '会员登录';
                var registerText = (target.config && target.config.register_text) || '立即注册';
                var centerText = (target.config && target.config.center_text) || '进入会员中心';
                var variant = (target.config && target.config.variant) || 'card';
                var memberName = memberState.member_name || '会员';
                var html = '<div class="builder-card" style="padding:24px;border:1px solid #e2e8f0;border-radius:18px;background:#fff;">' +
                    '<h2 class="builder-title" style="font-size:28px;">' + escapeHtml(title) + '</h2>';
                if (memberState.logged_in) {
                    html += '<p style="margin-top:10px;font-size:14px;line-height:1.8;color:#64748b;">当前已登录：' + escapeHtml(memberName) + '</p>';
                    if (variant === 'minimal') {
                        html += '<div style="display:flex;flex-wrap:wrap;gap:12px;margin-top:18px;">';
                    } else {
                        html += '<div style="display:flex;flex-wrap:wrap;gap:12px;margin-top:18px;">';
                    }
                    html += '<a href="' + escapeHtml(memberState.center_url || '/member/index.php') + '" style="display:inline-flex;align-items:center;justify-content:center;padding:12px 18px;border-radius:999px;background:#111827;color:#fff;text-decoration:none;">' + escapeHtml(centerText) + '</a>' +
                        '<a href="' + escapeHtml(memberState.logout_url || '/member/logout.php') + '" style="display:inline-flex;align-items:center;justify-content:center;padding:12px 18px;border-radius:999px;border:1px solid #cbd5e1;color:#0f172a;text-decoration:none;">退出登录</a>' +
                    '</div>';
                } else {
                    html += '<p style="margin-top:10px;font-size:14px;line-height:1.8;color:#64748b;">未登录访客可直接进入登录页或注册页。</p>';
                    if (variant === 'banner') {
                        html += '<div style="display:flex;flex-wrap:wrap;gap:12px;margin-top:18px;padding:18px;border-radius:16px;background:linear-gradient(135deg,#eff6ff,#f8fafc);">';
                    } else {
                        html += '<div style="display:flex;flex-wrap:wrap;gap:12px;margin-top:18px;">';
                    }
                    html += '<a href="' + escapeHtml(memberState.login_url || '/member/login.php') + '" style="display:inline-flex;align-items:center;justify-content:center;padding:12px 18px;border-radius:999px;background:#111827;color:#fff;text-decoration:none;">' + escapeHtml(loginText) + '</a>' +
                        '<a href="' + escapeHtml(memberState.register_url || '/member/register.php') + '" style="display:inline-flex;align-items:center;justify-content:center;padding:12px 18px;border-radius:999px;border:1px solid #cbd5e1;color:#0f172a;text-decoration:none;">' + escapeHtml(registerText) + '</a>' +
                    '</div>';
                }
                html += '</div>';
                target.html = html;
                refreshRenderedState(state.selectedPath);
            });
            return;
        }
        if (target.type === 'vote_form') {
            fetchBusinessData('votes', {
                status: target.config && target.config.status ? target.config.status : '',
                keyword: target.config && target.config.keyword ? target.config.keyword : '',
                limit: target.config && target.config.limit ? target.config.limit : 10,
                order_by: target.config && target.config.order_by ? target.config.order_by : 'id_desc'
            }).then(function (result) {
                var votes = result && result.data ? result.data : [];
                state.businessOptions.votes = votes;
                var selectedVoteId = parseInt(target.config && target.config.vote_id ? target.config.vote_id : 0, 10) || 0;
                var selectedVote = null;
                votes.forEach(function (item) {
                    if (String(item.id) === String(selectedVoteId)) {
                        selectedVote = item;
                    }
                });
                if (!selectedVote && votes.length) {
                    selectedVote = votes[0];
                    if (!target.config) {
                        target.config = {};
                    }
                    target.config.vote_id = selectedVote.id;
                }
                var title = (target.config && target.config.title) || '在线投票';
                var buttonText = (target.config && target.config.button_text) || '立即投票';
                var showResult = String(target.config && target.config.show_result != null ? target.config.show_result : 1) !== '0';
                var html = '<div class="builder-card" style="padding:24px;border:1px solid #e2e8f0;border-radius:18px;background:#fff;">' +
                    '<h2 class="builder-title" style="font-size:28px;">' + escapeHtml(title) + '</h2>';
                if (!selectedVote) {
                    html += '<div style="margin-top:16px;padding:20px;border:1px dashed #cbd5e1;border-radius:16px;color:#64748b;">当前没有可用投票，请先在后台投票管理中创建主题。</div>';
                    html += '</div>';
                    target.html = html;
                    applyExtraRenderedHtml(target, options);
                    return;
                }
                var inputType = String(selectedVote.vote_type || 1) === '2' ? 'checkbox' : 'radio';
                html += '<p style="margin-top:10px;font-size:14px;line-height:1.8;color:#64748b;">' + escapeHtml(selectedVote.vote_title || '') + '</p>' +
                    '<form data-front-vote-form data-vote-id="' + escapeHtml(selectedVote.id) + '" style="display:grid;gap:12px;margin-top:18px;">';
                (selectedVote.vote_options || []).forEach(function (option, index) {
                    var optionText = option && option.text ? option.text : ('选项 ' + (index + 1));
                    var optionVotes = parseInt(option && option.votes ? option.votes : 0, 10) || 0;
                    html += '<label style="display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 16px;border:1px solid #e2e8f0;border-radius:14px;">' +
                        '<span style="display:flex;align-items:center;gap:10px;"><input type="' + inputType + '" name="vote_option' + (inputType === 'checkbox' ? '[]' : '') + '" value="' + index + '"><span>' + escapeHtml(optionText) + '</span></span>' +
                        (showResult ? '<strong style="color:#2563eb;">' + escapeHtml(optionVotes) + ' 票</strong>' : '') +
                    '</label>';
                });
                html += '<button type="submit" style="padding:12px 18px;border:0;border-radius:12px;background:#111827;color:#fff;cursor:pointer;">' + escapeHtml(buttonText) + '</button>' +
                    '<div data-front-vote-message style="font-size:13px;color:#64748b;"></div>' +
                '</form></div>';
                target.html = html;
                applyExtraRenderedHtml(target, options);
            });
            return;
        }
    }

    function applySelectedNodeValue(key, value) {
        if (!state.selectedNode || !state.selectedPath) {
            return false;
        }
        if (isProtectedEditableNode(state.selectedNode)) {
            setStatus(blockProtectedEditMessage());
            return false;
        }
        if (listHasRef((state.page.frontend_locked_paths || []), nodeRef(state.selectedNode))) {
            setStatus('当前区块已锁定，解除锁定后才能编辑。');
            return false;
        }
        var selectedRef = nodeRef(state.selectedNode);
        var overrideItem = ensureOverrideEntry(selectedRef);
        if (!overrideItem) {
            return false;
        }
        overrideItem[key] = value;
        if (key === 'text') {
            state.selectedNode.textContent = value;
            delete overrideItem.html;
        } else if (key === 'html') {
            state.selectedNode.innerHTML = value !== '' ? value : '';
        } else if (key === 'src' && state.selectedNode.tagName.toLowerCase() === 'img') {
            state.selectedNode.setAttribute('src', value || '');
        } else if (key === 'href' && state.selectedNode.tagName.toLowerCase() === 'a') {
            state.selectedNode.setAttribute('href', value || 'javascript:void(0)');
        } else if (key === 'style') {
            state.selectedNode.setAttribute('style', value || '');
        }
        syncExtraSectionHtml(state.selectedNode);
        postShellMessage('node_updated', nodeMeta(state.selectedNode));
        return true;
    }

    function updateSelectedNodeLive(key, value) {
        if (!applySelectedNodeValue(key, value)) {
            return;
        }
        renderFloatingOverlays();
        queueSaveDraft();
    }

    function finalizeSelectedNodeEdit() {
        endLiveEdit();
        if (!state.selectedPath) {
            return;
        }
        if (isProtectedEditableNode(state.selectedNode)) {
            setStatus(blockProtectedEditMessage());
            return;
        }
        commitMutation(state.selectedPath);
    }

    function updateSelectedNode(key, value) {
        if (isProtectedEditableNode(state.selectedNode)) {
            setStatus(blockProtectedEditMessage());
            return;
        }
        pushHistory();
        if (!applySelectedNodeValue(key, value)) {
            return;
        }
        commitMutation(state.selectedPath);
    }

    function updateSelectedStylePropertyLive(propertyName, value) {
        var nextStyle = buildStyleTextWithProperty(overrideValue('style', state.selectedNode ? (state.selectedNode.getAttribute('style') || '') : ''), propertyName, value);
        syncSelectedStylePropInputs(propertyName, value);
        syncSelectedStyleTextarea(nextStyle);
        updateSelectedNodeLive('style', nextStyle);
    }

    function updateSelectedStyleProperty(propertyName, value) {
        var nextStyle = buildStyleTextWithProperty(overrideValue('style', state.selectedNode ? (state.selectedNode.getAttribute('style') || '') : ''), propertyName, value);
        syncSelectedStylePropInputs(propertyName, value);
        syncSelectedStyleTextarea(nextStyle);
        updateSelectedNode('style', nextStyle);
    }

    function syncBusinessEditorField(key, value) {
        if (!state.businessEditor) {
            return;
        }
        state.businessEditor[key] = value;
    }

    function closeBusinessEditor() {
        state.businessEditor = null;
        state.businessModalPosition = null;
        renderFloatingOverlays();
    }

    function openBusinessEditor(node) {
        var business = businessItemMeta(node);
        if (!business) {
            return Promise.resolve(false);
        }
        setStatus('正在加载后台' + businessTypeLabel(business.type) + '数据...');
        return fetchBusinessItem(business.type, business.id).then(function (remoteData) {
            state.businessEditor = {
                type: business.type,
                id: business.id,
                extraId: business.extraId || '',
                title: remoteData && remoteData.title != null ? remoteData.title : (business.title || ''),
                summary: remoteData && remoteData.summary != null ? remoteData.summary : (business.summary || ''),
                price: remoteData && remoteData.price != null ? remoteData.price : (business.price || ''),
                litpic: remoteData && remoteData.litpic != null ? remoteData.litpic : (business.litpic || '')
            };
            state.businessModalPosition = null;
            renderFloatingOverlays();
            setStatus('已打开后台' + businessTypeLabel(business.type) + '数据编辑卡片。');
            return true;
        });
    }

    function uploadBusinessEditorImage() {
        if (!state.businessEditor || state.businessEditor.type !== 'product') {
            return;
        }
        var picker = document.createElement('input');
        picker.type = 'file';
        picker.accept = 'image/*';
        picker.addEventListener('change', function () {
            var file = picker.files && picker.files[0];
            if (!file) {
                return;
            }
            var formData = new FormData();
            formData.append('file', file);
            formData.append('upload_type', 'image');
            formData.append('csrf_token', boot.csrf_token);
            fetch(builderUploadUrl(), {
                method: 'POST',
                body: formData
            }).then(function (response) {
                return response.json();
            }).then(function (result) {
                if (result && result.code === 200 && result.data && result.data.url) {
                    state.businessEditor.litpic = result.data.url;
                    renderFloatingOverlays();
                } else {
                    alert(result && result.msg ? result.msg : '图片上传失败');
                }
            }).catch(function () {
                alert('图片上传失败');
            });
        });
        picker.click();
    }

    function saveBusinessEditor() {
        if (!state.businessEditor) {
            return;
        }
        var editor = state.businessEditor;
        if (!String(editor.title || '').trim()) {
            alert('标题不能为空');
            return;
        }
        var fields = {
            title: String(editor.title || '').trim(),
            summary: String(editor.summary || '').trim()
        };
        if (editor.type === 'product') {
            fields.price = String(editor.price || '').trim();
            fields.litpic = String(editor.litpic || '').trim();
        }
        setStatus('正在同步后台数据...');
        postApi('update_business_item', {
            item_type: editor.type,
            item_id: editor.id,
            fields: fields
        }).then(function (result) {
            if (!result || result.code !== 200) {
                setStatus(result && result.msg ? result.msg : '后台数据保存失败');
                alert(result && result.msg ? result.msg : '后台数据保存失败');
                return;
            }
            var currentEditor = state.businessEditor;
            closeBusinessEditor();
            if (currentEditor && currentEditor.extraId) {
                rebuildBusinessExtra(currentEditor.extraId);
                setStatus('后台' + businessTypeLabel(editor.type) + '数据已保存并刷新。');
                return;
            }
            if ((editor.type === 'article' && boot.builder_type === 'article_detail' && parseInt(boot.builder_ref_id, 10) === parseInt(editor.id, 10)) ||
                (editor.type === 'product' && boot.builder_type === 'product_detail' && parseInt(boot.builder_ref_id, 10) === parseInt(editor.id, 10))) {
                setStatus('后台' + businessTypeLabel(editor.type) + '数据已保存，正在刷新当前页面...');
                window.location.reload();
                return;
            }
            setStatus('后台' + businessTypeLabel(editor.type) + '数据已保存。若当前模块不是装修业务模块，刷新页面后即可看到最新内容。');
        }).catch(function () {
            setStatus('后台数据保存失败');
            alert('后台数据保存失败');
        });
    }

    function triggerPrimaryEditAction(actionNode) {
        if (!actionNode) {
            return;
        }
        if (isProtectedEditableNode(actionNode)) {
            selectNode(actionNode, false, false);
            hideSettingsPanel();
            setStatus(blockProtectedEditMessage());
            return;
        }
        var primaryAction = blockPrimaryActionMeta(actionNode);
        if (primaryAction.kind === 'business') {
            selectNode(actionNode, false, false);
            openBusinessEditor(actionNode).then(function (opened) {
                if (!opened) {
                    setStatus('当前业务数据暂不支持直接编辑。');
                }
            });
            return;
        }
        if (primaryAction.kind === 'section') {
            state.libraryModuleTab = 'section_module';
            setLibraryMode('modules', true);
            selectNode(actionNode, false, true);
            setStatus('已打开当前区块属性，左侧也已切换到栏目模块，可继续管理显隐、排序和样式。');
            return;
        }
        selectNode(actionNode, false, true);
        if (primaryAction.kind === 'image') {
            setStatus('已进入图片编辑，可修改图片地址、上传图片或调整样式。');
        } else {
            setStatus('已进入文本编辑，可修改内容、HTML 和样式。');
        }
    }

    function mergeInlineStyle(styleText, updates) {
        var probe = document.createElement('div');
        probe.setAttribute('style', styleText || '');
        Object.keys(updates || {}).forEach(function (property) {
            var value = updates[property];
            if (value == null || value === '') {
                probe.style.removeProperty(property);
            } else {
                probe.style.setProperty(property, value);
            }
        });
        return probe.getAttribute('style') || '';
    }

    function setStyleOverrideByPath(path, styleText) {
        var node = queryByPath(path);
        var ref = node ? nodeRef(node) : {path: path, key: ''};
        var overrideItem = ensureOverrideEntry(ref);
        if (!overrideItem) {
            return;
        }
        overrideItem.style = styleText;
        if (styleText && styleText.indexOf('--cms-builder-free-transform') !== -1) {
            overrideItem.free_transform = 1;
        } else {
            delete overrideItem.free_transform;
        }
    }

    function parseScaleFromStyle(styleText) {
        var match = String(styleText || '').match(/--cms-builder-free-transform\s*:\s*[^;]*scale\(([^)]+)\)/i)
            || String(styleText || '').match(/scale\(([^)]+)\)/i);
        if (!match) {
            return 1;
        }
        var value = parseFloat(match[1]);
        return isNaN(value) || value <= 0 ? 1 : value;
    }

    function parseTranslateFromStyle(styleText) {
        var match = String(styleText || '').match(/--cms-builder-free-transform\s*:\s*[^;]*translate3d\(\s*([-0-9.]+)px\s*,\s*([-0-9.]+)px\s*,\s*0(?:px)?\s*\)/i)
            || String(styleText || '').match(/translate3d\(\s*([-0-9.]+)px\s*,\s*([-0-9.]+)px\s*,\s*0(?:px)?\s*\)/i)
            || String(styleText || '').match(/translate\(\s*([-0-9.]+)px\s*,\s*([-0-9.]+)px\s*\)/i);
        if (!match) {
            return {x: 0, y: 0};
        }
        return {
            x: parseFloat(match[1]) || 0,
            y: parseFloat(match[2]) || 0
        };
    }

    function buildTransformValue(x, y, scale) {
        var parts = [];
        if (Math.round(x) !== 0 || Math.round(y) !== 0) {
            parts.push('translate3d(' + Math.round(x) + 'px, ' + Math.round(y) + 'px, 0)');
        }
        if (Math.abs(scale - 1) > 0.01) {
            parts.push('scale(' + scale.toFixed(2) + ')');
        }
        return parts.join(' ');
    }

    function parsePixelStyleValue(styleText, propertyName) {
        var probe = document.createElement('div');
        probe.setAttribute('style', styleText || '');
        var raw = probe.style.getPropertyValue(propertyName) || '';
        if (!raw) {
            return 0;
        }
        var value = parseFloat(raw);
        return isNaN(value) ? 0 : value;
    }

    function beginBlockTransform(path, mode, event, options) {
        options = options && typeof options === 'object' ? options : {};
        if (isFreeLayoutMode()) {
            setStatus('当前页面存在历史自由画布数据，先保存一次后会自动恢复为模板区块模式。');
            return;
        }
        var node = queryByPath(path);
        if (!node) {
            return;
        }
        if (isProtectedEditableNode(node)) {
            setStatus(blockProtectedEditMessage());
            return;
        }
        if (listHasRef((state.page.frontend_locked_paths || []), nodeRef(node))) {
            setStatus('当前区块已锁定，解除锁定后才能移动或调整。');
            return;
        }
        event.preventDefault();
        event.stopPropagation();
        pushHistory();
        selectNode(node, false, false);
        var rect = node.getBoundingClientRect();
        var initialStyle = node.getAttribute('style') || '';
        var startTranslate = parseTranslateFromStyle(initialStyle);
        state.resizingMeta = {
            path: path,
            direction: mode,
            startX: typeof options.startClientX === 'number' ? options.startClientX : event.clientX,
            startY: typeof options.startClientY === 'number' ? options.startClientY : event.clientY,
            startWidth: rect.width,
            startHeight: rect.height,
            initialStyle: initialStyle,
            startScale: parseScaleFromStyle(initialStyle),
            startTranslateX: startTranslate.x,
            startTranslateY: startTranslate.y,
            startLeft: parsePixelStyleValue(initialStyle, 'left'),
            startTop: parsePixelStyleValue(initialStyle, 'top')
        };
        document.body.classList.add('cms-builder-is-resizing');
        setStatus(mode === 'move' ? '正在移动模块...' : '正在调整模块...');
        function applyMove(moveEvent) {
            if (!state.resizingMeta) {
                return;
            }
            var nextWidth = state.resizingMeta.startWidth;
            var nextHeight = state.resizingMeta.startHeight;
            var nextScale = state.resizingMeta.startScale;
            var nextX = state.resizingMeta.startTranslateX;
            var nextY = state.resizingMeta.startTranslateY;
            var nextLeft = state.resizingMeta.startLeft;
            var nextTop = state.resizingMeta.startTop;
            if (mode === 'move') {
                nextLeft = state.resizingMeta.startLeft + (moveEvent.clientX - state.resizingMeta.startX);
                nextTop = state.resizingMeta.startTop + (moveEvent.clientY - state.resizingMeta.startY);
                if (moveEvent.shiftKey) {
                    nextLeft = Math.round(nextLeft / 10) * 10;
                    nextTop = Math.round(nextTop / 10) * 10;
                }
            }
            if (mode === 'e' || mode === 'se') {
                nextWidth = Math.max(60, state.resizingMeta.startWidth + (moveEvent.clientX - state.resizingMeta.startX));
            }
            if (mode === 's' || mode === 'se') {
                nextHeight = Math.max(32, state.resizingMeta.startHeight + (moveEvent.clientY - state.resizingMeta.startY));
            }
            if (mode === 'scale') {
                nextScale = Math.max(0.2, Math.min(3, state.resizingMeta.startScale + ((moveEvent.clientX - state.resizingMeta.startX) / Math.max(state.resizingMeta.startWidth, 120))));
            }
            var stylePatch = {};
            if (mode === 'move') {
                stylePatch.position = 'relative';
                stylePatch.left = Math.round(nextLeft) + 'px';
                stylePatch.top = Math.round(nextTop) + 'px';
                stylePatch['z-index'] = '3';
                stylePatch['--cms-builder-free-transform'] = '';
                stylePatch['--cms-builder-transform-origin'] = '';
            } else if (mode === 'scale') {
                stylePatch['--cms-builder-free-transform'] = buildTransformValue(nextX, nextY, nextScale) || 'none';
                stylePatch['--cms-builder-transform-origin'] = 'top left';
            } else {
                stylePatch.width = Math.round(nextWidth) + 'px';
                stylePatch.height = Math.round(nextHeight) + 'px';
            }
            var styleText = mergeInlineStyle(state.resizingMeta.initialStyle, stylePatch);
            setStyleOverrideByPath(path, styleText);
            node.setAttribute('style', styleText);
            if (mode === 'scale') {
                node.classList.add('cms-builder-free-transform');
            } else if (mode === 'move') {
                node.classList.remove('cms-builder-free-transform');
            }
            renderFloatingOverlays();
        }
        function onMove(moveEvent) {
            applyMove(moveEvent);
        }
        function onUp() {
            document.body.classList.remove('cms-builder-is-resizing');
            document.removeEventListener('mousemove', onMove, true);
            document.removeEventListener('mouseup', onUp, true);
            state.resizingMeta = null;
            state.suppressClickUntil = Date.now() + 180;
            commitMutation(path);
            setStatus(mode === 'move' ? '模块位置已更新' : '模块尺寸已更新');
        }
        document.addEventListener('mousemove', onMove, true);
        document.addEventListener('mouseup', onUp, true);
        if (options.immediateEvent) {
            applyMove(options.immediateEvent);
        }
    }

    function resetBlockTransform(path) {
        var node = queryByPath(path);
        if (!node) {
            return;
        }
        if (listHasRef((state.page.frontend_locked_paths || []), nodeRef(node))) {
            setStatus('当前区块已锁定，解除锁定后才能重置。');
            return;
        }
        pushHistory();
        var styleText = mergeInlineStyle(node.getAttribute('style') || '', {
            '--cms-builder-free-transform': '',
            '--cms-builder-transform-origin': '',
            position: '',
            left: '',
            top: '',
            'z-index': ''
        });
        setStyleOverrideByPath(path, styleText);
        node.setAttribute('style', styleText);
        node.classList.remove('cms-builder-free-transform');
        commitMutation(path);
        setStatus('模块位置与缩放已重置');
    }

    function updatePageField(key, value) {
        pushHistory();
        state.page[key] = value;
        if (key === 'device_mode' || key === 'mobile_page_width') {
            syncDeviceButtons();
            notifyShellDeviceState();
        }
        commitMutation(state.selectedPath);
    }

    function updatePageFieldLive(key, value) {
        state.page[key] = value;
        applyPageVisualSettings();
        applyPageHeadSettings();
        ensurePageWidgetsLayer();
        renderFloatingOverlays();
        if (key === 'device_mode' || key === 'mobile_page_width') {
            syncDeviceButtons();
            notifyShellDeviceState();
        }
        queueSaveDraft();
    }

    function finalizePageFieldEdit() {
        endLiveEdit();
        commitMutation(state.selectedPath);
    }

    function clearSelectedOverride() {
        if (!state.selectedPath) {
            return;
        }
        var selectedRef = state.selectedNode ? nodeRef(state.selectedNode) : {path: state.selectedPath, key: ''};
        var overrides = currentOverrideBucket();
        var bucketKey = refKey(selectedRef) || state.selectedPath;
        if (!bucketKey || !overrides[bucketKey]) {
            return;
        }
        pushHistory();
        delete overrides[bucketKey];
        if (selectedRef.path && overrides[selectedRef.path]) {
            delete overrides[selectedRef.path];
        }
        commitMutation(state.selectedPath);
    }

    function toggleSelectedHidden() {
        var block = resolveActionBlockNode(currentBlockNode());
        if (!block) {
            return;
        }
        if (isProtectedBlockNode(block)) {
            setStatus(protectedBlockMessage());
            return;
        }
        pushHistory();
        var hiddenPaths = currentHiddenPaths().slice();
        hiddenPaths = toggleRefValue(hiddenPaths, nodeRef(block));
        setCurrentHiddenPaths(hiddenPaths);
        commitMutation(getDomPath(block));
    }

    function toggleBlockHiddenByPath(path) {
        var block = resolveActionBlockNode(queryByPath(path));
        if (!block) {
            return;
        }
        if (isProtectedBlockNode(block)) {
            setStatus(protectedBlockMessage());
            return;
        }
        pushHistory();
        var hiddenPaths = currentHiddenPaths().slice();
        hiddenPaths = toggleRefValue(hiddenPaths, nodeRef(block));
        setCurrentHiddenPaths(hiddenPaths);
        commitMutation(getDomPath(block));
    }

    function toggleSelectedLocked() {
        var block = currentBlockNode();
        if (!block) {
            return;
        }
        pushHistory();
        state.page.frontend_locked_paths = toggleRefValue((state.page.frontend_locked_paths || []).slice(), nodeRef(block));
        commitMutation(getDomPath(block));
    }

    function toggleBlockLockedByPath(path) {
        var block = queryByPath(path);
        if (!block) {
            return;
        }
        pushHistory();
        state.page.frontend_locked_paths = toggleRefValue((state.page.frontend_locked_paths || []).slice(), nodeRef(block));
        commitMutation(getDomPath(block));
    }

    function moveSelectedNode(direction) {
        if (isFreeLayoutMode()) {
            setStatus('当前已进入自由布局主模式，模板区块排序已停用，请改为操作自由画布模块。');
            return;
        }
        var block = currentBlockNode();
        if (!block || !block.parentNode) {
            return;
        }
        var sibling = direction < 0 ? block.previousElementSibling : block.nextElementSibling;
        if (!sibling) {
            return;
        }
        pushHistory();
        if (direction < 0) {
            block.parentNode.insertBefore(block, sibling);
        } else {
            block.parentNode.insertBefore(sibling, block);
        }
        updateParentOrder(block.parentNode, getDomPath(block));
        commitMutation(getDomPath(block));
    }

    function duplicateSelectedBlock() {
        var block = currentBlockNode();
        if (!block) {
            return;
        }
        pushHistory();
        if (block.hasAttribute('data-builder-extra-id')) {
            var extraId = block.getAttribute('data-builder-extra-id');
            var source = null;
            state.page.frontend_extra_sections.forEach(function (item) {
                if (String(item.id) === String(extraId)) {
                    source = item;
                }
            });
            if (!source) {
                return;
            }
            state.page.frontend_extra_sections.push({
                id: 'extra_' + Math.random().toString(16).slice(2, 10),
                type: source.type,
                html: source.html,
                config: source.config ? JSON.parse(JSON.stringify(source.config)) : {},
                anchor_path: source.anchor_path || '',
                insert_mode: source.insert_mode || 'after'
            });
            commitMutation('');
            return;
        }
        state.page.frontend_extra_sections.push({
            id: 'extra_' + Math.random().toString(16).slice(2, 10),
            type: 'html',
            html: block.outerHTML
        });
        commitMutation('');
    }

    function deleteSelectedBlock() {
        var block = resolveActionBlockNode(currentBlockNode());
        if (!block) {
            return;
        }
        if (isProtectedBlockNode(block)) {
            setStatus(protectedBlockMessage());
            return;
        }
        pushHistory();
        if (block.hasAttribute('data-builder-extra-id')) {
            var extraId = block.getAttribute('data-builder-extra-id');
            state.page.frontend_extra_sections = state.page.frontend_extra_sections.filter(function (item) {
                return String(item.id) !== String(extraId);
            });
            state.selectedNode = null;
            state.selectedPath = '';
            commitMutation('');
            setStatus('已删除当前模块。');
            return;
        }
        var blockPath = getDomPath(block);
        var blockRef = nodeRef(block);
        var hidden = currentHiddenPaths().slice().filter(function (item) {
            return !refsEqual(normalizeStoredRef(item), blockRef);
        });
        var deleted = currentDeletedPaths().slice();
        if (!listHasRef(deleted, blockRef)) {
            deleted.push(blockRef);
        }
        setCurrentHiddenPaths(hidden);
        setCurrentDeletedPaths(deleted);
        state.selectedNode = null;
        state.selectedPath = '';
        commitMutation('');
        setStatus('已删除当前模块。');
    }

    function deleteBlockByPath(path) {
        var block = resolveActionBlockNode(queryByPath(path));
        if (!block) {
            return;
        }
        if (isProtectedBlockNode(block)) {
            setStatus(protectedBlockMessage());
            return;
        }
        pushHistory();
        if (block.hasAttribute('data-builder-extra-id')) {
            var extraId = block.getAttribute('data-builder-extra-id');
            state.page.frontend_extra_sections = state.page.frontend_extra_sections.filter(function (item) {
                return String(item.id) !== String(extraId);
            });
            if (state.selectedPath === path) {
                state.selectedNode = null;
                state.selectedPath = '';
            }
            commitMutation('');
            setStatus('已删除所选模块。');
            return;
        }
        var blockPath = getDomPath(block);
        var blockRef = nodeRef(block);
        var hidden = currentHiddenPaths().slice().filter(function (item) {
            return !refsEqual(normalizeStoredRef(item), blockRef);
        });
        var deleted = currentDeletedPaths().slice();
        if (!listHasRef(deleted, blockRef)) {
            deleted.push(blockRef);
        }
        setCurrentHiddenPaths(hidden);
        setCurrentDeletedPaths(deleted);
        if (state.selectedPath === path) {
            state.selectedNode = null;
            state.selectedPath = '';
        }
        commitMutation('');
        setStatus('已删除所选模块。');
    }

    function reorderByDraggedPath(sourcePath, targetPath, insertMode) {
        if (isFreeLayoutMode()) {
            setStatus('当前已进入自由布局主模式，模板区块排序已停用。请直接拖拽画布模块。');
            return;
        }
        if (!sourcePath || !targetPath || sourcePath === targetPath) {
            return;
        }
        insertMode = insertMode === 'before' ? 'before' : 'after';
        var source = queryByPath(sourcePath);
        var target = queryByPath(targetPath);
        if (!source || !target) {
            return;
        }
        var targetKey = nodeStableKey(target);
        var sourceKey = nodeStableKey(source);
        if (source.hasAttribute('data-builder-extra-id')) {
            pushHistory();
            var sourceExtraId = source.getAttribute('data-builder-extra-id');
            var targetExtra = target.closest('[data-builder-extra-id]');
            if (targetExtra) {
                var targetExtraId = targetExtra.getAttribute('data-builder-extra-id');
                var extras = (state.page.frontend_extra_sections || []).slice();
                var sourceItem = null;
                var targetIndex = -1;
                extras = extras.filter(function (item, index) {
                    if (String(item.id) === String(sourceExtraId)) {
                        sourceItem = item;
                        return false;
                    }
                    if (String(item.id) === String(targetExtraId)) {
                        targetIndex = index;
                    }
                    return true;
                });
                if (sourceItem) {
                    var targetItem = null;
                    extras.forEach(function (item) {
                        if (String(item.id) === String(targetExtraId)) {
                            targetItem = item;
                        }
                    });
                    if (targetItem) {
                        sourceItem.anchor_path = targetItem.anchor_path || '';
                        sourceItem.anchor_key = targetItem.anchor_key || '';
                        sourceItem.insert_mode = insertMode;
                    }
                    if (targetIndex < 0 || targetIndex >= extras.length) {
                        extras.push(sourceItem);
                    } else {
                        extras.splice(insertMode === 'before' ? targetIndex : targetIndex + 1, 0, sourceItem);
                    }
                    state.page.frontend_extra_sections = extras;
                }
            } else {
                setExtraPlacement(sourceExtraId, targetPath, insertMode, targetKey);
            }
            commitMutation('');
            return;
        }
        if (target.hasAttribute('data-builder-extra-id')) {
            pushHistory();
            var targetExtraId = target.getAttribute('data-builder-extra-id');
            state.page.frontend_extra_sections = (state.page.frontend_extra_sections || []).map(function (item) {
                if (String(item.id) === String(targetExtraId)) {
                    item.anchor_path = sourcePath;
                    item.anchor_key = sourceKey;
                    item.insert_mode = insertMode;
                }
                return item;
            });
            commitMutation(sourcePath);
            setStatus('模块位置已更新。');
            return;
        }
        if (!source.parentNode || source.parentNode !== target.parentNode) {
            setStatus('普通模板区块仅支持同级排序；需要跨区域布局时请添加独立模块。');
            return;
        }
        pushHistory();
        target.parentNode.insertBefore(source, insertMode === 'before' ? target : target.nextSibling);
        updateParentOrder(target.parentNode, getDomPath(source));
        commitMutation(getDomPath(source));
        setStatus('模块位置已更新。');
    }

    function clearCanvasDropIndicators() {
        qsa('.cms-builder-canvas-drop').forEach(function (node) {
            node.classList.remove('cms-builder-canvas-drop');
        });
    }

    function canDropDraggedPath(sourcePath,targetNode) {
        if(!sourcePath||!targetNode)return false;var source=queryByPath(sourcePath),target=targetNode.hasAttribute('data-drag-path')?queryByPath(targetNode.getAttribute('data-drag-path')||''):targetNode;
        if(!source||!target||source===target)return false;if(source.hasAttribute('data-builder-extra-id')||target.hasAttribute('data-builder-extra-id'))return true;return !!(source.parentNode&&source.parentNode===target.parentNode);
    }

    function interactiveDragIgnoreTarget(target) {
        return !!(target && target.closest && target.closest('#cms_frontend_builder_root button,#cms_frontend_builder_root input,#cms_frontend_builder_root textarea,#cms_frontend_builder_root select,#cms_frontend_builder_root a,[data-hover-toolbar],[data-hover-toolbar] *,[data-transform-handle],[data-resize-handle],[data-panel-role],[data-modal-handle],[data-canvas-action],[data-canvas-resize]'));
    }

    function detectDropInsertMode(node, clientX, clientY) {
        if (!node || !(node instanceof Element)) {
            return 'after';
        }
        var rect = node.getBoundingClientRect();
        if (!rect) {
            return 'after';
        }
        var horizontal = (isInlineEditableNode(node) || isSimpleTextNode(node)) && rect.width > rect.height * 1.35;
        if (horizontal) {
            return clientX < rect.left + rect.width / 2 ? 'before' : 'after';
        }
        return clientY < rect.top + rect.height / 2 ? 'before' : 'after';
    }

    function findDropTargetAtPoint(clientX, clientY, sourcePath) {
        var elements = typeof document.elementsFromPoint === 'function' ? document.elementsFromPoint(clientX, clientY) : [];
        for (var i = 0; i < elements.length; i++) {
            var element = elements[i];
            if (!element || !(element instanceof Element) || element.closest('#cms_frontend_builder_root')) {
                continue;
            }
            var button = element.closest('[data-drag-path]');
            if (button) {
                var buttonPath = button.getAttribute('data-drag-path') || '';
                if (buttonPath && buttonPath !== sourcePath) {
                    return {type: 'button', node: button, path: buttonPath, insertMode: detectDropInsertMode(button, clientX, clientY)};
                }
            }
            var block = element.closest('[data-builder-block-path]');
            if (block) {
                var blockPath = getDomPath(block);
                if (blockPath && blockPath !== sourcePath) {
                    return {type: 'block', node: block, path: blockPath, insertMode: detectDropInsertMode(block, clientX, clientY)};
                }
            }
        }
        return null;
    }

    function selectedExtraImageNode() {
        if (!state.selectedNode) {
            return null;
        }
        var wrapper = state.selectedNode.closest('[data-builder-extra-id]');
        if (!wrapper) {
            return null;
        }
        return qs('img', wrapper);
    }

    function selectedImageUploadNode() {
        if (!state.selectedNode) {
            return null;
        }
        if (state.selectedNode.tagName && state.selectedNode.tagName.toLowerCase() === 'img') {
            return state.selectedNode;
        }
        var extraImage = selectedExtraImageNode();
        if (extraImage) {
            return extraImage;
        }
        return qs('img', state.selectedNode);
    }

    function selectedImageInsertTargetNode() {
        if (!state.selectedNode || isProtectedEditableNode(state.selectedNode)) {
            return null;
        }
        var actionNode = resolveActionBlockNode(state.selectedNode) || state.selectedNode;
        if (!actionNode || !(actionNode instanceof Element)) {
            return null;
        }
        if (actionNode.hasAttribute('data-builder-extra-id')) {
            return actionNode;
        }
        var tag = (actionNode.tagName || '').toLowerCase();
        if (/^(h1|h2|h3|h4|h5|h6|p|span|strong|em|b|a|li)$/i.test(tag) && actionNode.parentElement && !isProtectedEditableNode(actionNode.parentElement)) {
            return actionNode.parentElement;
        }
        return actionNode;
    }

    function canInsertImageIntoSelectedNode() {
        var targetNode = selectedImageInsertTargetNode();
        if (!targetNode) {
            return false;
        }
        if (targetNode.tagName && targetNode.tagName.toLowerCase() === 'img') {
            return false;
        }
        var tag = (targetNode.tagName || '').toLowerCase();
        return /^(div|section|article|p|span|a|li|h1|h2|h3|h4|h5|h6|button)$/i.test(tag) || targetNode.hasAttribute('data-builder-extra-id');
    }

    function updateSpecificNode(node, key, value, keepSelectedPath) {
        if (!node || !(node instanceof Element)) {
            return false;
        }
        var previousNode = state.selectedNode;
        var previousPath = state.selectedPath;
        state.selectedNode = node;
        state.selectedPath = getDomPath(node);
        pushHistory();
        if (!applySelectedNodeValue(key, value)) {
            state.selectedNode = previousNode;
            state.selectedPath = previousPath;
            return false;
        }
        commitMutation(keepSelectedPath || state.selectedPath);
        return true;
    }

    function buildInlineInsertedHtml(type) {
        if (type === 'text') {
            return '<p style="margin:12px 0;line-height:1.9;color:#475569;">这里是新插入的文本内容，点击后可继续直接修改。</p>';
        }
        if (type === 'button') {
            return '<a href="javascript:void(0)" style="display:inline-flex;align-items:center;justify-content:center;margin:12px 0;padding:12px 22px;border-radius:999px;background:#111827;color:#ffffff;text-decoration:none;">新的按钮</a>';
        }
        return '';
    }

    function insertHtmlIntoSelectedNode(html, successText) {
        var targetNode = selectedImageInsertTargetNode();
        if (!targetNode || !html) {
            return false;
        }
        var currentHtml = targetNode.innerHTML || '';
        if (updateSpecificNode(targetNode, 'html', currentHtml + html, state.selectedPath || getDomPath(targetNode))) {
            setStatus(successText || '内容已插入当前模块。');
            return true;
        }
        return false;
    }

    function insertPresetIntoSelectedNode(type) {
        var html = buildInlineInsertedHtml(type);
        if (!html) {
            return;
        }
        insertHtmlIntoSelectedNode(html, type === 'button' ? '按钮已插入当前模块。' : '文本已插入当前模块。');
    }

    function getSiteWidgetConfigs() {
        state.page.site_widget_configs = normalizeSiteWidgetConfigs(state.page.site_widget_configs);
        return state.page.site_widget_configs;
    }

    function widgetConfigFor(key) {
        var configs = getSiteWidgetConfigs();
        return configs[key] || {};
    }

    function widgetConfigText(key, field, fallback) {
        var value = widgetConfigFor(key)[field];
        value = typeof value === 'undefined' || value === null ? '' : String(value).trim();
        return value !== '' ? value : String(fallback || '');
    }

    function widgetConfigBool(key, field) {
        return widgetConfigFor(key)[field] ? 1 : 0;
    }

    function updateSiteWidgetConfig(widgetKey, fieldKey, value) {
        var configs = normalizeSiteWidgetConfigs(state.page.site_widget_configs);
        if (!configs[widgetKey]) {
            return;
        }
        configs[widgetKey][fieldKey] = value;
        applyPageStylePatch({site_widget_configs: configs}, '', '页面挂件配置已更新');
    }

    async function syncWidgetToAllPages(widgetKey) {
        widgetKey = widgetKey || '';
        if (!widgetKey) {
            return;
        }
        if (!await builderConfirm({title:'同步全站挂件',message:'将当前挂件的开启状态和配置同步到所有页面。此操作会覆盖其它页面同名挂件设置。',confirmText:'确认同步'})) {
            return;
        }
        var saved=await saveDraftNow();if(!(saved&&saved.code===200)){if(!(saved&&saved.code===409))setStatus('当前草稿保存失败，已停止全站同步');return;}
        var widgets = normalizeSiteWidgets(state.page.site_widgets);
        var configs = normalizeSiteWidgetConfigs(state.page.site_widget_configs);
        setStatus('正在同步到所有页面...');
        postApi('sync_widget_all_pages', {
            widget_key: widgetKey,
            widget_enabled: widgets[widgetKey] ? 1 : 0,
            widget_config: configs[widgetKey] || {}
        }).then(function (response) {
            var updated = response && response.updated ? response.updated : 0;
            var versions=response&&response.page_versions?response.page_versions:{},currentVersion=parseInt(versions[String(boot.builder_page_id||0)]||0,10);if(currentVersion){state.serverVersion=currentVersion;announceSavedVersion();}
            setStatus(updated > 0 ? ('已同步到所有页面（' + updated + '个页面）。') : '已同步到所有页面。');
        }).catch(function () {
            setStatus('同步到所有页面失败。');
        });
    }

    async function syncSelectedContactToGlobal(fieldKey, fieldValue, fieldLabel) {
        if (!fieldKey) {
            return;
        }
        if (!await builderConfirm({title:'同步全站公共信息',message:'将当前' + (fieldLabel || '公共信息') + '同步为全站统一内容。此操作会覆盖所有页面和系统公共联系信息。',confirmText:'确认同步'})) {
            return;
        }
        setStatus('正在同步全站公共信息...');
        postApi('sync_site_contact', {
            field_key: fieldKey,
            field_value: fieldValue || ''
        }).then(function () {
            state.businessOptions.loaded = false;
            ensureBusinessOptions().then(function () {
                applyPageState();
                setStatus('全站公共信息已同步。');
            });
        }).catch(function () {
            setStatus('全站公共信息同步失败。');
        });
    }

    function applyMouseDragTarget(targetMeta) {
        clearCanvasDropIndicators();
        if (!targetMeta || !targetMeta.node) {
            return;
        }
        targetMeta.node.classList.add('cms-builder-canvas-drop');
    }

    function startMouseDrag(meta) {
        state.mouseDragMeta = meta;
        if (meta.kind === 'new_extra') {
            state.draggingNewExtraType = meta.extraType || '';
        } else {
            state.draggingPath = meta.sourcePath || '';
        }
        if (meta.sourceNode) {
            meta.sourceNode.classList.add('is-dragging');
        }
        renderFloatingOverlays();
    }

    function finishMouseDrag() {
        if (!state.mouseDragMeta) {
            return;
        }
        var meta = state.mouseDragMeta;
        if (meta.active) {
            if (meta.kind === 'new_extra') {
                var dropMeta = meta.currentTarget && meta.currentTarget.path
                    ? meta.currentTarget
                    : defaultExtraAnchorMeta();
                var dropPath = dropMeta.path || '';
                var dropKey = dropMeta.key || '';
                var dropInsertMode = meta.currentTarget && meta.currentTarget.insertMode ? meta.currentTarget.insertMode : 'after';
                addExtraSection(meta.extraType, dropPath, dropInsertMode, null, dropKey);
                applyPageState();
                setStatus('新模块已插入页面。');
            } else if (meta.currentTarget && meta.currentTarget.path && meta.sourcePath && meta.currentTarget.path !== meta.sourcePath) {
                reorderByDraggedPath(meta.sourcePath, meta.currentTarget.path, meta.currentTarget.insertMode || 'after');
            }
        }
        if (meta.sourceNode) {
            meta.sourceNode.classList.remove('is-dragging');
        }
        state.suppressClickUntil = Date.now() + 280;
        state.mouseDragMeta = null;
        state.draggingPath = '';
        state.draggingNewExtraType = '';
        clearCanvasDropIndicators();
        renderFloatingOverlays();
    }

    function cancelMouseDrag() {
        if (!state.mouseDragMeta) {
            return;
        }
        var meta = state.mouseDragMeta;
        if (meta.sourceNode) {
            meta.sourceNode.classList.remove('is-dragging');
        }
        state.mouseDragMeta = null;
        state.draggingPath = '';
        state.draggingNewExtraType = '';
        clearCanvasDropIndicators();
        renderFloatingOverlays();
    }

    function undoChange() {
        if (!state.history.length) {
            setStatus('没有可撤销的操作。');
            return;
        }
        state.future.push(clonePageState());
        state.suspendHistory = true;
        restorePageState(state.history.pop());
        state.suspendHistory = false;
        applyPageState();
        refreshSelectionByPath(state.selectedPath);
        syncHistoryButtons();
        queueSaveDraft();
        setStatus('已撤销上一步操作。');
    }

    function redoChange() {
        if (!state.future.length) {
            setStatus('没有可重做的操作。');
            return;
        }
        state.history.push(clonePageState());
        state.suspendHistory = true;
        restorePageState(state.future.pop());
        state.suspendHistory = false;
        applyPageState();
        refreshSelectionByPath(state.selectedPath);
        syncHistoryButtons();
        queueSaveDraft();
        setStatus('已恢复下一步操作。');
    }

    function preflightLevelLabel(level) {
        return level === 'error' ? '阻断' : (level === 'warning' ? '警告' : '建议');
    }

    function closePreflightPanel() {
        var modal = qs('#cms_builder_preflight_modal');
        if (modal) modal.classList.add('is-hidden');
    }

    function openPreflightPanel(report) {
        var modal = qs('#cms_builder_preflight_modal');
        var body = qs('#cms_builder_preflight_body');
        if (!modal || !body || !report) return;
        var counts = report.counts || {error: 0, warning: 0, info: 0};
        var issues = Array.isArray(report.issues) ? report.issues : [];
        var issueHtml = issues.map(function (issue) {
            var level = issue.severity || 'info';
            return '<article class="cms-builder-preflight-item is-' + escapeHtml(level) + '">' +
                '<span class="cms-builder-preflight-level">' + escapeHtml(preflightLevelLabel(level)) + '</span>' +
                '<div class="cms-builder-preflight-copy"><strong>' + escapeHtml(issue.title || '检查提示') + '</strong>' +
                '<p>' + escapeHtml(issue.message || '') + '</p>' +
                (issue.fix ? '<div class="cms-builder-preflight-fix">建议：' + escapeHtml(issue.fix) + '</div>' : '') +
                (issue.path ? '<div class="cms-builder-preflight-path">' + escapeHtml(issue.path) + '</div>' : '') +
                '</div></article>';
        }).join('');
        body.innerHTML = '<div class="cms-builder-preflight-summary">' +
            '<div class="cms-builder-preflight-score"><strong>' + escapeHtml(report.score == null ? '--' : report.score) + '</strong><span>页面健康分 · ' + escapeHtml(report.grade || '待检查') + '</span></div>' +
            '<div class="cms-builder-preflight-stat is-error"><strong>' + escapeHtml(counts.error || 0) + '</strong><span>阻断问题</span></div>' +
            '<div class="cms-builder-preflight-stat is-warning"><strong>' + escapeHtml(counts.warning || 0) + '</strong><span>优化警告</span></div>' +
            '<div class="cms-builder-preflight-stat is-info"><strong>' + escapeHtml(counts.info || 0) + '</strong><span>改进建议</span></div>' +
            '</div>' +
            '<div class="cms-builder-preflight-result' + (report.can_publish ? '' : ' has-errors') + '">' +
                (report.can_publish ? '体检通过，可以发布上线。警告项不会阻断发布。' : '存在阻断问题，发布已停止。请修复后重新体检。') +
            '</div>' +
            '<div class="cms-builder-preflight-list">' + (issueHtml || '<div class="cms-builder-empty">没有发现问题，当前页面状态良好。</div>') + '</div>';
        modal.classList.remove('is-hidden');
    }

    function runPreflight(showReport) {
        setStatus('正在执行发布体检...');
        return postApi('preflight', {title: builderTitle(), layout: buildLayout()}).then(function (result) {
            var report = result && result.data ? result.data : null;
            if (!report) throw new Error('invalid_preflight_response');
            if (showReport || !report.can_publish) openPreflightPanel(report);
            setStatus(report.can_publish
                ? ('发布体检通过 · ' + parseInt((report.counts || {}).warning || 0, 10) + ' 项警告')
                : ('发布体检未通过 · ' + parseInt((report.counts || {}).error || 0, 10) + ' 项阻断'));
            return report;
        }).catch(function () {
            setStatus('发布体检失败');
            alert('发布体检请求失败，请稍后重试。');
            return null;
        });
    }

    function closeHistoryPanel(){var modal=qs('#cms_builder_history_modal');if(modal)modal.classList.add('is-hidden');}

    function openHistoryPanel(){var modal=qs('#cms_builder_history_modal'),list=qs('#cms_builder_history_list');if(!modal||!list)return;modal.classList.remove('is-hidden');list.innerHTML='<div class="cms-builder-empty">正在加载历史版本...</div>';postApi('load',{}).then(function(result){var data=result&&result.data?result.data:{},history=Array.isArray(data.history)?data.history:[],pageId=parseInt(data.page&&data.page.id||0,10)||0;if(!history.length){list.innerHTML='<div class="cms-builder-empty">当前页面还没有可回滚的历史版本。发布或持续编辑后，系统会自动保留关键快照。</div>';return;}list.innerHTML=history.map(function(item){var time=parseInt(item.create_time||0,10),label=time?new Date(time*1000).toLocaleString():'时间未知';return '<div class="cms-builder-history-row"><div><strong>版本 '+escapeHtml(item.version_no||'-')+'</strong><span>'+escapeHtml(label)+'</span></div><button type="button" class="cms-builder-btn cms-builder-btn-secondary" data-history-restore="'+escapeHtml(item.id)+'" data-history-page="'+pageId+'">恢复此版本</button></div>';}).join('');}).catch(function(){list.innerHTML='<div class="cms-builder-empty">历史版本加载失败，请稍后重试。</div>';});}

    async function restoreHistoryVersion(button){var historyId=parseInt(button.getAttribute('data-history-restore')||0,10),pageId=parseInt(button.getAttribute('data-history-page')||0,10);if(!historyId||!pageId)return;if(!await builderConfirm({title:'恢复历史版本',message:'恢复历史版本会替换当前草稿；系统会先保存当前修改作为可回滚版本。',confirmText:'确认恢复'}))return;button.disabled=true;setStatus('正在保存当前草稿...');saveDraftNow().then(function(saved){if(!(saved&&saved.code===200))throw new Error('save_failed');setStatus('正在恢复历史版本...');return postApi('restore_history',{history_id:historyId,builder_page_id:pageId,expected_version:state.serverVersion});}).then(function(result){if(result&&result.code===409){button.disabled=false;showVersionConflict(result);return;}if(!(result&&result.code===200))throw new Error(result&&result.msg?result.msg:'restore_failed');state.serverVersion=parseInt(result.version_no||state.serverVersion,10)||state.serverVersion;clearLocalRecovery();state.allowExit=true;setStatus('历史版本已恢复，正在刷新...');window.location.reload();}).catch(function(){button.disabled=false;setStatus('历史版本恢复失败');alert('历史版本恢复失败，请稍后重试。');});}

    function uploadImageForSelected() {
        var imageNode = selectedImageUploadNode();
        if (!imageNode) {
            return;
        }
        var keepSelectedPath = state.selectedPath || getDomPath(imageNode);
        var picker = document.createElement('input');
        picker.type = 'file';
        picker.accept = 'image/*';
        picker.addEventListener('change', function () {
            var file = picker.files && picker.files[0];
            if (!file) {
                return;
            }
            var formData = new FormData();
            formData.append('file', file);
            formData.append('upload_type', 'image');
            formData.append('csrf_token', boot.csrf_token);
            fetch(builderUploadUrl(), {
                method: 'POST',
                body: formData
            }).then(function (response) {
                return response.json();
            }).then(function (result) {
                if (result && result.code === 200 && result.data && result.data.url) {
                    updateSpecificNode(imageNode, 'src', result.data.url, keepSelectedPath);
                    renderSettings();
                } else {
                    alert(result && result.msg ? result.msg : '图片上传失败');
                }
            }).catch(function () {
                alert('图片上传失败');
            });
        });
        picker.click();
    }

    function insertImageIntoSelectedNode() {
        var targetNode = selectedImageInsertTargetNode();
        if (!targetNode || !canInsertImageIntoSelectedNode()) {
            return;
        }
        var picker = document.createElement('input');
        picker.type = 'file';
        picker.accept = 'image/*';
        picker.addEventListener('change', function () {
            var file = picker.files && picker.files[0];
            if (!file) {
                return;
            }
            var formData = new FormData();
            formData.append('file', file);
            formData.append('upload_type', 'image');
            formData.append('csrf_token', boot.csrf_token);
            fetch(builderUploadUrl(), {
                method: 'POST',
                body: formData
            }).then(function (response) {
                return response.json();
            }).then(function (result) {
                if (!(result && result.code === 200 && result.data && result.data.url)) {
                    alert(result && result.msg ? result.msg : '图片上传失败');
                    return;
                }
                var imageHtml = '<img src="' + escapeHtml(result.data.url) + '" alt="" style="max-width:100%;display:block;border-radius:12px;margin:12px 0;">';
                insertHtmlIntoSelectedNode(imageHtml, '图片已插入当前模块。');
            }).catch(function () {
                alert('图片上传失败');
            });
        });
        picker.click();
    }

    function uploadImageForExtraField(extraId, fieldKey, uploadType) {
        if (!extraId || !fieldKey) {
            return;
        }
        uploadType = uploadType || 'image';
        var picker = document.createElement('input');
        picker.type = 'file';
        picker.accept = uploadType === 'video' ? 'video/*' : 'image/*';
        picker.addEventListener('change', function () {
            var file = picker.files && picker.files[0];
            if (!file) {
                return;
            }
            var formData = new FormData();
            formData.append('file', file);
            formData.append('upload_type', uploadType);
            formData.append('csrf_token', boot.csrf_token);
            fetch(builderUploadUrl(), {
                method: 'POST',
                body: formData
            }).then(function (response) {
                return response.json();
            }).then(function (result) {
                if (result && result.code === 200 && result.data && result.data.url) {
                    updateExtraConfig(extraId, fieldKey, result.data.url);
                    renderSettings();
                } else {
                    alert(result && result.msg ? result.msg : (uploadType === 'video' ? '视频上传失败' : '图片上传失败'));
                }
            }).catch(function () {
                alert(uploadType === 'video' ? '视频上传失败' : '图片上传失败');
            });
        });
        picker.click();
    }

    function uploadImageForPageField(fieldKey) {
        if (!fieldKey) {
            return;
        }
        var picker = document.createElement('input');
        picker.type = 'file';
        picker.accept = 'image/*';
        picker.addEventListener('change', function () {
            var file = picker.files && picker.files[0];
            if (!file) {
                return;
            }
            var formData = new FormData();
            formData.append('file', file);
            formData.append('upload_type', 'image');
            formData.append('csrf_token', boot.csrf_token);
            fetch(builderUploadUrl(), {
                method: 'POST',
                body: formData
            }).then(function (response) {
                return response.json();
            }).then(function (result) {
                if (result && result.code === 200 && result.data && result.data.url) {
                    updatePageField(fieldKey, result.data.url);
                    if (fieldKey === 'favicon_url') {
                        syncPageFieldInputs('favicon_url', result.data.url);
                    }
                    renderSettings();
                } else {
                    alert(result && result.msg ? result.msg : '图片上传失败');
                }
            }).catch(function () {
                alert('图片上传失败');
            });
        });
        picker.click();
    }

    function mountEditUi() {
        document.documentElement.classList.add('cms-frontend-builder-edit');
        var root = qs('#cms_frontend_builder_root');
        if (!root) {
            return;
        }
        root.innerHTML = '' +
            '<div class="cms-builder-topbar">' +
                '<div class="cms-builder-topbar-left"><button type="button" class="cms-builder-btn cms-builder-btn-danger" id="cms_builder_reset_origin" title="危险操作：清空当前页面装修修改并恢复模板原始状态">恢复原始状态</button><div><div class="cms-builder-topbar-title">' + escapeHtml(builderTitle()) + '</div><div class="cms-builder-topbar-meta">前台原模板可视化装修，所见即所得编辑</div></div></div>' +
                '<div class="cms-builder-topbar-actions">' +
                    '<span class="cms-builder-presence" id="cms_builder_presence" title="正在连接协作状态">正在连接</span>' +
                    '<span class="cms-builder-status" id="cms_builder_status">编辑中</span>' +
                    '<div class="cms-builder-device-switch">' +
                        '<button type="button" id="cms_builder_top_device_pc" class="' + (currentDevice() === 'pc' ? 'is-active' : '') + '">PC</button>' +
                        '<button type="button" id="cms_builder_top_device_tablet" class="' + (currentDevice() === 'tablet' ? 'is-active' : '') + '">平板</button>' +
                        '<button type="button" id="cms_builder_top_device_mobile" class="' + (currentDevice() === 'mobile' ? 'is-active' : '') + '">移动端</button>' +
                    '</div>' +
                    '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_undo" title="撤销（Ctrl/⌘ + Z）">撤销</button>' +
                    '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_redo" title="重做（Ctrl/⌘ + Shift + Z）">重做</button>' +
                    '<button type="button" class="cms-builder-btn cms-builder-btn-secondary cms-builder-ui-hidden" id="cms_builder_save_draft" title="保存草稿（Ctrl/⌘ + S）">保存草稿</button>' +
                    '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_preflight">发布体检</button>' +
                    '<button type="button" class="cms-builder-btn cms-builder-btn-secondary cms-builder-ui-hidden" id="cms_builder_history">历史版本</button>' +
                    '<button type="button" class="cms-builder-btn cms-builder-btn-secondary cms-builder-ui-hidden" id="cms_builder_clear_history">清空回滚</button>' +
                    '<button type="button" class="cms-builder-btn cms-builder-btn-primary" id="cms_builder_publish">发布上线</button>' +
                    '<button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_exit">退出装修</button>' +
                '</div>' +
            '</div>' +
            '<div class="cms-builder-dock" id="cms_builder_dock">' +
                '<button type="button" class="cms-builder-dock-btn" data-library-mode="modules"><span class="cms-builder-dock-btn-icon"><svg viewBox="0 0 24 24" fill="none" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1.5"></rect><rect x="14" y="3" width="7" height="7" rx="1.5"></rect><rect x="3" y="14" width="7" height="7" rx="1.5"></rect><rect x="14" y="14" width="7" height="7" rx="1.5"></rect></svg></span><span>模块</span></button>' +
                '<button type="button" class="cms-builder-dock-btn" data-library-mode="style"><span class="cms-builder-dock-btn-icon"><svg viewBox="0 0 24 24" fill="none" stroke-width="2"><path d="M6 4l4 4-6 6a3 3 0 0 0 4 4l6-6 4 4 2-10-10 2z"></path></svg></span><span>样式</span></button>' +
                '<button type="button" class="cms-builder-dock-btn" data-library-mode="settings"><span class="cms-builder-dock-btn-icon"><svg viewBox="0 0 24 24" fill="none" stroke-width="2"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.6 1.6 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.6 1.6 0 0 0-1.8-.3 1.6 1.6 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.2a1.6 1.6 0 0 0-1-1.5 1.6 1.6 0 0 0-1.8.3l-.1.1a2 2 0 0 1-2.8-2.8l.1-.1a1.6 1.6 0 0 0 .3-1.8 1.6 1.6 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.2a1.6 1.6 0 0 0 1.5-1 1.6 1.6 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.6 1.6 0 0 0 1.8.3h.1A1.6 1.6 0 0 0 10 3.2V3a2 2 0 1 1 4 0v.2a1.6 1.6 0 0 0 1 1.5 1.6 1.6 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.6 1.6 0 0 0-.3 1.8v.1a1.6 1.6 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.2a1.6 1.6 0 0 0-1.4 1z"></path></svg></span><span>设置</span></button>' +
                '<button type="button" class="cms-builder-dock-btn" data-library-mode="tools"><span class="cms-builder-dock-btn-icon"><svg viewBox="0 0 24 24" fill="none" stroke-width="2"><path d="M4 21h16"></path><path d="M7 21v-4"></path><path d="M12 21v-8"></path><path d="M17 21v-6"></path><path d="M5 9l4-4 4 3 6-5"></path></svg></span><span>辅助工具</span></button>' +
            '</div>' +
            '<aside class="cms-builder-panel cms-builder-panel-left is-hidden cms-builder-no-copy" data-panel-role="library">' +
                '<div class="cms-builder-panel-body cms-builder-panel-body-library" id="cms_builder_library_body"></div>' +
            '</aside>' +
            '<div class="cms-builder-settings-backdrop is-hidden" id="cms_builder_settings_backdrop"></div>' +
            '<aside class="cms-builder-panel cms-builder-panel-right cms-builder-settings-modal cms-builder-no-copy is-hidden" data-panel-role="settings">' +
                '<div class="cms-builder-panel-head" data-panel-handle="settings"><div class="cms-builder-panel-head-main"><div class="cms-builder-panel-title">属性配置面板</div><div class="cms-builder-panel-desc">根据当前选中的页面区块，按类型编辑文本、图片、栏目样式和后台业务数据。</div></div><div class="cms-builder-panel-tools"><button type="button" class="cms-builder-panel-tool" data-panel-reset="settings">复位</button><button type="button" class="cms-builder-panel-tool" id="cms_builder_close_settings">关闭</button></div></div>' +
                '<div class="cms-builder-panel-body" id="cms_builder_settings"></div>' +
            '</aside>' +
            '<div class="cms-builder-overlay-layer" id="cms_builder_overlay_layer"></div>';
        root.innerHTML+='<div class="cms-builder-history-modal is-hidden" id="cms_builder_history_modal"><section class="cms-builder-history-card" role="dialog" aria-modal="true" aria-label="历史版本"><div class="cms-builder-history-head"><div><h2>历史版本</h2><p>草稿快照最多保留 100 条，发布前始终保留回滚点。</p></div><button type="button" class="cms-builder-panel-tool" data-history-close="1">关闭</button></div><div class="cms-builder-history-list" id="cms_builder_history_list"></div></section></div>';
        root.innerHTML+='<div class="cms-builder-preflight-modal is-hidden" id="cms_builder_preflight_modal"><section class="cms-builder-preflight-card cms-builder-no-copy" role="dialog" aria-modal="true" aria-labelledby="cms_builder_preflight_title"><div class="cms-builder-preflight-head"><div><h2 id="cms_builder_preflight_title">发布前页面体检</h2><p>检查安全、模块内容、移动端、素材、SEO 与业务数据，不修改当前草稿。</p></div><button type="button" class="cms-builder-panel-tool" data-preflight-close="1">关闭</button></div><div class="cms-builder-preflight-body" id="cms_builder_preflight_body"></div><div class="cms-builder-preflight-actions"><button type="button" class="cms-builder-btn cms-builder-btn-primary" data-preflight-close="1">完成</button></div></section></div>';
        root.innerHTML+='<div class="cms-builder-confirm-modal is-hidden" id="cms_builder_confirm_modal"><section class="cms-builder-confirm-card" role="alertdialog" aria-modal="true" aria-labelledby="cms_builder_confirm_title" aria-describedby="cms_builder_confirm_message"><h2 id="cms_builder_confirm_title">确认操作</h2><p id="cms_builder_confirm_message"></p><div class="cms-builder-confirm-actions"><button type="button" class="cms-builder-btn cms-builder-btn-secondary" id="cms_builder_confirm_cancel">取消</button><button type="button" class="cms-builder-btn cms-builder-btn-primary cms-builder-confirm-submit" id="cms_builder_confirm_submit">确认</button></div></section></div>';
        root.innerHTML+='<div class="cms-builder-rich-editor-modal is-hidden" id="cms_builder_rich_editor_modal"><section class="cms-builder-rich-editor-card cms-builder-no-copy" role="dialog" aria-modal="true" aria-labelledby="cms_builder_rich_editor_title"><div class="cms-builder-rich-editor-head"><div><h2 id="cms_builder_rich_editor_title">新版 UEditor 富文本编辑</h2><p>支持排版、图片、视频、附件和表格；保存时仍会经过服务端安全校验。</p></div><button type="button" class="cms-builder-panel-tool" id="cms_builder_close_rich_editor">关闭</button></div><div class="cms-ueditor-wrap"><textarea id="cms_builder_rich_text_source" class="cms-ueditor-fallback" data-height="440" data-z-index="2147483100" aria-label="富文本内容"></textarea></div><div class="cms-builder-rich-editor-actions"><button type="button" class="cms-builder-btn cms-builder-btn-light" id="cms_builder_cancel_rich_editor">取消</button><button type="button" class="cms-builder-btn cms-builder-btn-primary" id="cms_builder_save_rich_editor">保存内容</button></div></section></div>';

        ensurePanelDefaults();
        hideSettingsPanel();
        renderLibraryPanel();
        hideLibraryPanel();
        refreshModeStatus();
        syncDeviceButtons();

        qs('#cms_builder_undo').addEventListener('click', undoChange);
        qs('#cms_builder_redo').addEventListener('click', redoChange);
        qs('#cms_builder_preflight').addEventListener('click', function () { runPreflight(true); });
        qs('#cms_builder_history').addEventListener('click',openHistoryPanel);
        qs('#cms_builder_reset_origin').addEventListener('click', restoreOriginalTemplateLayout);
        qs('#cms_builder_top_device_pc').addEventListener('click', function () {
            updatePageField('device_mode', 'pc');
        });
        qs('#cms_builder_top_device_tablet').addEventListener('click', function () { updatePageField('device_mode','tablet'); });
        qs('#cms_builder_top_device_mobile').addEventListener('click', function () {
            updatePageField('device_mode', 'mobile');
        });
        syncHistoryButtons();
        qs('#cms_builder_history_modal').addEventListener('click',function(event){if(event.target===this||event.target.closest('[data-history-close]')){closeHistoryPanel();return;}var restore=event.target.closest('[data-history-restore]');if(restore)restoreHistoryVersion(restore);});
        qs('#cms_builder_preflight_modal').addEventListener('click', function (event) { if (event.target === this || event.target.closest('[data-preflight-close]')) closePreflightPanel(); });
        qs('#cms_builder_confirm_cancel').addEventListener('click',function(){closeBuilderConfirm(false);});
        qs('#cms_builder_confirm_submit').addEventListener('click',function(){closeBuilderConfirm(true);});
        qs('#cms_builder_confirm_modal').addEventListener('click',function(event){if(event.target===this)closeBuilderConfirm(false);});

        qs('#cms_builder_save_draft').addEventListener('click', function () {
            saveDraftNow().then(function (result) {
                if(result&&result.code===200)setStatus('草稿已保存 · '+savedTimeLabel(state.lastSavedAt));
                else if(!(result&&result.code===409)&&!(result&&result.offline))setStatus('草稿保存失败');
                if (result && result.msg) {
                    alert(result.msg);
                }
            }).catch(function () {
                setStatus('草稿保存失败');
                alert('草稿保存失败，请稍后重试。');
            });
        });


        qs('#cms_builder_clear_history').addEventListener('click', async function () {
            if (!await builderConfirm({title:'清空回滚记录',message:'将清空当前页面全部回滚记录，清空后不可恢复。',confirmText:'确认清空',tone:'danger'})) {
                return;
            }
            setStatus('正在清空当前页面回滚记录...');
            postApi('clear_history', {}).then(function (result) {
                setStatus(result && result.code === 200 ? '当前页面回滚记录已清空' : '清空回滚记录失败');
                alert(result && result.msg ? result.msg : '清空回滚记录失败。');
            }).catch(function () {
                setStatus('清空回滚记录失败');
                alert('清空回滚记录失败，请稍后重试。');
            });
        });

        qs('#cms_builder_publish').addEventListener('click', async function () {
            var preflight = await runPreflight(false);
            if (!preflight || !preflight.can_publish) return;
            var warningCount = parseInt((preflight.counts || {}).warning || 0, 10);
            var preflightSummary = warningCount ? ('发布体检已通过，另有 ' + warningCount + ' 项非阻断警告。') : '发布体检已全部通过。';
            if(!await builderConfirm({title:'确认发布上线',message:preflightSummary + ' ' + publishChangeSummary(),confirmText:'发布上线'}))return;
            clearTimeout(state.saveTimer);
            state.saveTimer = null;
            setStatus('正在同步最新草稿...');
            saveDraftNow().then(function(saved){if(!(saved&&saved.code===200)){if(!(saved&&saved.code===409))setStatus('草稿保存失败，已停止发布');alert(saved&&saved.msg?saved.msg:'最新草稿保存失败，已停止发布。');return null;}setStatus('正在发布...');return postApi('publish',{title:builderTitle(),is_enabled:1,layout:buildLayout(),expected_version:state.serverVersion});}).then(function (result) {
                if(!result)return;
                if(result.code===409){showVersionConflict(result);return;}
                if(result && result.preflight) openPreflightPanel(result.preflight);
                setStatus(result && result.code === 200 ? '发布成功' : '发布失败');
                if(result&&result.code===200){state.serverVersion=parseInt(result.version_no||state.serverVersion,10)||state.serverVersion;state.savedRevision=state.changeRevision;state.dirty=false;state.lastSavedAt=Date.now();announceSavedVersion();clearLocalRecovery();}
                alert(result && result.msg ? result.msg : (result && result.code === 200 ? '模板已发布并立即生效。' : '模板发布失败。'));
            }).catch(function () {
                setStatus('发布失败');
                alert('模板发布失败。');
            });
        });

        qs('#cms_builder_exit').addEventListener('click', function () {
            var target=boot.exit_url||'/';if(!hasPendingDraft()){state.allowExit=true;window.location.href=target;return;}setStatus('退出前正在保存草稿...');saveDraftNow().then(function(result){if(result&&result.code===200){state.allowExit=true;window.location.href=target;return;}return builderConfirm({title:'草稿尚未保存',message:'草稿保存失败或存在编辑冲突，仍要退出装修吗？当前页面中的未保存修改会丢失。',confirmText:'仍然退出',tone:'danger'}).then(function(ok){if(ok){state.allowExit=true;window.location.href=target;}});});
        });

        window.addEventListener('online',function(){state.online=true;clearTimeout(state.retryTimer);state.retryTimer=null;builderToast('网络已恢复，正在保存最新草稿','success');if(state.dirty&&!state.conflict)startDraftSave(false);});
        window.addEventListener('offline',function(){state.online=false;state.dirty=hasPendingDraft()||state.dirty;setStatus('当前离线 · 修改已保留，联网后自动保存');});
        window.addEventListener('storage',function(event){if(event.key!==builderStorageKey()||!event.newValue)return;try{var data=JSON.parse(event.newValue);if(data.tab_id!==state.tabId&&parseInt(data.version_no||0,10)>state.serverVersion)showVersionConflict(data);}catch(error){}});
        heartbeatCollaborators();state.collaboratorTimer=setInterval(heartbeatCollaborators,15000);
        document.addEventListener('visibilitychange',function(){if(document.visibilityState==='visible')heartbeatCollaborators();});
        window.addEventListener('pagehide',leaveBuilderSession);
        window.addEventListener('beforeunload',function(event){if(state.dirty)persistLocalRecoveryNow();if(state.allowExit||!hasPendingDraft())return;event.preventDefault();event.returnValue='';});
        document.addEventListener('keydown',function(event){if(state.confirmResolve){if(event.key==='Escape'){event.preventDefault();closeBuilderConfirm(false);return;}if(event.key==='Tab'){var controls=[qs('#cms_builder_confirm_cancel'),qs('#cms_builder_confirm_submit')],index=controls.indexOf(document.activeElement),next=event.shiftKey?(index<=0?controls.length-1:index-1):(index>=controls.length-1?0:index+1);event.preventDefault();controls[next].focus();return;}}var key=String(event.key||'').toLowerCase(),command=event.ctrlKey||event.metaKey,target=event.target,isField=target&&target.closest&&target.closest('input,textarea,select,[contenteditable="true"]');if(command&&key==='s'){event.preventDefault();qs('#cms_builder_save_draft').click();return;}if(!command||isField)return;if(key==='z'){event.preventDefault();if(event.shiftKey)redoChange();else undoChange();return;}if(key==='y'){event.preventDefault();redoChange();}},true);
        setTimeout(offerLocalRecovery,120);

        document.addEventListener('mousedown', function (event) {
            var dock = dockNode();
            if (!dock || window.innerWidth <= 980 || !event.target.closest('#cms_builder_dock')) {
                return;
            }
            var rect = dock.getBoundingClientRect();
            var startX = event.clientX;
            var startY = event.clientY;
            var offsetX = event.clientX - rect.left;
            var offsetY = event.clientY - rect.top;
            var moved = false;
            function onMove(moveEvent) {
                var distance = Math.max(Math.abs(moveEvent.clientX - startX), Math.abs(moveEvent.clientY - startY));
                if (!moved && distance < 6) {
                    return;
                }
                moved = true;
                state.draggingDock = true;
                dock.classList.add('is-dragging');
                state.dockPosition.left = clamp(moveEvent.clientX - offsetX, 14, Math.max(14, window.innerWidth - rect.width - 14));
                state.dockPosition.top = clamp(moveEvent.clientY - offsetY, 82, Math.max(82, window.innerHeight - rect.height - 14));
                applyDockPosition();
            }
            function onUp() {
                if (moved) {
                    state.suppressDockClickUntil = Date.now() + 300;
                    syncEditorStateToPage();
                    queueSaveDraft();
                }
                state.draggingDock = false;
                dock.classList.remove('is-dragging');
                document.removeEventListener('mousemove', onMove, true);
                document.removeEventListener('mouseup', onUp, true);
            }
            document.addEventListener('mousemove', onMove, true);
            document.addEventListener('mouseup', onUp, true);
        }, true);

        document.addEventListener('mousedown', function (event) {
            var canvasResize = event.target.closest('[data-canvas-resize]');
            if (canvasResize) {
                beginCanvasInteraction(canvasResize.getAttribute('data-canvas-id') || '', canvasResize.getAttribute('data-canvas-resize') || 'se', event);
                return;
            }
            var canvasBlock = event.target.closest('[data-canvas-block-id]');
            if (canvasBlock && !event.target.closest('[data-canvas-action]') && !event.target.closest('[data-widget-dismiss],[data-widget-popup-open],[data-widget-scroll],[data-widget-music-toggle],[data-widget-mobile-toggle],button,input,textarea,select,a,label')) {
                var blockId = canvasBlock.getAttribute('data-canvas-block-id') || '';
                if (event.ctrlKey || event.metaKey || event.shiftKey) {
                    toggleCanvasBlockSelection(blockId, false);
                    return;
                }
                selectCanvasBlock(blockId, false);
                cancelPendingCanvasDrag();
                var pendingCanvas = {
                    id: blockId,
                    startX: event.clientX,
                    startY: event.clientY,
                    moved: false,
                    onMove: null,
                    onUp: null
                };
                pendingCanvas.onMove = function (moveEvent) {
                    var delta = Math.max(Math.abs(moveEvent.clientX - pendingCanvas.startX), Math.abs(moveEvent.clientY - pendingCanvas.startY));
                    if (delta < 6) {
                        return;
                    }
                    pendingCanvas.moved = true;
                    cancelPendingCanvasDrag();
                    beginCanvasInteraction(blockId, 'move', event, {
                        startClientX: pendingCanvas.startX,
                        startClientY: pendingCanvas.startY,
                        immediateEvent: moveEvent,
                        openSettings: false
                    });
                };
                pendingCanvas.onUp = function () {
                    cancelPendingCanvasDrag();
                    if (!pendingCanvas.moved) {
                        selectCanvasBlock(blockId, false);
                        setStatus('已选中画布模块，点击“编辑”再打开属性。');
                    }
                };
                state.pendingCanvasDrag = pendingCanvas;
                document.addEventListener('mousemove', pendingCanvas.onMove, true);
                document.addEventListener('mouseup', pendingCanvas.onUp, true);
                return;
            }
            var transformHandle = event.target.closest('[data-transform-handle],[data-resize-handle]');
            if (transformHandle) {
                var transformMode = transformHandle.getAttribute('data-transform-handle') || transformHandle.getAttribute('data-resize-handle') || 'se';
                beginBlockTransform(
                    transformHandle.getAttribute('data-block-path') || '',
                    transformMode,
                    event
                );
                return;
            }
        }, true);

        document.addEventListener('dblclick', function (event) {
            var transformHandle = event.target.closest('[data-transform-handle]');
            if (!transformHandle) {
                return;
            }
            if ((transformHandle.getAttribute('data-transform-handle') || '') === 'move') {
                event.preventDefault();
                event.stopPropagation();
                resetBlockTransform(transformHandle.getAttribute('data-block-path') || '');
            }
        }, true);

        document.addEventListener('mousedown', function (event) {
            if (event.button !== 0 || interactiveDragIgnoreTarget(event.target)) {
                return;
            }
            var insideBuilderRoot = !!event.target.closest('#cms_frontend_builder_root');
            var moduleButton = event.target.closest('[data-add-extra]');
            var listButton = event.target.closest('[data-drag-path]');
            var pageBlock = !insideBuilderRoot ? event.target.closest('[data-builder-block-path],[data-builder-extra-id]') : null;
            var meta = null;
            if (moduleButton && !moduleButton.disabled && moduleButton.getAttribute('aria-disabled') !== 'true') {
                meta = {
                    kind: 'new_extra',
                    extraType: moduleButton.getAttribute('data-add-extra') || '',
                    sourceNode: moduleButton,
                    startX: event.clientX,
                    startY: event.clientY,
                    active: false,
                    currentTarget: null
                };
            } else if (listButton) {
                meta = {
                    kind: 'reorder',
                    sourcePath: listButton.getAttribute('data-drag-path') || '',
                    sourceNode: listButton,
                    startX: event.clientX,
                    startY: event.clientY,
                    active: false,
                    currentTarget: null
                };
            } else if (pageBlock) {
                var pageBlockPath = getDomPath(pageBlock);
                if (!pageBlockPath || isProtectedEditableNode(pageBlock) || listHasRef((state.page.frontend_locked_paths || []), nodeRef(pageBlock))) {
                    return;
                }
                if (isFreeLayoutMode()) {
                    selectNode(pageBlock, false, false);
                    setStatus('当前页面存在历史自由画布数据，先保存一次后会自动恢复为模板区块模式。');
                    return;
                }
                selectNode(pageBlock, false, false);
                cancelPendingBlockDrag();
                var pendingBlock = {
                    path: pageBlockPath,
                    node: pageBlock,
                    startX: event.clientX,
                    startY: event.clientY,
                    moved: false,
                    onMove: null,
                    onUp: null
                };
                pendingBlock.onMove = function (moveEvent) {
                    var delta = Math.max(Math.abs(moveEvent.clientX - pendingBlock.startX), Math.abs(moveEvent.clientY - pendingBlock.startY));
                    if (delta < 6) {
                        return;
                    }
                    pendingBlock.moved = true;
                    cancelPendingBlockDrag();
                    beginBlockTransform(pendingBlock.path, 'move', event, {
                        startClientX: pendingBlock.startX,
                        startClientY: pendingBlock.startY,
                        immediateEvent: moveEvent
                    });
                };
                pendingBlock.onUp = function () {
                    cancelPendingBlockDrag();
                    if (!pendingBlock.moved) {
                        selectNode(pendingBlock.node, false, false);
                        setStatus('已选中模块，点击“编辑”再打开属性。');
                    }
                };
                state.pendingBlockDrag = pendingBlock;
                document.addEventListener('mousemove', pendingBlock.onMove, true);
                document.addEventListener('mouseup', pendingBlock.onUp, true);
                return;
            }
            if (!meta || (!meta.extraType && !meta.sourcePath)) {
                return;
            }
            function onMove(moveEvent) {
                if (!state.mouseDragMeta && !meta.active) {
                    var delta = Math.max(Math.abs(moveEvent.clientX - meta.startX), Math.abs(moveEvent.clientY - meta.startY));
                    if (delta < 6) {
                        return;
                    }
                    meta.active = true;
                    startMouseDrag(meta);
                }
                if (!meta.active) {
                    return;
                }
                moveEvent.preventDefault();
                meta.lastX = moveEvent.clientX;
                meta.lastY = moveEvent.clientY;
                var targetMeta = findDropTargetAtPoint(moveEvent.clientX, moveEvent.clientY, meta.sourcePath || '');
                meta.currentTarget = targetMeta;
                applyMouseDragTarget(targetMeta);
            }
            function onUp() {
                document.removeEventListener('mousemove', onMove, true);
                document.removeEventListener('mouseup', onUp, true);
                if (meta.active) {
                    finishMouseDrag();
                }
            }
            document.addEventListener('mousemove', onMove, true);
            document.addEventListener('mouseup', onUp, true);
        }, true);

        document.addEventListener('mousedown', function (event) {
            var handle = event.target.closest('[data-panel-handle]');
            var libraryPanel = event.target.closest('[data-panel-role="library"]');
            var settingsPanel = event.target.closest('[data-panel-role="settings"]');
            if (!handle && libraryPanel) {
                handle = libraryPanel;
            }
            if (!handle && settingsPanel) {
                handle = settingsPanel;
            }
            if (event.target.closest('#cms_builder_close_library,#cms_builder_close_settings,[data-panel-reset],.cms-builder-panel-tool,.cms-builder-library-close')) {
                return;
            }
            if (!handle || window.innerWidth <= 980 || event.target.closest('button, input, textarea, select, label, a')) {
                return;
            }
            var role = handle.getAttribute('data-panel-handle') || handle.getAttribute('data-panel-role');
            var panel = panelNode(role);
            if (!panel) {
                return;
            }
            event.preventDefault();
            var rect = panel.getBoundingClientRect();
            var offsetX = event.clientX - rect.left;
            var offsetY = event.clientY - rect.top;
            state.draggingPanelRole = role;
            function onMove(moveEvent) {
                var width = panel.offsetWidth || rect.width || 308;
                var height = panel.offsetHeight || rect.height || 420;
                state.panelPositions[role].left = clamp(moveEvent.clientX - offsetX, 14, Math.max(14, window.innerWidth - width - 14));
                state.panelPositions[role].top = clamp(moveEvent.clientY - offsetY, 82, Math.max(82, window.innerHeight - height - 14));
                applyPanelPosition(role);
            }
            function onUp() {
                state.draggingPanelRole = '';
                syncEditorStateToPage();
                queueSaveDraft();
                document.removeEventListener('mousemove', onMove, true);
                document.removeEventListener('mouseup', onUp, true);
            }
            document.addEventListener('mousemove', onMove, true);
            document.addEventListener('mouseup', onUp, true);
        }, true);

        document.addEventListener('mousedown', function (event) {
            var handle = event.target.closest('[data-modal-handle]');
            if (!handle || event.target.closest('button, input, textarea, select, label, a')) {
                return;
            }
            var card = modalCardNode();
            if (!card) {
                return;
            }
            event.preventDefault();
            var modalRole = handle.getAttribute('data-modal-handle') || 'material-preview';
            var rect = card.getBoundingClientRect();
            var offsetX = event.clientX - rect.left;
            var offsetY = event.clientY - rect.top;
            if (modalRole === 'business-editor') {
                state.businessModalPosition = {left: rect.left, top: rect.top};
            } else {
                state.materialModalPosition = {left: rect.left, top: rect.top};
            }
            renderFloatingOverlays();
            function onMove(moveEvent) {
                var cardWidth = rect.width || 920;
                var cardHeight = rect.height || 640;
                var nextPosition = {
                    left: clamp(moveEvent.clientX - offsetX, 14, Math.max(14, window.innerWidth - cardWidth - 14)),
                    top: clamp(moveEvent.clientY - offsetY, 14, Math.max(14, window.innerHeight - cardHeight - 14))
                };
                if (modalRole === 'business-editor') {
                    state.businessModalPosition = nextPosition;
                } else {
                    state.materialModalPosition = nextPosition;
                }
                renderFloatingOverlays();
            }
            function onUp() {
                syncEditorStateToPage();
                queueSaveDraft();
                document.removeEventListener('mousemove', onMove, true);
                document.removeEventListener('mouseup', onUp, true);
            }
            document.addEventListener('mousemove', onMove, true);
            document.addEventListener('mouseup', onUp, true);
        }, true);

        window.addEventListener('resize', function () {
            if (window.innerWidth <= 980) {
                return;
            }
            applyDockPosition();
            applyPanelPosition('library');
            if (state.selectedNode || state.selectedCanvasBlockId) {
                if (state.settingsPanelOpen) {
                    ensureSettingsPanelForCurrentSelection();
                } else {
                    applyPanelPosition('settings');
                }
            } else {
                applyPanelPosition('settings');
            }
            renderFloatingOverlays();
        });

        window.addEventListener('blur', function () {
            cancelPendingCanvasDrag();
            cancelPendingBlockDrag();
            cancelMouseDrag();
        }, true);
        window.addEventListener('scroll', renderFloatingOverlays, true);

        document.addEventListener('click', function (event) {
            if (Date.now() < state.suppressClickUntil) {
                event.preventDefault();
                event.stopPropagation();
                return;
            }
            var navLink = event.target.closest('a[href]');
            if (navLink && boot.edit_mode && !event.defaultPrevented && !navLink.closest('#cms_frontend_builder_root')) {
                var href = navLink.getAttribute('href') || '';
                var editUrl = resolveColumnEditUrlFromHref(href);
                if (editUrl && !event.ctrlKey && !event.metaKey && !event.shiftKey && !event.altKey) {
                    event.preventDefault();
                    window.location.href = editUrl;
                    return;
                }
            }
            if (event.target.closest('[data-canvas-block-id]') && !event.target.closest('[data-canvas-action],[data-canvas-resize]')) {
                return;
            }
            var globalCanvasAction = event.target.closest('[data-canvas-action]');
            if (globalCanvasAction) {
                var globalCanvasActionId = globalCanvasAction.getAttribute('data-canvas-id') || state.selectedCanvasBlockId;
                var globalCanvasActionName = globalCanvasAction.getAttribute('data-canvas-action') || '';
                if (globalCanvasActionName === 'edit') {
                    triggerCanvasPrimaryEditAction(globalCanvasActionId);
                } else if (globalCanvasActionName === 'duplicate') {
                    duplicateCanvasBlock(globalCanvasActionId);
                } else if (globalCanvasActionName === 'back') {
                    moveCanvasZ(globalCanvasActionId, -1);
                    selectCanvasBlock(globalCanvasActionId, false);
                } else if (globalCanvasActionName === 'selection-lock') {
                    patchCanvasSelection({locked: 1});
                } else if (globalCanvasActionName === 'selection-unlock') {
                    patchCanvasSelection({locked: 0});
                } else if (globalCanvasActionName === 'selection-hide') {
                    patchCanvasSelection({hidden: 1});
                } else if (globalCanvasActionName === 'selection-show') {
                    patchCanvasSelection({hidden: 0});
                } else if (globalCanvasActionName === 'selection-clear') {
                    selectCanvasBlock(state.selectedCanvasBlockId || selectedCanvasIds()[0] || '', false);
                } else if (globalCanvasActionName === 'selection-delete') {
                    deleteCanvasSelection();
                } else if (globalCanvasActionName.indexOf('align-') === 0) {
                    if ((selectedCanvasIds().length > 1 ? alignCanvasSelection(globalCanvasActionName.replace('align-', '')) : alignCanvasBlock(globalCanvasActionId, globalCanvasActionName.replace('align-', '')))) {
                        selectCanvasBlock(globalCanvasActionId, false);
                        setStatus('画布模块已完成快速对齐。');
                    }
                } else if (globalCanvasActionName === 'delete') {
                    deleteCanvasBlock(globalCanvasActionId);
                } else if (globalCanvasActionName === 'lock') {
                    var globalLockedBlock = findCanvasBlock(globalCanvasActionId);
                    if (globalLockedBlock) {
                        pushHistory();
                        updateCanvasBlock(globalCanvasActionId, {locked: globalLockedBlock.locked ? 0 : 1});
                        selectCanvasBlock(globalCanvasActionId, false);
                        queueSaveDraft();
                    }
                } else if (globalCanvasActionName === 'front') {
                    moveCanvasZ(globalCanvasActionId, 999);
                    selectCanvasBlock(globalCanvasActionId, false);
                }
                return;
            }
            var globalCanvasSelect = event.target.closest('[data-canvas-select]');
            if (globalCanvasSelect) {
                if (event.ctrlKey || event.metaKey || event.shiftKey) {
                    toggleCanvasBlockSelection(globalCanvasSelect.getAttribute('data-canvas-select') || '', false);
                } else {
                    selectCanvasBlock(globalCanvasSelect.getAttribute('data-canvas-select') || '', false);
                }
                return;
            }
            var dockButton = event.target.closest('[data-library-mode]');
            if (dockButton) {
                if (dockButton.closest('#cms_builder_dock') && Date.now() < state.suppressDockClickUntil) {
                    return;
                }
                var nextMode = dockButton.getAttribute('data-library-mode') || 'modules';
                var fromDock = !!dockButton.closest('#cms_builder_dock');
                if (fromDock && state.libraryPanelMode === nextMode && panelNode('library') && !panelNode('library').classList.contains('is-hidden')) {
                    hideLibraryPanel();
                } else {
                    setLibraryMode(nextMode, true);
                }
                return;
            }
            var widgetDismissButton = event.target.closest('[data-widget-dismiss]');
            if (widgetDismissButton) {
                var dismissKeyGlobal = widgetDismissButton.getAttribute('data-widget-dismiss') || '';
                qsa('[data-widget-popup="' + dismissKeyGlobal + '"]').forEach(function (node) {
                    if (dismissKeyGlobal === 'home_popup' || dismissKeyGlobal === 'welcome_screen') {
                        node.parentNode && node.parentNode.removeChild(node);
                    } else {
                        node.style.display = 'none';
                    }
                });
                return;
            }
            var widgetPopupOpenButton = event.target.closest('[data-widget-popup-open]');
            if (widgetPopupOpenButton) {
                var openKey = widgetPopupOpenButton.getAttribute('data-widget-popup-open') || '';
                qsa('[data-widget-popup="' + openKey + '"]').forEach(function (node) {
                    node.style.display = 'flex';
                });
                return;
            }
            var widgetScrollButton = event.target.closest('[data-widget-scroll]');
            if (widgetScrollButton) {
                scrollToWidgetTarget(widgetScrollButton.getAttribute('data-widget-scroll') || 'top');
                return;
            }
            if (event.target.closest('[data-widget-music-toggle]')) {
                toggleWidgetMusic();
                return;
            }
            if (event.target.closest('[data-widget-mobile-toggle]')) {
                togglePublicMobilePreview();
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root')) {
                var canvasAction = event.target.closest('[data-canvas-action]');
                if (canvasAction) {
                    var canvasActionId = canvasAction.getAttribute('data-canvas-id') || state.selectedCanvasBlockId;
                    var canvasActionName = canvasAction.getAttribute('data-canvas-action') || '';
                    if (canvasActionName === 'edit') {
                        triggerCanvasPrimaryEditAction(canvasActionId);
                    } else if (canvasActionName === 'duplicate') {
                        duplicateCanvasBlock(canvasActionId);
                    } else if (canvasActionName === 'back') {
                        moveCanvasZ(canvasActionId, -1);
                        selectCanvasBlock(canvasActionId, false);
                    } else if (canvasActionName.indexOf('align-') === 0) {
                        if (alignCanvasBlock(canvasActionId, canvasActionName.replace('align-', ''))) {
                            selectCanvasBlock(canvasActionId, false);
                            setStatus('画布模块已完成快速对齐。');
                        }
                    } else if (canvasActionName === 'delete') {
                        deleteCanvasBlock(canvasActionId);
                    } else if (canvasActionName === 'lock') {
                        var lockedBlock = findCanvasBlock(canvasActionId);
                        if (lockedBlock) {
                            pushHistory();
                            updateCanvasBlock(canvasActionId, {locked: lockedBlock.locked ? 0 : 1});
                            selectCanvasBlock(canvasActionId, false);
                            queueSaveDraft();
                        }
                    } else if (canvasActionName === 'front') {
                        moveCanvasZ(canvasActionId, 999);
                        selectCanvasBlock(canvasActionId, false);
                    }
                    return;
                }
                var canvasSelect = event.target.closest('[data-canvas-select]');
                if (canvasSelect) {
                    if (event.ctrlKey || event.metaKey || event.shiftKey) {
                        toggleCanvasBlockSelection(canvasSelect.getAttribute('data-canvas-select') || '', false);
                    } else {
                        selectCanvasBlock(canvasSelect.getAttribute('data-canvas-select') || '', false);
                    }
                    return;
                }
                var settingsTabButton = event.target.closest('[data-settings-panel-tab]');
                if (settingsTabButton) {
                    state.settingsPanelTab = settingsTabButton.getAttribute('data-settings-panel-tab') || 'basic';
                    syncEditorStateToPage();
                    renderLibraryPanel();
                    queueSaveDraft();
                    return;
                }
                var styleTabButton = event.target.closest('[data-style-panel-tab]');
                if (styleTabButton) {
                    state.stylePanelTab = styleTabButton.getAttribute('data-style-panel-tab') || 'theme';
                    syncEditorStateToPage();
                    renderLibraryPanel();
                    queueSaveDraft();
                    return;
                }
                var moduleTabButton = event.target.closest('[data-library-module-tab]');
                if (moduleTabButton) {
                    state.libraryModuleTab = moduleTabButton.getAttribute('data-library-module-tab') || 'new_module';
                    syncEditorStateToPage();
                    renderLibraryPanel();
                    queueSaveDraft();
                    return;
                }
                var groupToggle = event.target.closest('[data-library-group-toggle]');
                if (groupToggle) {
                    var groupKey = groupToggle.getAttribute('data-library-group-toggle') || '';
                    state.libraryExpandedGroups[groupKey] = !state.libraryExpandedGroups[groupKey];
                    syncEditorStateToPage();
                    renderLibraryPanel();
                    queueSaveDraft();
                    return;
                }
                var blockAction = event.target.closest('[data-block-action]');
                if (blockAction) {
                    var actionPath = blockAction.getAttribute('data-block-path') || '';
                    var actionNode = queryByPath(actionPath);
                    if (!actionNode) {
                        return;
                    }
                    if (blockAction.getAttribute('data-block-action') === 'edit') {
                        triggerPrimaryEditAction(actionNode);
                    } else if (blockAction.getAttribute('data-block-action') === 'lock') {
                        toggleBlockLockedByPath(actionPath);
                    } else if (blockAction.getAttribute('data-block-action') === 'delete') {
                        deleteBlockByPath(actionPath);
                    } else if (blockAction.getAttribute('data-block-action') === 'locate') {
                        centerNodeInViewport(actionNode);
                        selectNode(actionNode, false, false);
                        setStatus('已定位当前区块，点击悬浮主按钮可继续编辑。');
                    }
                    return;
                }
                if (event.target.matches('[data-select-path]')) {
                    selectNode(queryByPath(event.target.getAttribute('data-select-path')), true, false);
                    if (isFreeLayoutMode()) {
                        setStatus('当前页面存在历史自由画布数据，先保存一次后会自动恢复为模板区块模式。');
                    }
                    return;
                }
                var toggleHiddenNodeButton = event.target.closest('[data-node-toggle-hidden-path]');
                if (toggleHiddenNodeButton) {
                    toggleBlockHiddenByPath(toggleHiddenNodeButton.getAttribute('data-node-toggle-hidden-path'));
                    return;
                }
                var deleteNodeButton = event.target.closest('[data-node-delete-path]');
                if (deleteNodeButton) {
                    deleteBlockByPath(deleteNodeButton.getAttribute('data-node-delete-path'));
                    return;
                }
                if (event.target.hasAttribute('data-browser-title-visible')) {
                    applyPageStylePatch({browser_title_visible: parseInt(event.target.getAttribute('data-browser-title-visible'), 10) === 1 ? 1 : 0}, '', '浏览器标题设置已更新');
                    return;
                }
                if (event.target.closest('[data-widget-preview-close]') || event.target.id === 'cms_builder_settings_widget_modal') {
                    state.settingsWidgetPreviewKey = '';
                    renderFloatingOverlays();
                    return;
                }
                var widgetPreviewButton = event.target.closest('[data-widget-preview]');
                if (widgetPreviewButton) {
                    state.settingsWidgetPreviewKey = widgetPreviewButton.getAttribute('data-widget-preview') || '';
                    renderFloatingOverlays();
                    return;
                }
                var themeColorButton = event.target.closest('[data-page-theme-color]');
                if (themeColorButton) {
                    applyPageStylePatch({theme_color: themeColorButton.getAttribute('data-page-theme-color') || '#5f7eeb'}, '', '主题色已更新');
                    return;
                }
                var themeSurfaceButton = event.target.closest('[data-page-theme-surface]');
                if (themeSurfaceButton) {
                    applyPageStylePatch({theme_surface: themeSurfaceButton.getAttribute('data-page-theme-surface') || 'clean'}, '', '主题风格已更新');
                    return;
                }
                var widthPresetButton = event.target.closest('[data-page-width-preset]');
                if (widthPresetButton) {
                    applyPageWidthPreset(widthPresetButton.getAttribute('data-page-width-preset') || '1200');
                    return;
                }
                var backgroundModeButton = event.target.closest('[data-page-background-mode]');
                if (backgroundModeButton) {
                    applyPageBackgroundMode(backgroundModeButton.getAttribute('data-page-background-mode') || 'default');
                    return;
                }
                var layoutPresetButton = event.target.closest('[data-page-layout-preset]');
                if (layoutPresetButton) {
                    applyLayoutPreset(layoutPresetButton.getAttribute('data-page-layout-preset') || 'classic');
                    return;
                }
                var locateStyleButton = event.target.closest('[data-style-locate-part]');
                if (locateStyleButton) {
                    jumpToStylePart(locateStyleButton.getAttribute('data-style-locate-part') || 'layout');
                    return;
                }
                var selectStylePartButton = event.target.closest('[data-style-part-select]');
                if (selectStylePartButton) {
                    selectStylePart(selectStylePartButton.getAttribute('data-style-part-select') || 'layout', true);
                    return;
                }
                var toggleStylePartButton = event.target.closest('[data-style-part-toggle-hidden]');
                if (toggleStylePartButton) {
                    toggleStylePartHidden(toggleStylePartButton.getAttribute('data-style-part-toggle-hidden') || 'layout');
                    return;
                }
                var layoutBlockSelectButton = event.target.closest('[data-layout-block-select]');
                if (layoutBlockSelectButton) {
                    selectNode(queryByPath(layoutBlockSelectButton.getAttribute('data-layout-block-select') || ''), true, false);
                    return;
                }
                var layoutBlockToggleButton = event.target.closest('[data-layout-block-toggle-hidden]');
                if (layoutBlockToggleButton) {
                    toggleBlockHiddenByPath(layoutBlockToggleButton.getAttribute('data-layout-block-toggle-hidden') || '');
                    return;
                }
                var locateStylePathButton = event.target.closest('[data-style-locate-path]');
                if (locateStylePathButton) {
                    var locateNode = queryByPath(locateStylePathButton.getAttribute('data-style-locate-path') || '');
                    if (locateNode) {
                        centerNodeInViewport(locateNode);
                        selectNode(locateNode, false, false);
                        setStatus('已定位当前区块。');
                    }
                    return;
                }
                var materialCategoryButton = event.target.closest('[data-material-category]');
                if (materialCategoryButton) {
                    state.materialCategory = materialCategoryButton.getAttribute('data-material-category') || 'product_showcase';
                    syncEditorStateToPage();
                    renderLibraryPanel();
                    queueSaveDraft();
                    return;
                }
                var materialScopeButton = event.target.closest('[data-material-scope]');
                if (materialScopeButton) {
                    state.materialScope = materialScopeButton.getAttribute('data-material-scope') || 'scene';
                    syncEditorStateToPage();
                    renderLibraryPanel();
                    queueSaveDraft();
                    return;
                }
                var materialPreviewButton = event.target.closest('[data-material-preview]');
                if (materialPreviewButton) {
                    state.materialPreviewKey = materialPreviewButton.getAttribute('data-material-preview') || '';
                    renderLibraryPanel();
                    return;
                }
                var materialApplyButton = event.target.closest('[data-material-apply]');
                if (materialApplyButton) {
                    applyMaterialPreset(materialApplyButton.getAttribute('data-material-apply'));
                    return;
                }
                if (event.target.closest('[data-material-preview-close]') || event.target.id === 'cms_builder_material_modal') {
                    state.materialPreviewKey = '';
                    renderLibraryPanel();
                    return;
                }
                if (event.target.id === 'cms_builder_clear_override') {
                    clearSelectedOverride();
                    return;
                }
                if (event.target.id === 'cms_builder_upload_image') {
                    uploadImageForSelected();
                    return;
                }
                if (event.target.id === 'cms_builder_insert_image_into_block') {
                    insertImageIntoSelectedNode();
                    return;
                }
                if (event.target.id === 'cms_builder_insert_text_into_block') {
                    insertPresetIntoSelectedNode('text');
                    return;
                }
                if (event.target.id === 'cms_builder_insert_button_into_block') {
                    insertPresetIntoSelectedNode('button');
                    return;
                }
                if (event.target.hasAttribute('data-page-upload-key')) {
                    uploadImageForPageField(event.target.getAttribute('data-page-upload-key') || '');
                    return;
                }
                var extraUploadButton = event.target.closest('[data-extra-upload-key]');
                if (extraUploadButton) {
                    var currentExtra = selectedExtraConfig();
                    if (currentExtra) {
                        uploadImageForExtraField(
                            currentExtra.id,
                            extraUploadButton.getAttribute('data-extra-upload-key') || '',
                            extraUploadButton.getAttribute('data-extra-upload-type') || 'image'
                        );
                    }
                    return;
                }
                if (event.target.id === 'cms_builder_toggle_hide') {
                    toggleSelectedHidden();
                    return;
                }
                if (event.target.id === 'cms_builder_convert_free') {
                    if (state.selectedNode) {
                        convertNodeToCanvasBlock(state.selectedNode);
                    }
                    return;
                }
                if (event.target.id === 'cms_builder_convert_page_free') {
                    convertCurrentPageToFreeLayout();
                    return;
                }
                if (event.target.id === 'cms_builder_restore_template') {
                    restoreTemplateLayoutMode();
                    return;
                }
                if (event.target.id === 'cms_builder_restore_template_clear') {
                    restoreTemplateAndClearCanvasBlocks();
                    return;
                }
                if (event.target.id === 'cms_builder_toggle_lock') {
                    toggleSelectedLocked();
                    return;
                }
                if (event.target.id === 'cms_builder_move_up') {
                    moveSelectedNode(-1);
                    return;
                }
                if (event.target.id === 'cms_builder_move_down') {
                    moveSelectedNode(1);
                    return;
                }
                if (event.target.id === 'cms_builder_duplicate') {
                    duplicateSelectedBlock();
                    return;
                }
                if (event.target.id === 'cms_builder_delete') {
                    deleteSelectedBlock();
                    return;
                }
                if (event.target.id === 'cms_builder_device_pc') {
                    updatePageField('device_mode', 'pc');
                    return;
                }
                if (event.target.id === 'cms_builder_device_mobile') {
                    updatePageField('device_mode', 'mobile');
                    return;
                }
                if (event.target.id === 'cms_builder_reload_business') {
                    var extra = selectedExtraConfig();
                    if (extra) {
                        rebuildBusinessExtra(extra.id);
                    }
                    return;
                }
                if (event.target.id === 'cms_builder_device_tablet') { updatePageField('device_mode','tablet'); return; }
                if (event.target.id === 'cms_builder_open_business_editor') {
                    openBusinessEditor(state.selectedNode);
                    return;
                }
                if (event.target.id === 'cms_builder_open_rich_editor') {
                    openRichTextEditor();
                    return;
                }
                if (event.target.id === 'cms_builder_save_rich_editor') {
                    closeRichTextEditor(true);
                    return;
                }
                if (event.target.closest('#cms_builder_close_rich_editor,#cms_builder_cancel_rich_editor') || event.target.id === 'cms_builder_rich_editor_modal') {
                    closeRichTextEditor(false);
                    return;
                }
                if (event.target.id === 'cms_builder_sync_global_contact') {
                    var contactMeta = detectGlobalContactField(state.selectedNode, activeNodeMeta(state.selectedNode));
                    if (contactMeta) {
                        syncSelectedContactToGlobal(contactMeta.key, contactMeta.value, contactMeta.label);
                    }
                    return;
                }
                if (event.target.closest('#cms_builder_close_business_editor,#cms_builder_close_business_editor_footer') || event.target.id === 'cms_builder_business_modal') {
                    closeBusinessEditor();
                    return;
                }
                if (event.target.id === 'cms_builder_save_business_editor') {
                    saveBusinessEditor();
                    return;
                }
                if (event.target.id === 'cms_builder_business_upload_image') {
                    uploadBusinessEditorImage();
                    return;
                }
                if (event.target.closest('#cms_builder_close_settings')) {
                    hideSettingsPanel();
                    return;
                }
                if (event.target.id === 'cms_builder_settings_backdrop') {
                    hideSettingsPanel();
                    return;
                }
                if (event.target.closest('#cms_builder_close_library')) {
                    hideLibraryPanel();
                    return;
                }
                if (event.target.id === 'cms_builder_tool_undo') {
                    undoChange();
                    return;
                }
                if (event.target.id === 'cms_builder_tool_redo') {
                    redoChange();
                    return;
                }
                if (event.target.id === 'cms_builder_tool_save') {
                    qs('#cms_builder_save_draft').click();
                    return;
                }
                if (event.target.id === 'cms_builder_tool_preflight') {
                    qs('#cms_builder_preflight').click();
                    return;
                }
                if (event.target.id === 'cms_builder_tool_publish') {
                    qs('#cms_builder_publish').click();
                    return;
                }
                if (event.target.id === 'cms_builder_tool_reset_origin') {
                    restoreOriginalTemplateLayout();
                    return;
                }
                if (event.target.hasAttribute('data-gallery-lightbox-close')) {
                    closeGalleryLightbox();
                    return;
                }
                if (event.target.hasAttribute('data-carousel-dot')) {
                    var carouselRoot = event.target.closest('[data-front-carousel]');
                    if (carouselRoot) {
                        event.preventDefault();
                        event.stopPropagation();
                        updateSimpleCarousel(carouselRoot, clampNumber(event.target.getAttribute('data-carousel-dot') || 0, 0, 0, 999));
                    }
                    return;
                }
                if (event.target.closest('[data-front-gallery-open]')) {
                    var galleryButton = event.target.closest('[data-front-gallery-open]');
                    if (galleryButton) {
                        event.preventDefault();
                        event.stopPropagation();
                        openGalleryLightbox(galleryButton.getAttribute('data-front-gallery-open') || '', galleryButton.getAttribute('data-front-gallery-title') || '');
                    }
                    return;
                }
                if (event.target.hasAttribute('data-panel-reset')) {
                    var role = event.target.getAttribute('data-panel-reset');
                    resetPanelPosition(role);
                    if (role === 'settings' && (state.selectedNode || state.selectedCanvasBlockId)) {
                        ensureSettingsPanelForCurrentSelection();
                    } else if (role === 'library') {
                        showLibraryPanel();
                    }
                    syncEditorStateToPage();
                    queueSaveDraft();
                    return;
                }
                return;
            }
            var blockActionButton = event.target.closest('[data-block-action]');
            if (blockActionButton) {
                var blockAction = blockActionButton.getAttribute('data-block-action') || '';
                var blockPath = blockActionButton.getAttribute('data-block-path') || '';
                var actionNode = queryByPath(blockPath);
            }
            if (state.selectedCanvasBlockId && event.target.closest('#cms_builder_design_canvas')) {
                return;
            }
            if (isFreeLayoutMode()) {
                if (!event.target.closest('#cms_frontend_builder_root') && !event.target.closest('#cms_builder_design_canvas')) {
                    clearSelectionState({closeSettings: true});
                }
                return;
            }
            var node = closestEditableNode(event.target);
            if (!node) {
                return;
            }
            if (listHasRef(currentHiddenPaths(), nodeRef(node))) {
                return;
            }
            event.preventDefault();
            event.stopPropagation();
            setHoveredNode(node);
            selectNode(node, false, false);
        }, true);

        document.addEventListener('mousemove', function (event) {
            if (event.target.closest('[data-hover-toolbar]')) {
                state.hoverToolbarActive = true;
                clearTimeout(state.hoverLeaveTimer);
                return;
            }
            if (state.hoverToolbarActive) {
                state.hoverToolbarActive = false;
            }
            if (event.target.closest('[data-canvas-block-id],#cms_builder_design_canvas')) {
                return;
            }
            if (isFreeLayoutMode()) {
                return;
            }
            if (state.selectedPath) {
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root')) {
                if (event.target.closest('#cms_builder_material_modal,#cms_builder_business_modal,[data-panel-role],#cms_builder_dock')) {
                    return;
                }
                if (state.selectedCanvasBlockId) {
                    return;
                }
                if (!event.target.closest('[data-hover-toolbar],[data-transform-handle],[data-resize-handle],[data-resize-frame]')) {
                    scheduleHoverClear(220);
                }
                return;
            }
            var node = closestEditableNode(event.target);
            if (!node) {
                scheduleHoverClear(220);
                return;
            }
            setHoveredNode(node);
        }, true);

        document.addEventListener('mouseleave', function () {
            if (state.resizingMeta || state.draggingPath || state.draggingNewExtraType || state.mouseDragMeta || state.pendingCanvasDrag || state.pendingBlockDrag) {
                return;
            }
            if (state.selectedCanvasBlockId) {
                return;
            }
            state.hoverToolbarActive = false;
            scheduleHoverClear(120);
        }, true);

        document.addEventListener('keydown', function (event) {
            var target = event.target;
            if (target && target.closest && target.closest('input, textarea, select, [contenteditable="true"]')) {
                return;
            }
            if (!state.selectedCanvasBlockId) {
                return;
            }
            if (event.key === 'Delete' || event.key === 'Backspace') {
                event.preventDefault();
                deleteCanvasBlock(state.selectedCanvasBlockId);
                return;
            }
            var step = event.shiftKey ? 10 : 1;
            var moved = false;
            if (event.key === 'ArrowLeft') {
                moved = nudgeCanvasBlock(state.selectedCanvasBlockId, -step, 0);
            } else if (event.key === 'ArrowRight') {
                moved = nudgeCanvasBlock(state.selectedCanvasBlockId, step, 0);
            } else if (event.key === 'ArrowUp') {
                moved = nudgeCanvasBlock(state.selectedCanvasBlockId, 0, -step);
            } else if (event.key === 'ArrowDown') {
                moved = nudgeCanvasBlock(state.selectedCanvasBlockId, 0, step);
            }
            if (moved) {
                event.preventDefault();
                queueSaveDraft();
                setStatus(event.shiftKey ? '已快速微调画布模块位置（10px）。' : '已微调画布模块位置（1px）。');
            }
        }, true);

        document.addEventListener('focusin', function (event) {
            if (!event.target.closest || !event.target.closest('#cms_frontend_builder_root')) {
                return;
            }
            if (event.target.hasAttribute('data-front-key') && state.selectedPath) {
                beginLiveEdit('front:' + state.selectedPath + ':' + (event.target.getAttribute('data-front-key') || ''));
                return;
            }
            if (event.target.hasAttribute('data-page-key')) {
                beginLiveEdit('page:' + (event.target.getAttribute('data-page-key') || ''));
                return;
            }
            if (event.target.hasAttribute('data-style-part-bg')) {
                beginLiveEdit('stylepart:' + (event.target.getAttribute('data-style-part-bg') || 'layout') + ':background-color');
                return;
            }
            if (event.target.hasAttribute('data-style-part-bg-color')) {
                beginLiveEdit('stylepart:' + (event.target.getAttribute('data-style-part-bg-color') || 'layout') + ':background-color');
                return;
            }
            if (event.target.hasAttribute('data-extra-key')) {
                var activeExtra = selectedExtraConfig();
                if (activeExtra) {
                    beginLiveEdit('extra:' + activeExtra.id + ':' + (event.target.getAttribute('data-extra-key') || ''));
                }
            }
        });

        document.addEventListener('input', function (event) {
            if (state.selectedNode && isProtectedEditableNode(state.selectedNode) && event.target.closest('#cms_frontend_builder_root') && (event.target.hasAttribute('data-front-key') || event.target.hasAttribute('data-front-style-prop') || event.target.hasAttribute('data-front-style-color-prop'))) {
                setStatus(blockProtectedEditMessage());
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && event.target.hasAttribute('data-material-search')) {
                state.materialSearch = event.target.value || '';
                syncEditorStateToPage();
                queueLibrarySearchRender('[data-material-search]');
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && event.target.hasAttribute('data-module-search')) {
                state.moduleSearch = event.target.value || '';
                syncEditorStateToPage();
                queueLibrarySearchRender('[data-module-search]');
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && state.selectedCanvasBlockId && event.target.hasAttribute('data-canvas-geom-key')) {
                var geomKey = event.target.getAttribute('data-canvas-geom-key') || '';
                var geomPatch = {};
                geomPatch[geomKey] = parseInt(event.target.value, 10) || 0;
                if (geomKey === 'w') {
                    geomPatch[geomKey] = Math.max(48, geomPatch[geomKey]);
                }
                if (geomKey === 'h') {
                    geomPatch[geomKey] = Math.max(32, geomPatch[geomKey]);
                }
                if (geomKey === 'z') {
                    geomPatch[geomKey] = Math.max(1, geomPatch[geomKey]);
                }
                updateCanvasBlock(state.selectedCanvasBlockId, geomPatch, true);
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && state.selectedCanvasBlockId && event.target.hasAttribute('data-canvas-meta-key')) {
                var metaKey = event.target.getAttribute('data-canvas-meta-key') || '';
                var metaPatch = {};
                metaPatch[metaKey] = metaKey === 'hidden'
                    ? (parseInt(event.target.value, 10) === 1 ? 1 : 0)
                    : (event.target.value || '');
                updateCanvasBlock(state.selectedCanvasBlockId, metaPatch, true);
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && state.selectedCanvasBlockId && event.target.hasAttribute('data-canvas-content-key')) {
                updateCanvasBlockNested(state.selectedCanvasBlockId, 'content', event.target.getAttribute('data-canvas-content-key') || '', event.target.value || '', true);
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && state.selectedCanvasBlockId && event.target.hasAttribute('data-canvas-style-key')) {
                updateCanvasBlockNested(state.selectedCanvasBlockId, 'style', event.target.getAttribute('data-canvas-style-key') || '', event.target.value || '', true);
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && event.target.hasAttribute('data-front-style-color-prop')) {
                var styleColorProp = event.target.getAttribute('data-front-style-color-prop');
                var styleColorValue = event.target.value || '#ffffff';
                syncSelectedStylePropInputs(styleColorProp, styleColorValue);
                updateSelectedStylePropertyLive(styleColorProp, styleColorValue);
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && event.target.hasAttribute('data-front-style-prop')) {
                var styleProp = event.target.getAttribute('data-front-style-prop');
                var styleValue = event.target.value || '';
                syncSelectedStylePropInputs(styleProp, styleValue);
                updateSelectedStylePropertyLive(styleProp, styleValue);
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && event.target.hasAttribute('data-page-color-key')) {
                var colorKey = event.target.getAttribute('data-page-color-key');
                var colorValue = event.target.value || '#ffffff';
                syncPageColorInputs(colorKey, colorValue);
                if (colorKey === 'background_color') {
                    state.page.background_mode = 'custom';
                }
                updatePageFieldLive(colorKey, colorValue);
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && event.target.hasAttribute('data-style-part-bg-color')) {
                var stylePartColor = event.target.getAttribute('data-style-part-bg-color') || 'layout';
                var stylePartColorValue = event.target.value || '#ffffff';
                syncStylePartBackgroundInputs(stylePartColor, stylePartColorValue);
                updateStylePartBackgroundLive(stylePartColor, stylePartColorValue);
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && event.target.hasAttribute('data-business-edit-field')) {
                syncBusinessEditorField(event.target.getAttribute('data-business-edit-field'), event.target.value || '');
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && event.target.hasAttribute('data-front-key')) {
                if (event.target.getAttribute('data-front-key') === 'style') {
                    var styleTextValue = event.target.value || '';
                    syncInlineStylePresetSelect('image', styleTextValue);
                    syncInlineStylePresetSelect('section', styleTextValue);
                    syncInlineStylePresetSelect('text', styleTextValue);
                }
                updateSelectedNodeLive(event.target.getAttribute('data-front-key'), event.target.value);
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && event.target.hasAttribute('data-page-key')) {
                var pageKey = event.target.getAttribute('data-page-key');
                syncPageFieldInputs(pageKey, event.target.value, event.target);
                if (pageKey === 'background_color') {
                    state.page.background_mode = 'custom';
                    syncPageColorInputs('background_color', event.target.value || '');
                } else if (pageKey === 'footer_background_color' || pageKey === 'footer_text_color') {
                    syncPageColorInputs(pageKey, event.target.value || '');
                } else if (pageKey === 'page_width') {
                    state.page.page_width_mode = 'custom';
                }
                updatePageFieldLive(pageKey, event.target.value);
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && event.target.hasAttribute('data-style-part-bg')) {
                var stylePartKey = event.target.getAttribute('data-style-part-bg') || 'layout';
                syncStylePartBackgroundInputs(stylePartKey, event.target.value || '');
                updateStylePartBackgroundLive(stylePartKey, event.target.value || '');
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && event.target.hasAttribute('data-extra-key')) {
                var extra = selectedExtraConfig();
                if (extra) {
                    updateExtraConfigLive(extra.id, event.target.getAttribute('data-extra-key'), event.target.value);
                }
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && event.target.hasAttribute('data-extra-style-key')) {
                var styleExtra = selectedExtraConfig();
                var styleKey = event.target.getAttribute('data-extra-style-key') || '';
                syncExtraStyleInputs(styleKey, event.target.value || '');
                if (styleExtra) {
                    updateExtraStyleLive(styleExtra.id, styleKey, event.target.value);
                }
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && event.target.hasAttribute('data-extra-color-key')) {
                var colorExtra = selectedExtraConfig();
                var extraColorKey = event.target.getAttribute('data-extra-color-key') || '';
                var extraColorValue = event.target.value || '#ffffff';
                syncExtraColorInputs(extraColorKey, extraColorValue);
                if (colorExtra) {
                    updateExtraConfigLive(colorExtra.id, extraColorKey, extraColorValue);
                }
                return;
            }
            if (event.target.closest('#cms_frontend_builder_root') && event.target.hasAttribute('data-extra-style-color-key')) {
                var styleColorExtra = selectedExtraConfig();
                var styleColorKey = event.target.getAttribute('data-extra-style-color-key') || '';
                var styleColorValue = event.target.value || '#ffffff';
                syncExtraStyleInputs(styleColorKey, styleColorValue);
                if (styleColorExtra) {
                    updateExtraStyleLive(styleColorExtra.id, styleColorKey, styleColorValue);
                }
                return;
            }
        });

        document.addEventListener('copy', function (event) {
            var target = event.target;
            if (!(target instanceof Element)) {
                return;
            }
            if (target.closest('.cms-builder-no-copy') && !target.closest('input, textarea, select')) {
                event.preventDefault();
            }
        }, true);

        document.addEventListener('cut', function (event) {
            var target = event.target;
            if (!(target instanceof Element)) {
                return;
            }
            if (target.closest('.cms-builder-no-copy') && !target.closest('input, textarea, select')) {
                event.preventDefault();
            }
        }, true);

        document.addEventListener('selectstart', function (event) {
            var target = event.target;
            if (!(target instanceof Element)) {
                return;
            }
            if (target.closest('.cms-builder-no-copy') && !target.closest('input, textarea, select')) {
                event.preventDefault();
            }
        }, true);

        document.addEventListener('change', function (event) {
            if (!event.target.closest('#cms_frontend_builder_root')) {
                return;
            }
            if (state.selectedCanvasBlockId && (event.target.hasAttribute('data-canvas-geom-key') || event.target.hasAttribute('data-canvas-meta-key') || event.target.hasAttribute('data-canvas-content-key') || event.target.hasAttribute('data-canvas-style-key'))) {
                queueSaveDraft();
                return;
            }
            if (event.target.hasAttribute('data-style-preset-select')) {
                var presetKind = event.target.getAttribute('data-style-preset-select') || 'text';
                var presetKey = event.target.value || 'custom';
                var preset = findInlineStylePreset(presetKind, presetKey);
                if (preset) {
                    syncSelectedStyleTextarea(preset.value || '');
                    syncInlineStylePresetSelect(presetKind, preset.value || '');
                    updateSelectedNode('style', preset.value || '');
                } else {
                    syncInlineStylePresetSelect(presetKind, overrideValue('style', state.selectedNode ? (state.selectedNode.getAttribute('style') || '') : ''));
                }
                return;
            }
            if (event.target.hasAttribute('data-front-style-color-prop')) {
                var selectedColorProp = event.target.getAttribute('data-front-style-color-prop');
                var selectedColorValue = event.target.value || '#ffffff';
                syncSelectedStylePropInputs(selectedColorProp, selectedColorValue);
                finalizeSelectedNodeEdit();
                return;
            }
            if (event.target.hasAttribute('data-front-style-prop')) {
                syncSelectedStylePropInputs(event.target.getAttribute('data-front-style-prop'), event.target.value || '');
                finalizeSelectedNodeEdit();
                return;
            }
            if (event.target.hasAttribute('data-page-color-key')) {
                syncPageColorInputs(event.target.getAttribute('data-page-color-key'), event.target.value || '#ffffff');
                finalizePageFieldEdit();
                return;
            }
            if (event.target.hasAttribute('data-style-part-bg-color')) {
                syncStylePartBackgroundInputs(event.target.getAttribute('data-style-part-bg-color') || 'layout', event.target.value || '#ffffff');
                finalizeStylePartBackgroundEdit(event.target.getAttribute('data-style-part-bg-color') || 'layout');
                return;
            }
            if (event.target.hasAttribute('data-front-key')) {
                finalizeSelectedNodeEdit();
                return;
            }
            if (event.target.hasAttribute('data-page-key')) {
                finalizePageFieldEdit();
                return;
            }
            if (event.target.hasAttribute('data-style-part-bg')) {
                finalizeStylePartBackgroundEdit(event.target.getAttribute('data-style-part-bg') || 'layout');
                return;
            }
            if (event.target.hasAttribute('data-widget-toggle')) {
                var widgets = normalizeSiteWidgets(state.page.site_widgets);
                widgets[event.target.getAttribute('data-widget-toggle') || ''] = event.target.checked ? 1 : 0;
                applyPageStylePatch({site_widgets: widgets}, '', '页面挂件设置已更新');
                return;
            }
            if (event.target.hasAttribute('data-widget-sync-all')) {
                syncWidgetToAllPages(event.target.getAttribute('data-widget-sync-all') || '');
                return;
            }
            if (event.target.hasAttribute('data-widget-config-key') && event.target.hasAttribute('data-widget-config-field')) {
                updateSiteWidgetConfig(
                    event.target.getAttribute('data-widget-config-key') || '',
                    event.target.getAttribute('data-widget-config-field') || '',
                    event.target.type === 'checkbox' ? (event.target.checked ? 1 : 0) : (event.target.value || '')
                );
                return;
            }
            if (event.target.hasAttribute('data-advanced-toggle')) {
                var advanced = normalizeAdvancedOptions(state.page.advanced_options);
                var advancedKey = event.target.getAttribute('data-advanced-toggle') || '';
                advanced[advancedKey] = event.target.checked ? 1 : 0;
                if (advancedKey === 'mobile_site') {
                    state.page.mobile_enabled = event.target.checked ? 1 : 0;
                }
                applyPageStylePatch({advanced_options: advanced, mobile_enabled: advanced.mobile_site ? 1 : 0}, '', '高级设置已更新');
                return;
            }
            if (event.target.hasAttribute('data-extra-key')) {
                var selectedExtra = selectedExtraConfig();
                if (selectedExtra) {
                    finalizeExtraConfigEdit(selectedExtra.id);
                } else {
                    endLiveEdit();
                }
                return;
            }
            if (event.target.hasAttribute('data-extra-color-key')) {
                var selectedColorExtra = selectedExtraConfig();
                if (selectedColorExtra) {
                    var selectedColorKey = event.target.getAttribute('data-extra-color-key') || '';
                    syncExtraColorInputs(selectedColorKey, event.target.value || '#ffffff');
                    finalizeExtraConfigEdit(selectedColorExtra.id);
                } else {
                    endLiveEdit();
                }
                return;
            }
            if (event.target.hasAttribute('data-extra-style-key')) {
                var selectedStyleExtra = selectedExtraConfig();
                if (selectedStyleExtra) {
                    syncExtraStyleInputs(event.target.getAttribute('data-extra-style-key') || '', event.target.value || '');
                    finalizeExtraConfigEdit(selectedStyleExtra.id);
                } else {
                    endLiveEdit();
                }
                return;
            }
            if (event.target.hasAttribute('data-extra-style-color-key')) {
                var selectedStyleColorExtra = selectedExtraConfig();
                if (selectedStyleColorExtra) {
                    var selectedStyleColorKey = event.target.getAttribute('data-extra-style-color-key') || '';
                    syncExtraStyleInputs(selectedStyleColorKey, event.target.value || '#ffffff');
                    finalizeExtraConfigEdit(selectedStyleColorExtra.id);
                } else {
                    endLiveEdit();
                }
                return;
            }
            if (event.target.hasAttribute('data-column-switch')) {
                var nextUrl = normalizeColumnUrl(event.target.value || '');
                var selectedOption = event.target.options && event.target.selectedIndex >= 0 ? event.target.options[event.target.selectedIndex] : null;
                var editUrl = selectedOption ? String(selectedOption.getAttribute('data-edit-url') || '') : '';
                if (!nextUrl || nextUrl === normalizeColumnUrl(window.location.href)) {
                    state.currentColumnUrl = nextUrl;
                    return;
                }
                state.currentColumnUrl = nextUrl;
                window.location.href = editUrl || builderEditUrl(nextUrl);
                return;
            }
            if (event.target.hasAttribute('data-material-search')) {
                syncEditorStateToPage();
                queueSaveDraft();
            }
            if (event.target.hasAttribute('data-module-search')) {
                syncEditorStateToPage();
                queueSaveDraft();
            }
        });

        document.addEventListener('submit', function (event) {
            var form = event.target.closest('[data-front-guestbook-form]');
            if (form) {
                event.preventDefault();
                var message = form.querySelector('[data-front-guestbook-message]');
                if (message) {
                message.textContent = '正在提交...';
                }
                var payload = {
                    form_type: 'guestbook',
                    name: (form.querySelector('[name="name"]') || {}).value || '',
                    phone: (form.querySelector('[name="phone"]') || {}).value || '',
                    title: (form.querySelector('[name="title"]') || {}).value || '妞ょ敻娼伴悾娆掆枅',
                    content: (form.querySelector('[name="content"]') || {}).value || ''
                };
                postApi('submit_form', payload).then(function (result) {
                    if (message) {
                    message.textContent = result && result.msg ? result.msg : (result && result.code === 200 ? '提交成功' : '提交失败');
                    }
                    if (result && result.code === 200) {
                        form.reset();
                    }
                }).catch(function () {
                    if (message) {
                    message.textContent = '提交失败';
                    }
                });
                return;
            }
            form = event.target.closest('[data-front-vote-form]');
            if (!form) {
                return;
            }
            event.preventDefault();
            var voteMessage = form.querySelector('[data-front-vote-message]');
            if (voteMessage) {
                voteMessage.textContent = '正在提交...';
            }
            var checked = qsa('input:checked', form).map(function (input) {
                return parseInt(input.value, 10);
            }).filter(function (value) {
                return !isNaN(value);
            });
            postApi('submit_form', {
                form_type: 'vote',
                vote_id: parseInt(form.getAttribute('data-vote-id') || '0', 10) || 0,
                options: checked
            }).then(function (result) {
                if (voteMessage) {
                    voteMessage.textContent = result && result.msg ? result.msg : (result && result.code === 200 ? '鎶曠エ鎴愬姛' : '鎶曠エ澶辫触');
                }
                if (result && result.code === 200) {
                    ensureBusinessOptions().then(function () {
                        (state.page.frontend_extra_sections || []).forEach(function (item) {
                            if (item.type === 'vote_form') {
                                rebuildBusinessExtra(item.id);
                            }
                        });
                    });
                }
            }).catch(function () {
                if (voteMessage) {
                    voteMessage.textContent = '鎶曠エ澶辫触';
                }
            });
        }, true);

        document.addEventListener('dragstart', function (event) {
            var moduleButton = event.target.closest('[data-add-extra]');
            if (moduleButton) {
                if (moduleButton.disabled || moduleButton.getAttribute('aria-disabled') === 'true') {
                    return;
                }
                state.draggingNewExtraType = moduleButton.getAttribute('data-add-extra') || '';
                moduleButton.classList.add('is-dragging');
                event.dataTransfer.setData('text/plain', state.draggingNewExtraType);
                renderFloatingOverlays();
                return;
            }
            var button = event.target.closest('[data-drag-path]');
            if (button) {
                if (isFreeLayoutMode()) {
                    event.preventDefault();
                    setStatus('当前已进入自由布局主模式，模板区块排序已停用。');
                    return;
                }
                state.draggingPath = button.getAttribute('data-drag-path') || '';
                button.classList.add('is-dragging');
                event.dataTransfer.setData('text/plain', state.draggingPath);
                renderFloatingOverlays();
                return;
            }
            var block = event.target.closest('[data-builder-block-path]');
            if (block) {
                event.preventDefault();
            }
        }, true);

        document.addEventListener('dragend', function () {
            qsa('.is-dragging').forEach(function (node) {
                node.classList.remove('is-dragging');
            });
            qsa('.cms-builder-canvas-drop').forEach(function (node) {
                node.classList.remove('cms-builder-canvas-drop');
            });
            state.draggingNewExtraType = '';
            state.draggingPath = '';
            renderFloatingOverlays();
        }, true);

        document.addEventListener('dragover', function (event) {
            var designCanvas = event.target.closest('#cms_builder_design_canvas');
            if (designCanvas && state.draggingNewExtraType) {
                event.preventDefault();
                designCanvas.classList.add('cms-builder-canvas-drop');
                return;
            }
            if (isFreeLayoutMode() && !state.draggingNewExtraType) {
                return;
            }
            var button = event.target.closest('[data-drag-path]');
            var block = event.target.closest('[data-builder-block-path]');
            if (!button && !block) {
                return;
            }
            if (!state.draggingPath && !state.draggingNewExtraType) {
                return;
            }
            var candidate=button||block;if(state.draggingPath&&!state.draggingNewExtraType&&!canDropDraggedPath(state.draggingPath,candidate)){if(event.dataTransfer)event.dataTransfer.dropEffect='none';return;}
            event.preventDefault();
            if (button) {
                button.classList.add('cms-builder-canvas-drop');
            }
            if (block) {
                block.classList.add('cms-builder-canvas-drop');
            }
        }, true);

        document.addEventListener('dragleave', function (event) {
            var designCanvas = event.target.closest('#cms_builder_design_canvas');
            if (designCanvas) {
                designCanvas.classList.remove('cms-builder-canvas-drop');
            }
            var button = event.target.closest('[data-drag-path]');
            var block = event.target.closest('[data-builder-block-path]');
            if (button) {
                button.classList.remove('cms-builder-canvas-drop');
            }
            if (block) {
                block.classList.remove('cms-builder-canvas-drop');
            }
        }, true);

        document.addEventListener('drop', function (event) {
            var designCanvas = event.target.closest('#cms_builder_design_canvas');
            if (designCanvas && state.draggingNewExtraType) {
                event.preventDefault();
                addExtraSectionAtCurrentPosition(state.draggingNewExtraType);
                state.draggingNewExtraType = '';
                designCanvas.classList.remove('cms-builder-canvas-drop');
                renderFloatingOverlays();
                return;
            }
            if (isFreeLayoutMode() && !state.draggingNewExtraType) {
                return;
            }
            var button = event.target.closest('[data-drag-path]');
            var block = event.target.closest('[data-builder-block-path]');
            if (!button && !block) {
                return;
            }
            event.preventDefault();
            var targetPath = button ? button.getAttribute('data-drag-path') : getDomPath(block);
            var targetNode = button || block;
            var insertMode = detectDropInsertMode(targetNode, event.clientX, event.clientY);
            var targetKey = targetNode ? nodeStableKey(targetNode) : '';
            if (state.draggingNewExtraType) {
                if (!targetPath) {
                    var fallbackAnchor = defaultExtraAnchorMeta();
                    targetPath = fallbackAnchor.path || '';
                    targetKey = fallbackAnchor.key || '';
                }
                addExtraSection(state.draggingNewExtraType, targetPath, insertMode, null, targetKey);
                state.draggingNewExtraType = '';
                setStatus('新模块已插入页面。');
            } else {
                reorderByDraggedPath(state.draggingPath, targetPath, insertMode);
            }
            state.draggingPath = '';
            qsa('.cms-builder-canvas-drop').forEach(function (node) {
                node.classList.remove('cms-builder-canvas-drop');
            });
            renderFloatingOverlays();
        }, true);

        ensureBusinessOptions().then(function () {
            applyPageState();
            (state.page.frontend_extra_sections || []).forEach(function (item) {
                if (item && (item.type === 'article_list' || item.type === 'product_list' || item.type === 'carousel' || item.type === 'image_text' || item.type === 'multi_image' || item.type === 'gallery' || item.type === 'video_embed' || item.type === 'channel_nav' || item.type === 'friend_links' || item.type === 'service_panel' || item.type === 'member_portal' || item.type === 'vote_form')) {
                    rebuildBusinessExtra(item.id);
                }
            });
        });
    }

    applyPageState();
    initSimpleCarousels(document);
    if (!boot.edit_mode) {
        (function(){var lastDevice=currentDevice(),resizeTimer=null;window.addEventListener('resize',function(){clearTimeout(resizeTimer);resizeTimer=setTimeout(function(){var nextDevice=currentDevice();if(nextDevice===lastDevice)return;lastDevice=nextDevice;applyPageState();initSimpleCarousels(document);},160);});})();
        ensureBusinessOptions().then(function () {
            (state.page.frontend_extra_sections || []).forEach(function (item) {
                if (item && (item.type === 'article_list' || item.type === 'product_list' || item.type === 'carousel' || item.type === 'image_text' || item.type === 'multi_image' || item.type === 'gallery' || item.type === 'video_embed' || item.type === 'channel_nav' || item.type === 'friend_links' || item.type === 'service_panel' || item.type === 'member_portal' || item.type === 'vote_form')) {
                    rebuildBusinessExtra(item.id);
                }
            });
        });
    }
    if (boot.edit_mode) {
        mountEditUi();
        postShellMessage('ready', {
            meta: {
                type: boot.builder_type || 'home',
                ref_id: parseInt(boot.builder_ref_id, 10) || 0,
                page_title: builderTitle()
            }
        });
        notifyShellDeviceState();
        window.addEventListener('message', function (event) {
            var payload = event && event.data ? event.data : null;
            if (!payload || payload.source !== 'cms_builder_shell') {
                return;
            }
            if (payload.type === 'ping') {
                postShellMessage('ready', {
                    meta: {
                        type: boot.builder_type || 'home',
                        ref_id: parseInt(boot.builder_ref_id, 10) || 0,
                        page_title: builderTitle()
                    }
                });
                return;
            }
            if (payload.type === 'select_dom_path') {
                var selected = queryByPath((payload.data || {}).path || '');
                if (selected) {
                    selectNode(selected, true, false);
                }
                return;
            }
            if (payload.type === 'set_preview_device') {
                var requestedDevice=(payload.data||{}).device;var nextDevice=requestedDevice==='mobile'?'mobile':(requestedDevice==='tablet'?'tablet':'pc');
                state.page.device_mode = nextDevice;
                applyPageVisualSettings();
                refreshRenderedState(state.selectedPath);
                syncDeviceButtons();
                notifyShellDeviceState();
                return;
            }
            if (payload.type === 'apply_dom_change') {
                var data = payload.data || {};
                var target = queryByPath(data.path || '');
                if (!target) {
                    return;
                }
                var previousSelected = state.selectedNode;
                state.selectedNode = target;
                state.selectedPath = getDomPath(target);
                ['text', 'html', 'src', 'href', 'style'].forEach(function (key) {
                    if (Object.prototype.hasOwnProperty.call(data, key)) {
                        applySelectedNodeValue(key, data[key]);
                    }
                });
                state.selectedNode = previousSelected === target ? target : state.selectedNode;
                state.selectedPath = state.selectedNode ? getDomPath(state.selectedNode) : '';
                refreshRenderedState(state.selectedPath);
                postShellMessage('node_updated', nodeMeta(target));
            }
        }, false);
    }
})();
</script>
        <?php
        return (string) ob_get_clean();
    }
}
