{% extends 'ewoooc_base.html' %}
{% block title %}PChome 業績成長自動化作戰系統 · EwoooC{% endblock %}

{% block extra_css %}
<style>
    .ai-intel-page {
        display: flex;
        flex-direction: column;
        gap: 18px;
    }

    .ai-intel-hero,
    .growth-executive-strip,
    #growthActionBoard,
    .ops-flow {
        display: none !important;
    }

    .growth-command-pro {
        display: grid;
        gap: 14px;
        width: 100%;
        max-width: 1480px;
        margin: 0 auto;
    }

    .growth-command-pro-head {
        display: flex;
        align-items: flex-start;
        justify-content: space-between;
        gap: 16px;
        border: 1px solid var(--momo-border-subtle);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.92);
        box-shadow: var(--momo-shadow-soft);
        padding: 16px 18px;
    }

    .growth-command-pro-title {
        margin: 0;
        color: var(--momo-text-strong);
        font-family: var(--momo-font-display);
        font-size: 1.3rem;
        font-weight: 900;
        letter-spacing: 0;
    }

    .growth-command-pro-subtitle {
        margin: 5px 0 0;
        color: var(--momo-text-muted);
        font-size: 0.82rem;
        font-weight: 760;
    }

    .growth-command-pro-actions {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: flex-end;
        gap: 8px;
        max-width: 780px;
    }

    .growth-command-status-pill {
        display: inline-flex;
        align-items: center;
        gap: 7px;
        min-height: 34px;
        border: 1px solid rgba(40, 128, 80, 0.22);
        border-radius: 999px;
        background: rgba(232, 247, 238, 0.9);
        color: #216542;
        font-size: 0.78rem;
        font-weight: 900;
        padding: 7px 12px;
    }

    .growth-command-action-group {
        display: inline-flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: flex-end;
        gap: 8px;
    }

    .growth-command-action-group.is-secondary {
        padding-left: 8px;
        border-left: 1px solid rgba(42, 37, 32, 0.12);
    }

    .growth-command-pro .ai-action-btn {
        min-height: 38px;
        border-width: 1px;
        box-shadow: none;
    }

    .growth-command-pro .ai-action-btn.btn-primary {
        background: #2267d5;
        border-color: #2267d5;
        color: #fff;
    }

    .growth-command-pro .ai-action-btn.btn-primary:hover,
    .growth-command-pro .ai-action-btn.btn-primary:focus {
        background: #1d58b9;
        border-color: #1d58b9;
    }

    .growth-command-pro .ai-action-btn.btn-outline-primary,
    .growth-command-pro .ai-action-btn.btn-outline-secondary {
        background: #fff;
        border-color: rgba(71, 82, 97, 0.45);
        color: #3e4a59;
    }

    .growth-command-pro .ai-action-btn.btn-outline-primary:hover,
    .growth-command-pro .ai-action-btn.btn-outline-primary:focus,
    .growth-command-pro .ai-action-btn.btn-outline-secondary:hover,
    .growth-command-pro .ai-action-btn.btn-outline-secondary:focus {
        background: rgba(34, 103, 213, 0.08);
        border-color: rgba(34, 103, 213, 0.55);
        color: #1f4f9f;
    }

    .growth-command-kpi-grid {
        display: grid;
        grid-template-columns: 1.28fr repeat(4, minmax(0, 1fr));
        gap: 10px;
    }

    .growth-command-card {
        min-height: 136px;
        border: 1px solid var(--momo-border-subtle);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.92);
        box-shadow: var(--momo-shadow-soft);
        padding: 13px;
    }

    .growth-command-card.is-clickable {
        cursor: pointer;
        transition: transform 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease;
    }

    .growth-command-card.is-clickable:hover,
    .growth-command-card.is-clickable:focus {
        border-color: rgba(49, 113, 234, 0.28);
        box-shadow: 0 0 0 3px rgba(49, 113, 234, 0.08), var(--momo-shadow-soft);
        outline: none;
        transform: translateY(-1px);
    }

    .growth-command-kpi-label {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 8px;
        color: var(--momo-text-muted);
        font-size: 0.74rem;
        font-weight: 900;
    }

    .growth-command-kpi-label i {
        color: var(--momo-warm-rust);
    }

    .growth-command-kpi-value {
        display: block;
        margin-top: 10px;
        color: var(--momo-text-strong);
        font-family: var(--momo-font-mono);
        font-size: 1.75rem;
        font-weight: 900;
        line-height: 1;
    }

    .growth-command-card.is-sales .growth-command-kpi-value {
        font-size: 2.05rem;
    }

    .growth-command-kpi-note {
        display: block;
        margin-top: 8px;
        color: var(--momo-text-muted);
        font-size: 0.76rem;
        font-weight: 800;
        line-height: 1.35;
    }

    .growth-command-spark {
        display: block;
        width: 100%;
        height: 34px;
        margin-top: 9px;
    }

    .growth-command-progress {
        overflow: hidden;
        height: 8px;
        margin-top: 12px;
        border-radius: 999px;
        background: rgba(42, 37, 32, 0.08);
    }

    .growth-command-progress span {
        display: block;
        width: 0;
        height: 100%;
        border-radius: inherit;
        background: #3bb273;
        transition: width 0.24s ease;
    }

    .growth-command-focus {
        display: grid;
        grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr) minmax(260px, 0.85fr);
        gap: 12px;
    }

    .growth-command-panel {
        border: 1px solid var(--momo-border-subtle);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.92);
        box-shadow: var(--momo-shadow-soft);
        padding: 14px;
    }

    .growth-command-panel-head {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 10px;
        margin-bottom: 10px;
    }

    .growth-command-panel-title {
        margin: 0;
        color: var(--momo-text-strong);
        font-size: 0.92rem;
        font-weight: 900;
    }

    .growth-command-panel-link {
        border: 0;
        background: transparent;
        color: #3171ea;
        font-size: 0.74rem;
        font-weight: 900;
        padding: 0;
    }

    .growth-command-table {
        width: 100%;
        border-collapse: collapse;
    }

    .growth-command-table td {
        border-top: 1px solid rgba(42, 37, 32, 0.08);
        padding: 9px 4px;
        vertical-align: middle;
    }

    .growth-command-product {
        display: block;
        max-width: 280px;
        overflow: hidden;
        color: var(--momo-text-strong);
        font-size: 0.82rem;
        font-weight: 900;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .growth-command-meta {
        display: block;
        margin-top: 3px;
        color: var(--momo-text-muted);
        font-size: 0.72rem;
        font-weight: 780;
    }

    .growth-command-badge {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 44px;
        border-radius: 999px;
        background: rgba(255, 239, 232, 0.88);
        color: #b94f3a;
        font-size: 0.72rem;
        font-weight: 900;
        padding: 5px 8px;
    }

    .growth-command-donut-wrap {
        display: grid;
        grid-template-columns: 168px minmax(0, 1fr);
        gap: 12px;
        align-items: center;
    }

    .growth-command-donut {
        --ready: 0;
        --review: 0;
        width: 152px;
        aspect-ratio: 1;
        border-radius: 50%;
        background:
            conic-gradient(#3bb273 0 calc(var(--ready) * 1%), #f2b25a 0 calc((var(--ready) + var(--review)) * 1%), #d9d5cc 0 100%);
        display: grid;
        place-items: center;
    }

    .growth-command-donut strong {
        display: grid;
        place-items: center;
        width: 92px;
        aspect-ratio: 1;
        border-radius: 50%;
        background: #fff;
        color: var(--momo-text-strong);
        font-family: var(--momo-font-mono);
        font-size: 1.35rem;
        font-weight: 900;
        text-align: center;
        line-height: 1.1;
    }

    .growth-command-legend {
        display: grid;
        gap: 8px;
    }

    .growth-command-legend-row {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr) auto;
        gap: 8px;
        align-items: center;
        color: var(--momo-text-muted);
        font-size: 0.78rem;
        font-weight: 850;
    }

    .growth-command-dot {
        width: 9px;
        height: 9px;
        border-radius: 50%;
        background: #d9d5cc;
    }

    .growth-command-dot.is-ready { background: #3bb273; }
    .growth-command-dot.is-review { background: #f2b25a; }

    .growth-command-mini-grid {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 9px;
    }

    .growth-command-mini-card {
        min-height: 88px;
        border: 1px solid rgba(42, 37, 32, 0.08);
        border-radius: 8px;
        background: rgba(250, 247, 240, 0.58);
        padding: 10px;
    }

    .growth-command-mini-card span {
        color: var(--momo-text-muted);
        font-size: 0.72rem;
        font-weight: 900;
    }

    .growth-command-mini-card strong {
        display: block;
        margin-top: 8px;
        color: var(--momo-text-strong);
        font-family: var(--momo-font-mono);
        font-size: 1.45rem;
        font-weight: 900;
        line-height: 1;
    }

    .growth-command-mini-card em {
        display: block;
        margin-top: 7px;
        color: var(--momo-text-muted);
        font-size: 0.72rem;
        font-style: normal;
        font-weight: 780;
    }

    .growth-command-alert {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr) auto;
        gap: 12px;
        align-items: center;
        justify-self: start;
        width: min(100%, 980px);
        min-height: 72px;
        border: 1px solid rgba(42, 37, 32, 0.14);
        border-left: 4px solid #3171ea;
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.94);
        box-shadow: var(--momo-shadow-soft);
        padding: 12px 14px;
    }

    .growth-command-alert i {
        display: inline-grid;
        place-items: center;
        width: 34px;
        height: 34px;
        border: 1px solid rgba(49, 113, 234, 0.18);
        border-radius: 50%;
        background: rgba(235, 243, 255, 0.95);
        color: #3171ea;
    }

    .growth-command-alert-copy {
        min-width: 0;
    }

    .growth-command-alert strong {
        color: var(--momo-text-strong);
        font-size: 0.92rem;
        font-weight: 900;
        line-height: 1.25;
    }

    .growth-command-alert span {
        display: block;
        max-width: 66ch;
        margin-top: 3px;
        color: var(--momo-text-muted);
        font-size: 0.76rem;
        font-weight: 780;
        line-height: 1.45;
    }

    #commandTaskButton.growth-command-alert-action {
        min-width: 108px;
        justify-self: end;
        border-color: #8f442b !important;
        background-color: #8f442b !important;
        background-image: none !important;
        color: #fff !important;
        box-shadow: 0 8px 18px rgba(143, 68, 43, 0.18) !important;
    }

    #commandTaskButton.growth-command-alert-action:hover,
    #commandTaskButton.growth-command-alert-action:focus {
        border-color: #743620 !important;
        background-color: #743620 !important;
        color: #fff !important;
    }

    .ai-intel-hero {
        position: relative;
        overflow: hidden;
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        gap: 18px;
        align-items: center;
        padding: 22px;
        border: 1px solid var(--momo-border-strong);
        border-radius: 8px;
        background:
            radial-gradient(circle at 18px 18px, rgba(42, 37, 32, 0.12) 1px, transparent 1px),
            linear-gradient(135deg, rgba(242, 178, 90, 0.28), rgba(255, 255, 255, 0.92) 42%, rgba(172, 92, 58, 0.12));
        background-size: 18px 18px, auto;
        box-shadow: var(--momo-shadow-soft);
    }

    .ai-intel-legacy-status {
        display: none;
    }

    .ai-intel-hero::after {
        content: "";
        position: absolute;
        inset: auto 20px 18px auto;
        width: 132px;
        height: 132px;
        border: 1px solid rgba(42, 37, 32, 0.12);
        border-radius: 50%;
        background: repeating-linear-gradient(
            90deg,
            rgba(42, 37, 32, 0.08) 0,
            rgba(42, 37, 32, 0.08) 1px,
            transparent 1px,
            transparent 8px
        );
        opacity: 0.72;
        pointer-events: none;
    }

    .ai-intel-title {
        position: relative;
        z-index: 1;
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
        align-items: center;
        margin: 0;
        color: var(--momo-text-strong);
        font-family: var(--momo-font-display);
        font-size: clamp(1.45rem, 2vw, 2.15rem);
        font-weight: 800;
        letter-spacing: 0;
    }

    .ai-intel-title i {
        color: var(--momo-warm-rust);
    }

    .ai-intel-badge,
    .ai-status-badge {
        display: inline-flex;
        align-items: center;
        gap: 6px;
        border: 1px solid rgba(42, 37, 32, 0.14);
        border-radius: 999px;
        background: rgba(255, 255, 255, 0.68);
        color: var(--momo-text-strong);
        font-family: var(--momo-font-mono);
        font-size: 0.78rem;
        font-weight: 800;
        padding: 5px 10px;
    }

    .ai-status-badge.is-success {
        border-color: rgba(40, 128, 80, 0.24);
        background: rgba(232, 247, 238, 0.88);
        color: #216542;
    }

    .ai-status-badge.is-error {
        border-color: rgba(188, 75, 49, 0.26);
        background: rgba(255, 241, 237, 0.9);
        color: #9b3d2b;
    }

    .ai-intel-subtitle {
        position: relative;
        z-index: 1;
        margin: 8px 0 0;
        color: var(--momo-text-muted);
        font-size: 0.93rem;
    }

    .ai-intel-actions {
        position: relative;
        z-index: 1;
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-end;
        gap: 8px;
        max-width: 620px;
    }

    .ai-action-btn {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: 7px;
        min-height: 38px;
        border-radius: 8px;
        font-weight: 800;
        white-space: nowrap;
    }

    .ai-action-btn.btn-outline-danger {
        color: var(--momo-warm-rust);
        border-color: rgba(172, 92, 58, 0.44);
    }

    .ai-action-btn.btn-outline-primary {
        color: var(--momo-accent-strong);
        border-color: rgba(42, 37, 32, 0.24);
    }

    .ai-action-btn.btn-outline-warning {
        color: #805313;
        border-color: rgba(242, 178, 90, 0.66);
    }

    .ai-intel-page #kpiRow .card,
    .ai-panel {
        border: 1px solid var(--momo-border-subtle) !important;
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.84);
        box-shadow: var(--momo-shadow-soft);
    }

    .ai-intel-page #kpiRow .card-body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        min-height: 116px;
    }

    .ai-intel-page #kpiRow .fs-2 {
        color: var(--momo-text-strong) !important;
        font-family: var(--momo-font-mono);
        font-size: 2rem !important;
        line-height: 1.05;
    }

    .ai-intel-page #kpiRow .small {
        color: var(--momo-text-muted) !important;
        font-weight: 700;
    }

    .ai-intel-page #kpiHighRiskCard.border-danger {
        background: linear-gradient(160deg, rgba(255, 245, 240, 0.98), rgba(255, 255, 255, 0.9));
        border-color: rgba(188, 75, 49, 0.48) !important;
    }

    .ai-panel .card-header,
    .ai-panel .card-footer {
        border-color: var(--momo-border-subtle) !important;
        background: rgba(255, 255, 255, 0.78) !important;
    }

    .ai-panel-title {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        color: var(--momo-text-strong);
        font-family: var(--momo-font-display);
        font-size: 0.95rem;
        font-weight: 800;
    }

    .ai-panel-title i {
        color: var(--momo-warm-caramel) !important;
    }

    .ai-panel .form-select,
    .ai-panel .form-control {
        border-color: var(--momo-border-subtle);
        border-radius: 8px;
        color: var(--momo-text-strong);
        font-size: 0.82rem;
    }

    .ai-legend {
        border-bottom: 1px solid var(--momo-border-subtle);
    }

    .ai-table-scroll {
        overflow-x: auto;
        overflow-y: auto;
        max-height: 520px;
    }

    .ai-intel-page .table {
        --bs-table-hover-bg: rgba(242, 178, 90, 0.12);
        color: var(--momo-text-strong);
    }

    .ai-intel-page .table thead th {
        border-bottom: 1px solid var(--momo-border-strong);
        background: rgba(250, 247, 240, 0.96) !important;
        color: var(--momo-text-muted);
        font-size: 0.76rem;
        font-weight: 800;
    }

    .ai-intel-page .table tbody td {
        border-color: rgba(42, 37, 32, 0.08);
    }

    .ai-recs-scroll {
        overflow-y: auto;
        max-height: 568px;
    }

    .ai-intel-page #aiRecsList > .border {
        border-color: var(--momo-border-subtle) !important;
        border-radius: 8px !important;
        background: rgba(250, 247, 240, 0.54);
    }

    .growth-ops-grid {
        display: grid;
        grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.7fr);
        gap: 14px;
    }

    .growth-executive-strip {
        display: grid;
        grid-template-columns: minmax(0, 1.25fr) repeat(3, minmax(0, 0.75fr));
        gap: 10px;
    }

    .growth-exec-card {
        min-height: 118px;
        border: 1px solid var(--momo-border-subtle);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.82);
        box-shadow: var(--momo-shadow-soft);
        padding: 13px;
    }

    .growth-exec-card.is-primary {
        border-color: rgba(172, 92, 58, 0.22);
        background: rgba(242, 178, 90, 0.15);
    }

    .growth-exec-card.is-ready {
        border-color: rgba(42, 134, 96, 0.24);
        background: rgba(235, 248, 241, 0.76);
    }

    .growth-exec-card.is-gap {
        border-color: rgba(188, 78, 67, 0.22);
        background: rgba(255, 244, 239, 0.78);
    }

    .growth-exec-label {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 10px;
        color: var(--momo-text-muted);
        font-size: 0.74rem;
        font-weight: 900;
    }

    .growth-exec-label i {
        color: var(--momo-warm-rust);
    }

    .growth-exec-value {
        margin-top: 11px;
        color: var(--momo-text-strong);
        font-family: var(--momo-font-display);
        font-size: 1.05rem;
        font-weight: 900;
        line-height: 1.28;
    }

    .growth-exec-card:not(.is-primary) .growth-exec-value {
        font-family: var(--momo-font-mono);
        font-size: 1.72rem;
        line-height: 1;
    }

    .growth-exec-card.is-clickable,
    .ops-dashboard-tile.is-clickable,
    .growth-metric.is-clickable,
    #kpiRow .card.is-clickable {
        cursor: pointer;
        transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
    }

    .growth-exec-card.is-clickable:hover,
    .growth-exec-card.is-clickable:focus,
    .ops-dashboard-tile.is-clickable:hover,
    .ops-dashboard-tile.is-clickable:focus,
    .growth-metric.is-clickable:hover,
    .growth-metric.is-clickable:focus,
    #kpiRow .card.is-clickable:hover,
    #kpiRow .card.is-clickable:focus {
        border-color: rgba(172, 92, 58, 0.34);
        box-shadow: 0 0 0 3px rgba(172, 92, 58, 0.1), var(--momo-shadow-soft);
        outline: none;
        transform: translateY(-1px);
    }

    .growth-exec-detail {
        margin-top: 7px;
        color: var(--momo-text-muted);
        font-size: 0.76rem;
        font-weight: 760;
        line-height: 1.4;
    }

    .drilldown-hint {
        display: inline-flex;
        align-items: center;
        gap: 4px;
        margin-left: 6px;
        color: var(--momo-warm-rust);
        font-size: 0.7rem;
        font-weight: 900;
        white-space: nowrap;
    }

    .ops-flow {
        border: 1px solid var(--momo-border-subtle);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.82);
        box-shadow: var(--momo-shadow-soft);
        padding: 14px;
    }

    .ops-flow-head {
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
        gap: 8px;
        align-items: center;
        margin-bottom: 10px;
    }

    .ops-flow-note {
        color: var(--momo-text-muted);
        font-size: 0.78rem;
        font-weight: 800;
    }

    .next-action-banner {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr) auto;
        gap: 12px;
        align-items: center;
        margin-bottom: 12px;
        border: 1px solid rgba(172, 92, 58, 0.2);
        border-radius: 8px;
        background: rgba(242, 178, 90, 0.16);
        padding: 12px;
    }

    .next-action-icon {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 34px;
        height: 34px;
        border-radius: 999px;
        background: rgba(172, 92, 58, 0.14);
        color: var(--momo-warm-rust);
    }

    .next-action-title {
        display: block;
        color: var(--momo-text-strong);
        font-size: 0.98rem;
        font-weight: 900;
        line-height: 1.25;
    }

    .next-action-reason {
        display: block;
        margin-top: 2px;
        color: var(--momo-text-muted);
        font-size: 0.78rem;
        font-weight: 800;
        line-height: 1.35;
    }

    .ops-flow-grid {
        display: grid;
        grid-template-columns: minmax(0, 1.12fr) minmax(0, 1fr) minmax(260px, 0.82fr);
        gap: 12px;
    }

    .ops-dashboard-tile {
        border: 1px solid rgba(42, 37, 32, 0.1);
        border-radius: 8px;
        background: rgba(250, 247, 240, 0.58);
        padding: 12px;
        min-height: 168px;
    }

    .ops-dashboard-title {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 8px;
        margin: 0 0 10px;
        color: var(--momo-text-muted);
        font-size: 0.76rem;
        font-weight: 900;
    }

    .ops-dashboard-value {
        color: var(--momo-text-strong);
        font-family: var(--momo-font-mono);
        font-size: 1.72rem;
        font-weight: 900;
        line-height: 1;
    }

    .ops-flow-item {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        gap: 10px;
        align-items: start;
        width: 100%;
        border: 1px solid rgba(42, 37, 32, 0.1);
        border-radius: 8px;
        background: rgba(250, 247, 240, 0.52);
        color: var(--momo-text-strong);
        padding: 11px;
        text-align: left;
        transition: transform 0.16s ease, border-color 0.16s ease, background-color 0.16s ease;
    }

    .ops-action-grid {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 8px;
    }

    .ops-action-grid .ops-flow-item {
        grid-template-columns: auto minmax(0, 1fr);
        min-height: 70px;
        padding: 10px;
    }

    .ops-funnel,
    .ops-source-bars,
    .price-risk-bars {
        display: grid;
        gap: 9px;
    }

    .ops-funnel-row,
    .ops-source-row,
    .price-risk-row {
        display: grid;
        gap: 5px;
    }

    .price-risk-row[role="button"] {
        border-radius: 8px;
        cursor: pointer;
        padding: 6px;
        transition: background-color 0.16s ease, transform 0.16s ease;
    }

    .price-risk-row[role="button"]:hover,
    .price-risk-row[role="button"]:focus {
        background: rgba(255, 255, 255, 0.82);
        outline: 0;
        transform: translateY(-1px);
    }

    .ops-funnel-meta,
    .ops-source-meta,
    .price-risk-meta {
        display: flex;
        justify-content: space-between;
        gap: 10px;
        color: var(--momo-text-muted);
        font-size: 0.74rem;
        font-weight: 900;
    }

    .ops-funnel-track,
    .ops-source-track,
    .price-risk-track {
        overflow: hidden;
        height: 8px;
        border-radius: 999px;
        background: rgba(42, 37, 32, 0.08);
    }

    .ops-funnel-bar,
    .ops-source-bar,
    .price-risk-bar {
        display: block;
        width: 0;
        height: 100%;
        border-radius: inherit;
        background: var(--momo-warm-caramel);
        transition: width 0.28s ease;
    }

    .ops-funnel-bar.is-ready,
    .price-risk-bar.is-low {
        background: #2f8f66;
    }

    .ops-funnel-bar.is-gap,
    .price-risk-bar.is-high {
        background: #b94f3a;
    }

    .ops-source-bar.is-secondary,
    .price-risk-bar.is-medium {
        background: #d8a13a;
    }

    .ops-flow-item:hover,
    .ops-flow-item:focus {
        border-color: rgba(172, 92, 58, 0.34);
        background: rgba(255, 255, 255, 0.86);
        transform: translateY(-1px);
    }

    .ops-flow-step {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 30px;
        height: 30px;
        border-radius: 999px;
        background: rgba(172, 92, 58, 0.12);
        color: var(--momo-warm-rust);
        font-family: var(--momo-font-mono);
        font-size: 0.76rem;
        font-weight: 900;
    }

    .ops-flow-title {
        margin: 0;
        color: var(--momo-text-strong);
        font-size: 0.86rem;
        font-weight: 900;
        line-height: 1.25;
    }

    .ops-flow-copy,
    .ops-flow-target {
        margin: 4px 0 0;
        color: var(--momo-text-muted);
        font-size: 0.74rem;
        line-height: 1.4;
    }

    .ops-flow-target {
        color: var(--momo-warm-rust);
        font-weight: 900;
    }

    .growth-metric-row {
        display: grid;
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: 8px;
    }

    .growth-metric {
        border: 1px solid var(--momo-border-subtle);
        border-radius: 8px;
        background: rgba(250, 247, 240, 0.62);
        padding: 10px;
    }

    .growth-metric strong {
        display: block;
        color: var(--momo-text-strong);
        font-family: var(--momo-font-mono);
        font-size: 1.35rem;
        line-height: 1.1;
    }

    .growth-metric span {
        color: var(--momo-text-muted);
        font-size: 0.75rem;
        font-weight: 800;
    }

    .growth-source-note {
        margin-top: 10px;
        color: var(--momo-text-muted);
        font-size: 0.82rem;
        line-height: 1.55;
    }

    .growth-source-note.is-compact {
        margin-top: 8px;
        font-weight: 800;
        line-height: 1.35;
    }

    .growth-action-hint {
        margin: 10px 0 0;
        border: 1px solid rgba(172, 92, 58, 0.16);
        border-radius: 8px;
        background: rgba(242, 178, 90, 0.14);
        color: var(--momo-text-strong);
        font-size: 0.82rem;
        font-weight: 800;
        line-height: 1.5;
        padding: 9px 10px;
    }

    .growth-data-summary {
        margin: 8px 0 0;
        border: 1px solid rgba(42, 37, 32, 0.08);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.68);
        color: var(--momo-text-muted);
        font-size: 0.78rem;
        line-height: 1.45;
        padding: 8px 10px;
    }

    .growth-detail-panel {
        margin-top: 12px;
    }

    .growth-detail-toolbar {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: space-between;
        gap: 10px;
        margin-bottom: 10px;
    }

    .growth-detail-title {
        margin: 0;
        color: var(--momo-text-strong);
        font-size: 0.95rem;
        font-weight: 900;
        line-height: 1.3;
    }

    .growth-detail-meta {
        color: var(--momo-text-muted);
        font-size: 0.76rem;
        font-weight: 800;
    }

    .growth-detail-tabs {
        display: flex;
        flex-wrap: wrap;
        gap: 7px;
    }

    .growth-detail-tab {
        border: 1px solid rgba(42, 37, 32, 0.12);
        border-radius: 999px;
        background: rgba(250, 247, 240, 0.66);
        color: var(--momo-text-muted);
        font-size: 0.74rem;
        font-weight: 900;
        padding: 6px 10px;
    }

    .growth-detail-tab.is-active {
        border-color: rgba(172, 92, 58, 0.34);
        background: rgba(242, 178, 90, 0.18);
        color: var(--momo-text-strong);
    }

    .growth-strategy-grid {
        display: grid;
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: 10px;
        margin-bottom: 10px;
    }

    .growth-strategy-card {
        display: grid;
        gap: 6px;
        min-height: 104px;
        border: 1px solid rgba(42, 37, 32, 0.1);
        border-radius: 8px;
        background: rgba(250, 247, 240, 0.7);
        color: var(--momo-text-strong);
        padding: 10px;
        text-align: left;
        transition: border-color 0.16s ease, box-shadow 0.16s ease, transform 0.16s ease;
    }

    .growth-strategy-card:hover,
    .growth-strategy-card:focus,
    .growth-strategy-card.is-active {
        border-color: rgba(172, 92, 58, 0.36);
        box-shadow: 0 0 0 3px rgba(172, 92, 58, 0.1), var(--momo-shadow-soft);
        outline: 0;
        transform: translateY(-1px);
    }

    .growth-strategy-card.is-risk {
        background: rgba(255, 244, 239, 0.9);
        border-color: rgba(185, 79, 58, 0.22);
    }

    .growth-strategy-card.is-advantage {
        background: rgba(238, 249, 241, 0.92);
        border-color: rgba(47, 143, 102, 0.22);
    }

    .growth-strategy-card.is-review {
        background: rgba(255, 249, 236, 0.92);
        border-color: rgba(216, 161, 58, 0.24);
    }

    .growth-strategy-card.is-needs {
        background: rgba(250, 247, 240, 0.92);
        border-color: rgba(42, 37, 32, 0.12);
    }

    .growth-strategy-label {
        color: var(--momo-text-muted);
        font-size: 0.72rem;
        font-weight: 900;
    }

    .growth-strategy-value {
        color: var(--momo-text-strong);
        font-family: var(--momo-font-mono);
        font-size: 1.55rem;
        font-weight: 900;
        line-height: 1;
    }

    .growth-strategy-note {
        color: var(--momo-text-muted);
        font-size: 0.72rem;
        font-weight: 760;
        line-height: 1.35;
    }

    .growth-strategy-track {
        overflow: hidden;
        height: 6px;
        border-radius: 999px;
        background: rgba(42, 37, 32, 0.08);
    }

    .growth-strategy-bar {
        display: block;
        width: 0;
        height: 100%;
        border-radius: inherit;
        background: var(--momo-warm-caramel);
    }

    .growth-strategy-card.is-risk .growth-strategy-bar {
        background: #b94f3a;
    }

    .growth-strategy-card.is-advantage .growth-strategy-bar {
        background: #2f8f66;
    }

    .growth-strategy-card.is-review .growth-strategy-bar {
        background: #d8a13a;
    }

    .growth-category-board {
        border: 1px solid rgba(42, 37, 32, 0.1);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.68);
        margin-bottom: 10px;
        padding: 10px;
    }

    .growth-category-head {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 10px;
        margin-bottom: 8px;
    }

    .growth-category-title {
        color: var(--momo-text-strong);
        font-size: 0.84rem;
        font-weight: 950;
        margin: 0;
    }

    .growth-category-note {
        color: var(--momo-text-muted);
        font-size: 0.68rem;
        font-weight: 820;
        text-align: right;
    }

    .growth-category-list {
        display: grid;
        gap: 7px;
    }

    .growth-category-row {
        border: 1px solid rgba(42, 37, 32, 0.08);
        border-radius: 8px;
        background: rgba(250, 247, 240, 0.58);
        color: var(--momo-text-strong);
        display: grid;
        grid-template-columns: minmax(160px, 1fr) minmax(260px, 1.25fr) minmax(88px, auto);
        gap: 10px;
        align-items: center;
        padding: 9px 10px;
        text-align: left;
        width: 100%;
    }

    .growth-category-row:hover,
    .growth-category-row:focus {
        border-color: rgba(172, 92, 58, 0.3);
        background: rgba(255, 248, 232, 0.8);
        outline: 0;
    }

    .growth-category-row.is-active {
        border-color: rgba(172, 92, 58, 0.38);
        background: rgba(255, 248, 232, 0.9);
        box-shadow: inset 3px 0 0 rgba(172, 92, 58, 0.48);
    }

    .growth-category-name {
        display: block;
        font-size: 0.8rem;
        font-weight: 950;
        line-height: 1.25;
    }

    .growth-category-meta,
    .growth-category-action {
        color: var(--momo-text-muted);
        display: block;
        font-size: 0.68rem;
        font-weight: 850;
        line-height: 1.3;
        margin-top: 3px;
    }

    .growth-category-bars {
        display: grid;
        gap: 5px;
    }

    .growth-category-track {
        background: rgba(42, 37, 32, 0.08);
        border-radius: 999px;
        height: 7px;
        overflow: hidden;
    }

    .growth-category-bar {
        background: #ac5c3a;
        border-radius: inherit;
        display: block;
        height: 100%;
    }

    .growth-category-stats {
        color: var(--momo-text-muted);
        display: flex;
        flex-wrap: wrap;
        gap: 5px;
        font-size: 0.66rem;
        font-weight: 850;
        line-height: 1.25;
    }

    .growth-category-stats strong {
        color: var(--momo-text-strong);
        font-family: var(--momo-font-mono);
    }

    .growth-category-cta {
        color: #8f4d33;
        font-size: 0.72rem;
        font-weight: 950;
        text-align: right;
    }

    .growth-playbook-board {
        border: 1px solid rgba(42, 37, 32, 0.1);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.7);
        margin-bottom: 10px;
        padding: 10px;
    }

    .growth-playbook-head {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 10px;
        margin-bottom: 8px;
    }

    .growth-playbook-title {
        color: var(--momo-text-strong);
        font-size: 0.84rem;
        font-weight: 950;
        margin: 0;
    }

    .growth-playbook-note {
        color: var(--momo-text-muted);
        font-size: 0.68rem;
        font-weight: 820;
        text-align: right;
    }

    .growth-playbook-grid {
        display: grid;
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: 8px;
    }

    .growth-playbook-card {
        border: 1px solid rgba(42, 37, 32, 0.09);
        border-radius: 8px;
        background: rgba(250, 247, 240, 0.58);
        color: var(--momo-text-strong);
        display: grid;
        gap: 7px;
        min-height: 142px;
        padding: 10px;
        text-align: left;
        width: 100%;
    }

    .growth-playbook-card:hover,
    .growth-playbook-card:focus,
    .growth-playbook-card.is-active {
        border-color: rgba(172, 92, 58, 0.3);
        background: rgba(255, 248, 232, 0.82);
        outline: 0;
    }

    .growth-playbook-card.is-defense {
        border-color: rgba(185, 79, 58, 0.2);
    }

    .growth-playbook-card.is-boost {
        border-color: rgba(47, 143, 102, 0.2);
    }

    .growth-playbook-card.is-bundle {
        border-color: rgba(216, 161, 58, 0.22);
    }

    .growth-playbook-card.is-data {
        border-color: rgba(92, 111, 135, 0.2);
    }

    .growth-playbook-label {
        color: var(--momo-text-muted);
        font-size: 0.68rem;
        font-weight: 950;
        line-height: 1.2;
    }

    .growth-playbook-value {
        color: var(--momo-text-strong);
        font-family: var(--momo-font-mono);
        font-size: 1.22rem;
        font-weight: 950;
        line-height: 1;
    }

    .growth-playbook-sales,
    .growth-playbook-product,
    .growth-playbook-action {
        color: var(--momo-text-muted);
        font-size: 0.68rem;
        font-weight: 820;
        line-height: 1.35;
    }

    .growth-playbook-action {
        color: #8f4d33;
        font-weight: 950;
    }

    .growth-playbook-track {
        background: rgba(42, 37, 32, 0.08);
        border-radius: 999px;
        height: 7px;
        overflow: hidden;
    }

    .growth-playbook-bar {
        background: #ac5c3a;
        border-radius: inherit;
        display: block;
        height: 100%;
    }

    .growth-action-board {
        border: 1px solid rgba(42, 37, 32, 0.1);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.72);
        margin-bottom: 10px;
        padding: 10px;
    }

    .growth-action-board-head {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 10px;
        margin-bottom: 8px;
    }

    .growth-action-board-title {
        color: var(--momo-text-strong);
        font-size: 0.84rem;
        font-weight: 950;
        margin: 0;
    }

    .growth-action-board-note {
        color: var(--momo-text-muted);
        font-size: 0.68rem;
        font-weight: 820;
        text-align: right;
    }

    .growth-action-list {
        display: grid;
        gap: 7px;
    }

    .growth-action-row {
        border: 1px solid rgba(42, 37, 32, 0.08);
        border-radius: 8px;
        background: rgba(250, 247, 240, 0.58);
        display: grid;
        grid-template-columns: auto minmax(0, 1.1fr) minmax(170px, 0.55fr) auto;
        gap: 10px;
        align-items: center;
        padding: 9px 10px;
    }

    .growth-action-rank {
        align-items: center;
        background: rgba(172, 92, 58, 0.12);
        border-radius: 999px;
        color: #8f4d33;
        display: inline-flex;
        font-family: var(--momo-font-mono);
        font-size: 0.72rem;
        font-weight: 950;
        height: 34px;
        justify-content: center;
        width: 34px;
    }

    .growth-action-title {
        color: var(--momo-text-strong);
        font-size: 0.8rem;
        font-weight: 950;
        line-height: 1.3;
        margin: 0;
    }

    .growth-action-meta,
    .growth-action-reason {
        color: var(--momo-text-muted);
        font-size: 0.68rem;
        font-weight: 820;
        line-height: 1.35;
        margin: 3px 0 0;
    }

    .growth-action-pill {
        border: 1px solid rgba(172, 92, 58, 0.18);
        border-radius: 999px;
        color: #8f4d33;
        display: inline-flex;
        font-size: 0.68rem;
        font-weight: 950;
        justify-content: center;
        padding: 4px 8px;
        white-space: nowrap;
    }

    .growth-action-buttons {
        display: flex;
        flex-wrap: wrap;
        gap: 6px;
        justify-content: flex-end;
    }

    .growth-action-evidence {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 5px;
        margin-top: 7px;
    }

    .growth-action-evidence-chip {
        border: 1px solid rgba(42, 37, 32, 0.08);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.64);
        min-width: 0;
        padding: 5px 6px;
    }

    .growth-action-evidence-label {
        color: var(--momo-text-muted);
        display: block;
        font-size: 0.6rem;
        font-weight: 950;
        line-height: 1.2;
    }

    .growth-action-evidence-value {
        color: var(--momo-text-strong);
        display: block;
        font-family: var(--momo-font-mono);
        font-size: 0.7rem;
        font-weight: 950;
        line-height: 1.25;
        margin-top: 2px;
        overflow-wrap: anywhere;
    }

    .growth-decision-panel {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        gap: 12px;
        align-items: center;
        border: 1px solid rgba(172, 92, 58, 0.18);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.72);
        margin-bottom: 10px;
        padding: 10px 12px;
    }

    .growth-decision-title {
        margin: 0;
        color: var(--momo-text-strong);
        font-size: 0.86rem;
        font-weight: 900;
        line-height: 1.3;
    }

    .growth-decision-copy {
        margin: 4px 0 0;
        color: var(--momo-text-muted);
        font-size: 0.74rem;
        font-weight: 760;
        line-height: 1.4;
    }

    .growth-decision-metrics {
        display: flex;
        flex-wrap: wrap;
        gap: 6px;
        margin-top: 8px;
    }

    .growth-decision-metric {
        border: 1px solid rgba(42, 37, 32, 0.08);
        border-radius: 999px;
        background: rgba(250, 247, 240, 0.72);
        color: var(--momo-text-muted);
        font-size: 0.68rem;
        font-weight: 900;
        padding: 4px 8px;
    }

    .growth-decision-metric strong {
        color: var(--momo-text-strong);
        font-family: var(--momo-font-mono);
    }

    .growth-detail-controls {
        display: grid;
        grid-template-columns: minmax(180px, 1fr) minmax(150px, 0.42fr) auto;
        gap: 8px;
        align-items: end;
        margin-bottom: 10px;
    }

    .growth-detail-field {
        display: grid;
        gap: 5px;
    }

    .growth-detail-field span {
        color: var(--momo-text-muted);
        font-size: 0.68rem;
        font-weight: 900;
    }

    .growth-detail-control {
        width: 100%;
        border: 1px solid rgba(42, 37, 32, 0.12);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.86);
        color: var(--momo-text-strong);
        font-size: 0.78rem;
        font-weight: 800;
        min-height: 34px;
        padding: 7px 10px;
    }

    .growth-detail-control:focus {
        border-color: rgba(172, 92, 58, 0.36);
        box-shadow: 0 0 0 3px rgba(172, 92, 58, 0.1);
        outline: 0;
    }

    .growth-product-panel {
        border: 1px solid rgba(172, 92, 58, 0.2);
        border-radius: 8px;
        background: linear-gradient(180deg, rgba(255, 255, 255, 0.86), rgba(250, 247, 240, 0.82));
        box-shadow: 0 10px 28px rgba(42, 37, 32, 0.08);
        margin-bottom: 10px;
        padding: 12px;
    }

    .growth-product-panel.is-hidden {
        display: none;
    }

    .growth-product-head {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        gap: 10px;
        align-items: start;
        border-bottom: 1px solid rgba(42, 37, 32, 0.08);
        padding-bottom: 10px;
    }

    .growth-product-kicker {
        color: var(--momo-text-muted);
        font-size: 0.68rem;
        font-weight: 900;
        letter-spacing: 0;
        margin-bottom: 4px;
    }

    .growth-product-title {
        color: var(--momo-text-strong);
        font-size: 0.98rem;
        font-weight: 950;
        line-height: 1.35;
        margin: 0;
    }

    .growth-product-meta {
        color: var(--momo-text-muted);
        font-size: 0.72rem;
        font-weight: 760;
        line-height: 1.45;
        margin: 5px 0 0;
    }

    .growth-product-close {
        border: 1px solid rgba(42, 37, 32, 0.12);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.74);
        color: var(--momo-text-muted);
        font-size: 0.74rem;
        font-weight: 900;
        min-height: 34px;
        padding: 6px 10px;
    }

    .growth-product-body {
        display: grid;
        grid-template-columns: minmax(0, 0.95fr) minmax(280px, 1.05fr);
        gap: 12px;
        margin-top: 12px;
    }

    .growth-product-evidence-grid,
    .growth-product-reason-list {
        display: grid;
        gap: 8px;
    }

    .growth-product-evidence-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .growth-product-reason-list {
        margin: 0;
        padding: 0;
        list-style: none;
    }

    .growth-product-reason-list li {
        border: 1px solid rgba(42, 37, 32, 0.08);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.66);
        color: var(--momo-text-strong);
        font-size: 0.75rem;
        font-weight: 800;
        line-height: 1.45;
        padding: 8px 10px;
    }

    .growth-product-actions {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
        justify-content: flex-end;
        margin-top: 12px;
    }

    .growth-detail-result {
        border: 1px solid rgba(42, 37, 32, 0.1);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.76);
        min-height: 190px;
        max-height: 390px;
        overflow: auto;
    }

    .growth-detail-row {
        display: grid;
        grid-template-columns: minmax(0, 1.1fr) minmax(300px, 0.9fr);
        gap: 12px;
        border-bottom: 1px solid rgba(42, 37, 32, 0.08);
        padding: 11px 12px;
    }

    .growth-detail-row:last-child {
        border-bottom: 0;
    }

    .growth-detail-row.is-active {
        background: rgba(255, 248, 232, 0.7);
        box-shadow: inset 3px 0 0 rgba(172, 92, 58, 0.5);
    }

    .growth-detail-name {
        margin: 0;
        color: var(--momo-text-strong);
        font-size: 0.86rem;
        font-weight: 900;
        line-height: 1.35;
    }

    .growth-detail-name-button {
        border: 0;
        background: transparent;
        color: inherit;
        display: inline;
        font: inherit;
        padding: 0;
        text-align: left;
        text-decoration: underline;
        text-decoration-color: rgba(172, 92, 58, 0.34);
        text-underline-offset: 3px;
    }

    .growth-detail-name-button:hover,
    .growth-detail-name-button:focus {
        color: #8f4d33;
        outline: 0;
        text-decoration-color: rgba(172, 92, 58, 0.76);
    }

    .growth-detail-line {
        margin: 4px 0 0;
        color: var(--momo-text-muted);
        font-size: 0.74rem;
        line-height: 1.4;
    }

    .growth-detail-action {
        display: grid;
        gap: 6px;
        align-content: start;
        justify-items: stretch;
    }

    .growth-detail-action .table-row-action {
        justify-self: end;
    }

    .growth-detail-action-row {
        display: flex;
        flex-wrap: wrap;
        gap: 6px;
        justify-content: flex-end;
    }

    .growth-detail-price-grid {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 7px;
    }

    .growth-price-chip {
        border: 1px solid rgba(42, 37, 32, 0.1);
        border-radius: 8px;
        background: rgba(250, 247, 240, 0.62);
        padding: 7px 8px;
    }

    .growth-price-chip.is-risk {
        border-color: rgba(185, 79, 58, 0.22);
        background: rgba(255, 244, 239, 0.86);
    }

    .growth-price-chip.is-good {
        border-color: rgba(47, 143, 102, 0.2);
        background: rgba(238, 249, 241, 0.88);
    }

    .growth-price-label {
        display: block;
        color: var(--momo-text-muted);
        font-size: 0.68rem;
        font-weight: 900;
        line-height: 1.2;
    }

    .growth-price-value {
        display: block;
        margin-top: 3px;
        color: var(--momo-text-strong);
        font-family: var(--momo-font-mono);
        font-size: 0.9rem;
        font-weight: 900;
        line-height: 1.15;
    }

    .growth-price-note {
        display: block;
        margin-top: 2px;
        color: var(--momo-text-muted);
        font-size: 0.66rem;
        font-weight: 760;
        line-height: 1.25;
    }

    .growth-source-list {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 8px;
        margin-top: 10px;
    }

    .growth-source-chip {
        border: 1px solid rgba(42, 37, 32, 0.1);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.72);
        padding: 8px 10px;
    }

    .growth-source-chip.is-active {
        border-color: rgba(42, 134, 96, 0.24);
        background: rgba(235, 248, 241, 0.78);
    }

    .growth-source-name {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 8px;
        color: var(--momo-text-strong);
        font-size: 0.8rem;
        font-weight: 900;
    }

    .growth-source-status {
        border-radius: 999px;
        background: rgba(42, 37, 32, 0.08);
        color: var(--momo-text-muted);
        font-size: 0.68rem;
        font-weight: 900;
        padding: 3px 7px;
        white-space: nowrap;
    }

    .growth-source-chip.is-active .growth-source-status {
        background: rgba(42, 134, 96, 0.14);
        color: #1f6d4c;
    }

    .growth-source-detail {
        margin: 5px 0 0;
        color: var(--momo-text-muted);
        font-size: 0.74rem;
        line-height: 1.4;
    }

    .growth-list {
        display: grid;
        gap: 8px;
        max-height: 292px;
        overflow-y: auto;
        padding-right: 4px;
    }

    .growth-ops-table-wrap {
        overflow: auto;
        max-height: 328px;
        border: 1px solid rgba(42, 37, 32, 0.08);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.76);
    }

    .growth-ops-table {
        width: 100%;
        border-collapse: collapse;
        font-size: 0.8rem;
    }

    .growth-ops-table th,
    .growth-ops-table td {
        border-bottom: 1px solid rgba(42, 37, 32, 0.08);
        padding: 9px 10px;
        vertical-align: top;
    }

    .growth-ops-table th {
        position: sticky;
        top: 0;
        z-index: 1;
        background: rgba(250, 247, 240, 0.96);
        color: var(--momo-text-muted);
        font-size: 0.72rem;
        font-weight: 900;
        white-space: nowrap;
    }

    .growth-ops-table td {
        color: var(--momo-text-strong);
    }

    .growth-ops-table tr:last-child td {
        border-bottom: 0;
    }

    .growth-ops-name {
        max-width: 280px;
        font-weight: 900;
        line-height: 1.35;
    }

    .growth-ops-muted {
        color: var(--momo-text-muted);
        font-size: 0.72rem;
        line-height: 1.35;
    }

    .growth-item {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        gap: 10px;
        align-items: start;
        border: 1px solid rgba(42, 37, 32, 0.1);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.78);
        padding: 10px 12px;
    }

    .growth-item-title {
        margin: 0;
        color: var(--momo-text-strong);
        font-size: 0.9rem;
        font-weight: 800;
        line-height: 1.35;
    }

    .growth-item-meta,
    .growth-item-reason {
        margin: 4px 0 0;
        color: var(--momo-text-muted);
        font-size: 0.78rem;
        line-height: 1.45;
    }

    .growth-action-pill {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 92px;
        border: 1px solid rgba(42, 37, 32, 0.12);
        border-radius: 999px;
        background: rgba(242, 178, 90, 0.2);
        color: var(--momo-text-strong);
        font-size: 0.74rem;
        font-weight: 900;
        padding: 5px 8px;
        text-align: center;
        white-space: nowrap;
    }

    .price-risk-board {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
        gap: 10px;
        border-bottom: 1px solid var(--momo-border-subtle);
        padding: 10px 12px;
        background: rgba(250, 247, 240, 0.46);
    }

    .offer-dryrun-grid {
        display: grid;
        grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.55fr);
        gap: 14px;
    }

    .offer-dryrun-field {
        display: grid;
        gap: 8px;
    }

    .offer-dryrun-field label {
        color: var(--momo-text-strong);
        font-size: 0.78rem;
        font-weight: 900;
    }

    .offer-dryrun-field textarea {
        min-height: 132px;
        resize: vertical;
        border-color: var(--momo-border-subtle);
        border-radius: 8px;
        font-size: 0.8rem;
    }

    .offer-dryrun-summary {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 8px;
        margin-bottom: 10px;
    }

    .offer-dryrun-result {
        border: 1px solid rgba(42, 37, 32, 0.1);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.76);
        min-height: 216px;
        padding: 10px;
    }

    .offer-dryrun-row {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        gap: 10px;
        border-bottom: 1px solid rgba(42, 37, 32, 0.08);
        padding: 8px 0;
    }

    .offer-dryrun-row:last-child {
        border-bottom: 0;
    }

    .offer-dryrun-title {
        margin: 0;
        color: var(--momo-text-strong);
        font-size: 0.82rem;
        font-weight: 900;
        line-height: 1.35;
    }

    .offer-dryrun-meta,
    .offer-dryrun-reason {
        margin: 3px 0 0;
        color: var(--momo-text-muted);
        font-size: 0.74rem;
        line-height: 1.4;
    }

    .offer-status-pill {
        align-self: start;
        border-radius: 999px;
        background: rgba(42, 37, 32, 0.08);
        color: var(--momo-text-muted);
        font-size: 0.68rem;
        font-weight: 900;
        padding: 4px 8px;
        white-space: nowrap;
    }

    .offer-status-pill.is-ready {
        background: rgba(42, 134, 96, 0.14);
        color: #1f6d4c;
    }

    .offer-status-pill.is-blocked {
        background: rgba(188, 78, 67, 0.14);
        color: #94372d;
    }

    .review-candidate-panel {
        display: grid;
        grid-template-columns: minmax(0, 0.76fr) minmax(0, 1.6fr);
        gap: 14px;
    }

    .review-candidate-summary {
        display: grid;
        gap: 8px;
    }

    .review-candidate-flow {
        display: grid;
        gap: 8px;
    }

    .review-candidate-flow-step {
        display: grid;
        grid-template-columns: 28px minmax(0, 1fr);
        gap: 9px;
        align-items: center;
        min-height: 54px;
        padding: 9px 10px;
        border: 1px solid rgba(42, 37, 32, 0.09);
        border-radius: 8px;
        background: rgba(250, 247, 240, 0.72);
    }

    .review-candidate-flow-index {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 28px;
        height: 28px;
        border-radius: 999px;
        background: rgba(172, 92, 58, 0.13);
        color: var(--momo-warm-rust);
        font-family: var(--momo-font-mono);
        font-size: 0.74rem;
        font-weight: 900;
    }

    .review-candidate-flow-step strong {
        display: block;
        color: var(--momo-text-strong);
        font-size: 0.78rem;
        font-weight: 900;
        line-height: 1.2;
    }

    .review-candidate-flow-step span:last-child {
        display: block;
        margin-top: 2px;
        color: var(--momo-text-muted);
        font-size: 0.7rem;
        font-weight: 780;
        line-height: 1.25;
    }

    .review-candidate-result {
        border: 1px solid rgba(42, 37, 32, 0.1);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.76);
        min-height: 246px;
        max-height: 520px;
        overflow: auto;
        padding: 10px;
    }

    .review-candidate-row {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(148px, auto);
        gap: 14px;
        border-bottom: 1px solid rgba(42, 37, 32, 0.08);
        padding: 14px 0;
    }

    .review-candidate-row:last-child {
        border-bottom: 0;
    }

    .review-candidate-row.is-highlight {
        border-radius: 8px;
        background: rgba(242, 178, 90, 0.16);
        box-shadow: inset 3px 0 0 var(--momo-warm-caramel);
        padding-left: 10px;
    }

    .review-candidate-title {
        margin: 0;
        color: var(--momo-text-strong);
        font-size: 0.84rem;
        font-weight: 900;
        line-height: 1.35;
    }

    .review-candidate-meta {
        display: flex;
        align-items: center;
        flex-wrap: wrap;
        gap: 6px;
        margin: 7px 0 0;
    }

    .review-candidate-reason {
        margin: 8px 0 0;
        color: var(--momo-text-muted);
        font-size: 0.74rem;
        line-height: 1.4;
    }

    .review-candidate-compare {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 10px;
        margin-top: 10px;
    }

    .review-candidate-store {
        border: 1px solid rgba(42, 37, 32, 0.1);
        border-radius: 8px;
        background: rgba(255, 255, 255, 0.68);
        padding: 11px;
        min-width: 0;
    }

    .review-candidate-store.is-pchome {
        border-color: rgba(46, 125, 91, 0.2);
        background: rgba(235, 248, 241, 0.62);
    }

    .review-candidate-store.is-momo {
        border-color: rgba(201, 100, 66, 0.2);
        background: rgba(255, 244, 239, 0.58);
    }

    .review-candidate-store strong {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 8px;
        color: var(--momo-text-strong);
        font-size: 0.72rem;
        line-height: 1.2;
    }

    .review-candidate-store-head {
        display: flex;
        align-items: center;
        gap: 9px;
        min-width: 0;
    }

    .review-candidate-thumb {
        display: grid;
        flex: 0 0 48px;
        width: 48px;
        height: 48px;
        overflow: hidden;
        place-items: center;
        color: var(--momo-text-tertiary);
        background: rgba(245, 241, 234, 0.92);
        border: 1px solid rgba(42, 37, 32, 0.08);
        border-radius: 8px;
        font-size: 1rem;
    }

    .review-candidate-thumb.is-missing {
        align-content: center;
        gap: 2px;
        color: #786f63;
        font-size: 0.62rem;
        font-weight: 900;
        line-height: 1.1;
        text-align: center;
    }

    .review-candidate-thumb.is-missing i {
        display: block;
        font-size: 0.92rem;
    }

    .review-candidate-thumb img {
        width: 100%;
        height: 100%;
        object-fit: cover;
    }

    .review-candidate-store-price {
        display: block;
        color: var(--momo-text-strong);
        font-family: var(--momo-font-mono);
        font-size: 0.95rem;
        font-weight: 900;
        margin-top: 4px;
    }

    .review-candidate-store-title {
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        min-height: 2.5em;
        margin: 4px 0 0;
        overflow: hidden;
        color: var(--momo-text-muted);
        font-size: 0.72rem;
        line-height: 1.25;
    }

    .review-candidate-store-meta {
        display: flex;
        flex-wrap: wrap;
        gap: 5px;
        margin-top: 6px;
    }

    .review-candidate-store-meta span {
        border: 1px solid rgba(42, 37, 32, 0.08);
        border-radius: 999px;
        background: rgba(255, 255, 255, 0.7);
        color: var(--momo-text-muted);
        font-family: var(--momo-font-mono);
        font-size: 0.66rem;
        font-weight: 900;
        padding: 2px 7px;
    }

    .review-candidate-store a {
        white-space: nowrap;
        font-size: 0.7rem;
        font-weight: 800;
    }

    .review-candidate-store-label {
        display: inline-flex;
        align-items: center;
        gap: 5px;
        color: var(--momo-text-muted);
        font-size: 0.68rem;
        font-weight: 900;
        letter-spacing: 0.02em;
    }

    .review-candidate-pill {
        display: inline-flex;
        align-items: center;
        min-height: 24px;
        padding: 3px 8px;
        color: var(--momo-text-secondary);
        background: rgba(245, 241, 234, 0.88);
        border: 1px solid rgba(42, 37, 32, 0.1);
        border-radius: 999px;
        font-size: 0.72rem;
        font-weight: 900;
        line-height: 1.1;
    }

    .review-candidate-pill.is-risk {
        color: #94372d;
        background: rgba(255, 244, 239, 0.92);
        border-color: rgba(188, 78, 67, 0.2);
    }

    .review-candidate-pill.is-win {
        color: #1f6d4c;
        background: rgba(235, 248, 241, 0.92);
        border-color: rgba(42, 134, 96, 0.2);
    }

    .review-candidate-pill.is-review {
        color: #8a5a0a;
        background: rgba(255, 248, 231, 0.95);
        border-color: rgba(210, 158, 58, 0.24);
    }

    .review-candidate-reason-chips {
        display: flex;
        flex-wrap: wrap;
        gap: 6px;
        margin-top: 8px;
    }

    .review-candidate-actions {
        display: grid;
        gap: 7px;
        align-content: start;
        min-width: 148px;
    }

    .review-candidate-actions .btn {
        white-space: nowrap;
    }

    .review-candidate-actions .review-candidate-primary {
        border-color: rgba(49, 113, 234, 0.32);
        background: rgba(49, 113, 234, 0.08);
        color: #2557b8;
        font-weight: 900;
    }

    @media (max-width: 1320px) {
        .growth-command-kpi-grid {
            grid-template-columns: repeat(3, minmax(0, 1fr));
        }

        .growth-command-card.is-sales {
            grid-column: span 2;
        }

        .growth-command-focus {
            grid-template-columns: minmax(0, 1fr) minmax(300px, 0.82fr);
        }

        .growth-command-focus .growth-command-panel:first-child {
            grid-column: 1 / -1;
        }
    }

    @media (max-width: 992px) {
        .growth-command-pro-head {
            display: grid;
            grid-template-columns: 1fr;
        }

        .growth-command-pro-actions {
            justify-content: flex-start;
            max-width: none;
        }

        .growth-command-kpi-grid,
        .growth-command-focus {
            grid-template-columns: 1fr;
        }

        .growth-command-card.is-sales,
        .growth-command-focus .growth-command-panel:first-child {
            grid-column: auto;
        }

        .growth-command-alert {
            width: 100%;
        }

        .ai-intel-hero {
            grid-template-columns: 1fr;
        }

        .ai-intel-actions {
            justify-content: flex-start;
            max-width: none;
        }
    }

    @media (max-width: 576px) {
        .growth-command-alert {
            grid-template-columns: auto minmax(0, 1fr);
            align-items: start;
        }

        .growth-command-alert-action {
            grid-column: 1 / -1;
            justify-self: stretch;
            width: 100%;
        }

        .growth-command-pro-actions .ai-action-btn,
        .growth-command-status-pill {
            width: 100%;
        }

        .ai-intel-hero {
            padding: 18px;
        }

        .ai-intel-hero .ai-action-btn {
            display: none;
        }
    }

    @media (max-width: 768px) {
        .ai-panel .card-header {
            align-items: flex-start !important;
            flex-direction: column;
            gap: 10px;
        }

        .ai-panel .card-header > .d-flex {
            display: grid !important;
            grid-template-columns: 1fr;
            width: 100%;
        }

        .ai-panel .card-header .form-select,
        .ai-panel .card-header .form-control {
            width: 100% !important;
        }

        .ai-table-scroll {
            max-height: none;
            overflow: visible;
        }

        #competitorTable,
        #competitorTable tbody,
        #competitorTable tr,
        #competitorTable td {
            display: block;
            width: 100% !important;
        }

        #competitorTable thead {
            display: none;
        }

        #competitorTable tr {
            padding: 0.85rem 0.95rem;
            border-top: 1px solid rgba(42, 37, 32, 0.08);
        }

        .growth-ops-table,
        .growth-ops-table tbody,
        .growth-ops-table tr,
        .growth-ops-table td {
            display: block;
            width: 100% !important;
        }

        .growth-ops-table thead {
            display: none;
        }

        .growth-ops-table-wrap {
            max-height: none;
            overflow: visible;
        }

        .growth-ops-table tr {
            padding: 0.85rem 0.95rem;
            border-top: 1px solid rgba(42, 37, 32, 0.08);
        }

        .growth-ops-table tr:first-child {
            border-top: 0;
        }

        .growth-ops-table td {
            display: grid;
            grid-template-columns: 6rem minmax(0, 1fr);
            gap: 0.65rem;
            align-items: start;
            padding: 0.36rem 0 !important;
            border: 0 !important;
            text-align: left !important;
            overflow-wrap: anywhere;
            white-space: normal;
        }

        .growth-ops-table td::before {
            content: attr(data-label);
            color: var(--momo-text-muted);
            font-family: var(--momo-font-mono);
            font-size: 0.68rem;
            font-weight: 800;
        }

        .growth-ops-table .table-row-action,
        #competitorTable .table-row-action {
            width: 100%;
        }

        #competitorTable tr:first-child {
            border-top: 0;
        }

        #competitorTable td {
            display: grid;
            grid-template-columns: 5.8rem minmax(0, 1fr);
            gap: 0.65rem;
            align-items: start;
            padding: 0.36rem 0 !important;
            border: 0 !important;
            text-align: left !important;
            overflow-wrap: anywhere;
            white-space: normal;
        }

        #competitorTable td::before {
            content: attr(data-label);
            color: var(--momo-text-muted);
            font-family: var(--momo-font-mono);
            font-size: 0.68rem;
            font-weight: 800;
            letter-spacing: 0.06em;
        }

        #competitorTable td[colspan] {
            display: block;
        }

        #competitorTable td[colspan]::before {
            content: none;
            display: none;
        }

        .ai-panel .card-footer {
            align-items: flex-start;
            flex-direction: column;
            gap: 4px;
        }

        .next-action-banner {
            grid-template-columns: auto minmax(0, 1fr);
        }

        .next-action-banner .ai-action-btn {
            grid-column: 1 / -1;
            width: 100%;
        }

        .growth-ops-grid,
        .growth-source-list,
        .review-candidate-panel,
        .growth-executive-strip,
        .offer-dryrun-grid,
        .growth-metric-row,
        .growth-strategy-grid,
        .ops-flow-grid,
        .offer-dryrun-summary,
        .price-risk-board {
            grid-template-columns: 1fr;
        }

        .growth-item,
        .growth-detail-row,
        .offer-dryrun-row {
            grid-template-columns: 1fr;
        }

        .review-candidate-row,
        .review-candidate-compare {
            grid-template-columns: 1fr;
        }

        .review-candidate-actions {
            grid-template-columns: 1fr 1fr;
            min-width: 0;
            width: 100%;
        }

        .review-candidate-actions .btn:first-child {
            grid-column: 1 / -1;
        }

        .growth-detail-action {
            justify-items: stretch;
        }

        .growth-detail-action .table-row-action {
            justify-self: stretch;
        }

        .growth-decision-panel {
            grid-template-columns: 1fr;
        }

        .growth-decision-panel .table-row-action {
            width: 100%;
        }

        .growth-detail-controls {
            grid-template-columns: 1fr;
        }

        .growth-product-head,
        .growth-product-body,
        .growth-product-evidence-grid {
            grid-template-columns: 1fr;
        }

        .growth-product-actions,
        .growth-detail-action-row {
            justify-content: stretch;
        }

        .growth-product-actions .table-row-action,
        .growth-detail-action-row .table-row-action {
            flex: 1 1 100%;
        }

        .growth-category-head {
            align-items: flex-start;
            flex-direction: column;
        }

        .growth-category-note {
            text-align: left;
        }

        .growth-category-row {
            grid-template-columns: 1fr;
        }

        .growth-category-cta {
            text-align: left;
        }

        .growth-playbook-head {
            align-items: flex-start;
            flex-direction: column;
        }

        .growth-playbook-note {
            text-align: left;
        }

        .growth-playbook-grid {
            grid-template-columns: 1fr;
        }

        .growth-playbook-card {
            min-height: 0;
        }

        .growth-action-board-head {
            align-items: flex-start;
            flex-direction: column;
        }

        .growth-action-board-note {
            text-align: left;
        }

        .growth-action-row {
            grid-template-columns: 1fr;
        }

        .growth-action-buttons {
            justify-content: stretch;
        }

        .growth-action-buttons .table-row-action {
            flex: 1 1 100%;
        }

        .growth-action-evidence {
            grid-template-columns: repeat(2, minmax(0, 1fr));
        }

        .growth-detail-price-grid {
            grid-template-columns: 1fr;
        }
    }
</style>
{% endblock %}

{% block ewooo_content %}
<div class="ai-intel-page">

    <section class="growth-command-pro" aria-label="PChome 業績成長系統">
        <div class="growth-command-pro-head">
            <div>
                <h1 class="growth-command-pro-title">PChome 業績成長系統</h1>
                <p class="growth-command-pro-subtitle">先看今日優先商品，再決定價格、曝光、組合與資料補強。</p>
            </div>
            <div class="growth-command-pro-actions">
                <span id="growthCommandStatus" class="growth-command-status-pill">
                    <i class="fas fa-circle-check"></i>
                    <span>讀取中</span>
                </span>
                <div class="growth-command-action-group" aria-label="今日主要操作">
                    <button class="btn btn-primary btn-sm ai-action-btn" id="btnPickList" data-action="generate-picks" onclick="generatePickList()">
                        <i class="fas fa-list-check me-1"></i>產生今日清單
                    </button>
                    <button class="btn btn-primary btn-sm ai-action-btn" id="btnBackfill" data-action="backfill" onclick="backfillPchomeMatches()">
                        <i class="fas fa-link me-1"></i>補齊比價資料
                    </button>
                </div>
                <div class="growth-command-action-group is-secondary" aria-label="資料更新操作">
                    <button class="btn btn-outline-primary btn-sm ai-action-btn" id="btnTrigger" data-action="trigger-analysis" onclick="triggerAnalysis()">
                        <i class="fas fa-sparkles me-1"></i>更新建議
                    </button>
                    <button class="btn btn-outline-secondary btn-sm ai-action-btn" onclick="loadDashboard(); loadGrowthOps(true);">
                        <i class="fas fa-rotate me-1"></i>重新整理
                    </button>
                </div>
            </div>
        </div>

        <div class="growth-command-kpi-grid" aria-label="業績重點數字">
            <article class="growth-command-card is-sales is-clickable" role="button" tabindex="0" onclick="showGrowthDetail('all')" onkeydown="handleGrowthDetailKey(event, 'all')">
                <div class="growth-command-kpi-label">
                    <span>PChome 近 7 天業績</span>
                    <i class="fas fa-chart-line"></i>
                </div>
                <strong class="growth-command-kpi-value" id="commandSales7d">—</strong>
                <svg class="growth-command-spark" viewBox="0 0 140 34" role="img" aria-label="近 7 天趨勢">
                    <polyline points="3,25 24,20 45,23 66,12 87,16 108,8 137,11" fill="none" stroke="#3171ea" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"></polyline>
                </svg>
                <span class="growth-command-kpi-note" id="commandSalesDelta">等待資料</span>
            </article>
            <article class="growth-command-card is-clickable" role="button" tabindex="0" onclick="showGrowthDetail('ready')" onkeydown="handleGrowthDetailKey(event, 'ready')">
                <div class="growth-command-kpi-label">
                    <span>比價可用率</span>
                    <i class="fas fa-link"></i>
                </div>
                <strong class="growth-command-kpi-value" id="commandMatchRate">—%</strong>
                <div class="growth-command-progress"><span id="commandMatchRateBar"></span></div>
                <span class="growth-command-kpi-note" id="commandMappedRatio">等待 MOMO 對應</span>
            </article>
            <article class="growth-command-card is-clickable" role="button" tabindex="0" onclick="showGrowthDetail('all')" onkeydown="handleGrowthDetailKey(event, 'all')">
                <div class="growth-command-kpi-label">
                    <span>下滑商品</span>
                    <i class="fas fa-arrow-trend-down"></i>
                </div>
                <strong class="growth-command-kpi-value" id="commandDecliningProducts">—</strong>
                <span class="growth-command-kpi-note" id="commandActiveProducts">等待業績商品數</span>
            </article>
            <article class="growth-command-card is-clickable" role="button" tabindex="0" onclick="showGrowthDetail('needs')" onkeydown="handleGrowthDetailKey(event, 'needs')">
                <div class="growth-command-kpi-label">
                    <span>待補比價</span>
                    <i class="fas fa-magnifying-glass-chart"></i>
                </div>
                <strong class="growth-command-kpi-value" id="commandNeedMapping">—</strong>
                <span class="growth-command-kpi-note"><span id="commandReviewCount">—</span> 筆候選待確認</span>
            </article>
            <article class="growth-command-card is-clickable" role="button" tabindex="0" onclick="showGrowthCategoryDetail('')" onkeydown="handleGrowthDetailKey(event, 'all')">
                <div class="growth-command-kpi-label">
                    <span>最大業績分類</span>
                    <i class="fas fa-layer-group"></i>
                </div>
                <strong class="growth-command-kpi-value" id="commandTopCategory">—</strong>
                <span class="growth-command-kpi-note" id="commandTopCategorySales">等待分類業績</span>
            </article>
        </div>

        <div class="growth-command-alert" aria-live="polite">
            <i class="fas fa-bullseye"></i>
            <div class="growth-command-alert-copy">
                <strong id="commandTaskTitle">正在整理今天第一件事</strong>
                <span id="commandTaskCopy">讀取 PChome 業績與 MOMO 比價。</span>
            </div>
            <button type="button" class="btn btn-sm btn-primary ai-action-btn growth-command-alert-action" id="commandTaskButton" onclick="scrollToPanel('growthOpsPanel')">查看清單</button>
        </div>

        <div class="growth-command-focus">
            <section class="growth-command-panel" aria-label="下滑商品 TOP 5">
                <div class="growth-command-panel-head">
                    <h2 class="growth-command-panel-title">下滑商品 TOP 5</h2>
                    <button class="growth-command-panel-link" type="button" onclick="showGrowthDetail('all')">看全部</button>
                </div>
                <table class="growth-command-table">
                    <tbody id="commandTopDecliners">
                        <tr><td class="text-muted">整理商品中...</td></tr>
                    </tbody>
                </table>
            </section>

            <section class="growth-command-panel" aria-label="PChome 與 MOMO 價格狀態">
                <div class="growth-command-panel-head">
                    <h2 class="growth-command-panel-title">PChome 與 MOMO 價格狀態</h2>
                    <button class="growth-command-panel-link" type="button" onclick="showGrowthDetail('risk')">看價格壓力</button>
                </div>
                <div class="growth-command-donut-wrap">
                    <div class="growth-command-donut" id="commandPriceDonut">
                        <strong id="commandDonutText">—%</strong>
                    </div>
                    <div class="growth-command-legend">
                        <div class="growth-command-legend-row">
                            <span class="growth-command-dot is-ready"></span>
                            <span>PChome 價格優勢</span>
                            <strong id="commandPriceAdvantage">—</strong>
                        </div>
                        <div class="growth-command-legend-row">
                            <span class="growth-command-dot is-review"></span>
                            <span>MOMO 低價壓力</span>
                            <strong id="commandPricePressure">—</strong>
                        </div>
                        <div class="growth-command-legend-row">
                            <span class="growth-command-dot"></span>
                            <span>待確認 / 待補</span>
                            <strong id="commandPricePending">—</strong>
                        </div>
                    </div>
                </div>
            </section>

            <section class="growth-command-panel" aria-label="處理狀態與活動機會">
                <div class="growth-command-panel-head">
                    <h2 class="growth-command-panel-title">處理狀態</h2>
                    <button class="growth-command-panel-link" type="button" onclick="scrollToPanel('growthReviewPanel')">確認候選</button>
                </div>
                <div class="growth-command-mini-grid">
                    <div class="growth-command-mini-card">
                        <span>可立即處理</span>
                        <strong id="commandStatusReady">—</strong>
                        <em>已有外部價格可判斷</em>
                    </div>
                    <div class="growth-command-mini-card">
                        <span>待確認</span>
                        <strong id="commandStatusReview">—</strong>
                        <em>先確認同款</em>
                    </div>
                    <div class="growth-command-mini-card">
                        <span>待補比價</span>
                        <strong id="commandStatusNeeds">—</strong>
                        <em>需要補抓 MOMO</em>
                    </div>
                    <div class="growth-command-mini-card">
                        <span>最新業績日</span>
                        <strong id="commandLatestSalesDate">—</strong>
                        <em>資料更新狀態</em>
                    </div>
                </div>
            </section>
        </div>
    </section>

    <!-- ── 頁首 ── -->
    <section class="ai-intel-hero ai-intel-legacy-status" aria-hidden="true">
        <div>
            <h1 class="ai-intel-title">
                <i class="fas fa-brain"></i>
                PChome 業績成長指揮台
                <span class="ai-intel-badge">今日業績助手</span>
            </h1>
            <p class="ai-intel-subtitle">一進來先看：今天該處理哪些商品、哪裡價格偏高、哪些資料還不完整。</p>
        </div>
        <div class="ai-intel-actions">
            <span id="lastUpdateBadge" class="ai-status-badge">
                <i class="fas fa-sync me-1"></i>載入中...
            </span>
            <button class="btn btn-outline-primary btn-sm ai-action-btn" id="legacyBtnTrigger" data-action="trigger-analysis" onclick="triggerAnalysis()">
                <i class="fas fa-sparkles me-1"></i>更新建議
            </button>
            <button class="btn btn-outline-primary btn-sm ai-action-btn" id="legacyBtnPickList" data-action="generate-picks" onclick="generatePickList()" title="依目前業績與比價資料整理商品處理清單">
                <i class="fas fa-list-check me-1"></i>產生今日清單
            </button>
            <button class="btn btn-outline-warning btn-sm ai-action-btn" id="legacyBtnBackfill" data-action="backfill" onclick="backfillPchomeMatches()" title="替還不能比價的 PChome 商品尋找 MOMO 參考">
                <i class="fas fa-link me-1"></i>補齊比價資料
            </button>
            <button class="btn btn-outline-secondary btn-sm ai-action-btn" onclick="loadDashboard()" title="重新載入畫面資料">
                <i class="fas fa-redo me-1"></i>重新整理
            </button>
        </div>
    </section>

    <section class="growth-executive-strip" aria-label="今日任務摘要">
        <article class="growth-exec-card is-primary is-clickable" id="growthExecTaskCard" role="button" tabindex="0" onclick="showGrowthDetail('task')" onkeydown="handleGrowthDetailKey(event, 'task')">
            <div class="growth-exec-label">
                <span>今日任務</span>
                <i class="fas fa-location-arrow"></i>
            </div>
            <div class="growth-exec-value" id="growthExecTask">整理中</div>
            <div class="growth-exec-detail"><span id="growthExecTaskDetail">正在讀取 PChome 業績與 MOMO 外部價格。</span><span class="drilldown-hint">看明細</span></div>
        </article>
        <article class="growth-exec-card is-ready is-clickable" role="button" tabindex="0" onclick="showGrowthDetail('ready')" onkeydown="handleGrowthDetailKey(event, 'ready')">
            <div class="growth-exec-label">
                <span>可立即處理</span>
                <i class="fas fa-circle-check"></i>
            </div>
            <div class="growth-exec-value" id="growthExecReady">—</div>
            <div class="growth-exec-detail">已有可用比價資料<span class="drilldown-hint">看清單</span></div>
        </article>
        <article class="growth-exec-card is-gap is-clickable" id="growthExecGapCard" role="button" tabindex="0" onclick="showGrowthDetail('needs')" onkeydown="handleGrowthDetailKey(event, 'needs')">
            <div class="growth-exec-label">
                <span>待補比價</span>
                <i class="fas fa-link-slash"></i>
            </div>
            <div class="growth-exec-value" id="growthExecGap">—</div>
            <div class="growth-exec-detail">有業績但缺外部參考<span class="drilldown-hint">看商品</span></div>
        </article>
        <article class="growth-exec-card is-clickable" role="button" tabindex="0" onclick="showGrowthDetail('all')" onkeydown="handleGrowthDetailKey(event, 'all')">
            <div class="growth-exec-label">
                <span>最新業績日</span>
                <i class="fas fa-calendar-day"></i>
            </div>
            <div class="growth-exec-value" id="growthExecLatestDate">—</div>
            <div class="growth-exec-detail"><span id="growthExecLatestDetail">等待資料</span><span class="drilldown-hint">看清單</span></div>
        </article>
    </section>

    <section class="growth-action-board" id="growthActionBoard" aria-label="今日策略動作">
        <div class="growth-action-board-head">
            <h3 class="growth-action-board-title">今日策略動作</h3>
            <span class="growth-action-board-note">照順序處理最可能影響業績的商品</span>
        </div>
        <div class="growth-action-list">
            <div class="text-center py-3 text-muted">整理動作中...</div>
        </div>
    </section>

    <!-- ── 今日重點總覽 ── -->
    <section class="ops-flow" aria-label="今日重點總覽">
        <div class="ops-flow-head">
            <span class="ai-panel-title">
                <i class="fas fa-chart-simple"></i>今日重點總覽
            </span>
            <span class="ops-flow-note" id="opsDashboardStatus">資料整理中</span>
        </div>
        <div class="next-action-banner" aria-live="polite">
            <span class="next-action-icon"><i class="fas fa-location-arrow"></i></span>
            <span>
                <strong class="next-action-title" id="nextActionTitle">正在找出今天最該先做的事</strong>
                <span class="next-action-reason" id="nextActionReason">系統正在整理業績、比價與商品對應狀態。</span>
            </span>
            <button type="button" class="btn btn-sm btn-primary ai-action-btn" id="nextActionButton" onclick="scrollToPanel('growthOpsPanel')">
                查看今日清單
            </button>
        </div>
        <div class="ops-flow-grid">
            <div class="ops-dashboard-tile is-clickable" role="button" tabindex="0" onclick="showGrowthDetail('all')" onkeydown="handleGrowthDetailKey(event, 'all')">
                <div class="ops-dashboard-title">
                    <span>商品處理進度</span>
                    <strong class="ops-dashboard-value" id="opsReadyRate">—%</strong>
                </div>
                <div class="ops-funnel" aria-label="商品處理進度">
                    <div class="ops-funnel-row">
                        <div class="ops-funnel-meta"><span>追蹤商品</span><strong id="opsFunnelCandidateText">—</strong></div>
                        <div class="ops-funnel-track"><span class="ops-funnel-bar" id="opsFunnelCandidateBar"></span></div>
                    </div>
                    <div class="ops-funnel-row">
                        <div class="ops-funnel-meta"><span>可立即處理</span><strong id="opsFunnelMappedText">—</strong></div>
                        <div class="ops-funnel-track"><span class="ops-funnel-bar is-ready" id="opsFunnelMappedBar"></span></div>
                    </div>
                    <div class="ops-funnel-row">
                        <div class="ops-funnel-meta"><span>無法比價</span><strong id="opsFunnelNeedsText">—</strong></div>
                        <div class="ops-funnel-track"><span class="ops-funnel-bar is-gap" id="opsFunnelNeedsBar"></span></div>
                    </div>
                </div>
            </div>
            <div class="ops-dashboard-tile is-clickable" role="button" tabindex="0" onclick="showGrowthDetail('source')" onkeydown="handleGrowthDetailKey(event, 'source')">
                <div class="ops-dashboard-title">
                    <span>外部價格來源</span>
                    <strong class="ops-dashboard-value" id="opsSourceTotal">—</strong>
                </div>
                <div class="ops-source-bars" id="opsSourceBars" aria-label="外部價格來源">
                    <div class="ops-source-row">
                        <div class="ops-source-meta"><span>整理中</span><strong>—</strong></div>
                        <div class="ops-source-track"><span class="ops-source-bar"></span></div>
                    </div>
                </div>
            </div>
            <div class="ops-dashboard-tile">
                <div class="ops-dashboard-title">
                    <span>操作順序</span>
                    <span>4 步</span>
                </div>
                <div class="ops-action-grid">
                    <button type="button" class="ops-flow-item" onclick="scrollToPanel('growthOpsPanel')">
                        <span class="ops-flow-step">1</span>
                        <span class="ops-flow-title">今日清單</span>
                    </button>
                    <button type="button" class="ops-flow-item" data-action="backfill" onclick="backfillPchomeMatches()">
                        <span class="ops-flow-step">2</span>
                        <span class="ops-flow-title">補齊比價</span>
                    </button>
                    <button type="button" class="ops-flow-item" onclick="scrollToPanel('externalPricePanel')">
                        <span class="ops-flow-step">3</span>
                        <span class="ops-flow-title">檢查價格</span>
                    </button>
                    <button type="button" class="ops-flow-item" onclick="scrollToPanel('externalOfferDryRunPanel')">
                        <span class="ops-flow-step">4</span>
                        <span class="ops-flow-title">檢查備用資料</span>
                    </button>
                </div>
            </div>
        </div>
    </section>

    <!-- ── 點擊摘要後的商品明細 ── -->
    <section class="card shadow-sm ai-panel growth-detail-panel" id="growthDrilldownPanel">
        <div class="card-body">
            <div class="growth-detail-toolbar">
                <div>
                    <h2 class="growth-detail-title" id="growthDrilldownTitle">今日商品明細</h2>
                    <div class="growth-detail-meta" id="growthDrilldownMeta">整理中</div>
                </div>
                <div class="growth-detail-tabs" aria-label="明細切換">
                    <button type="button" class="growth-detail-tab is-active" data-detail-kind="all" onclick="showGrowthDetail('all')">全部</button>
                    <button type="button" class="growth-detail-tab" data-detail-kind="risk" onclick="showGrowthDetail('risk')">價格壓力</button>
                    <button type="button" class="growth-detail-tab" data-detail-kind="advantage" onclick="showGrowthDetail('advantage')">價格優勢</button>
                    <button type="button" class="growth-detail-tab" data-detail-kind="review" onclick="showGrowthDetail('review')">待確認</button>
                    <button type="button" class="growth-detail-tab" data-detail-kind="needs" onclick="showGrowthDetail('needs')">缺比價</button>
                    <button type="button" class="growth-detail-tab" data-detail-kind="source" onclick="showGrowthDetail('source')">有外部價</button>
                </div>
            </div>
            <div class="growth-strategy-grid" id="growthStrategyGrid" aria-label="商品策略分流">
                <button type="button" class="growth-strategy-card is-risk" onclick="showGrowthDetail('risk')">
                    <span class="growth-strategy-label">價格壓力</span>
                    <span class="growth-strategy-value">—</span>
                    <span class="growth-strategy-note">檢查售價、券或組合</span>
                    <span class="growth-strategy-track"><span class="growth-strategy-bar"></span></span>
                </button>
                <button type="button" class="growth-strategy-card is-advantage" onclick="showGrowthDetail('advantage')">
                    <span class="growth-strategy-label">價格優勢</span>
                    <span class="growth-strategy-value">—</span>
                    <span class="growth-strategy-note">放大曝光與主推位置</span>
                    <span class="growth-strategy-track"><span class="growth-strategy-bar"></span></span>
                </button>
                <button type="button" class="growth-strategy-card is-review" onclick="showGrowthDetail('review')">
                    <span class="growth-strategy-label">待確認</span>
                    <span class="growth-strategy-value">—</span>
                    <span class="growth-strategy-note">先確認同款再判斷價格</span>
                    <span class="growth-strategy-track"><span class="growth-strategy-bar"></span></span>
                </button>
                <button type="button" class="growth-strategy-card is-needs" onclick="showGrowthDetail('needs')">
                    <span class="growth-strategy-label">缺比價</span>
                    <span class="growth-strategy-value">—</span>
                    <span class="growth-strategy-note">補抓 MOMO 候選</span>
                    <span class="growth-strategy-track"><span class="growth-strategy-bar"></span></span>
                </button>
            </div>
            <div class="growth-category-board" id="growthCategoryBoard" aria-label="分類策略看板">
                <div class="growth-category-head">
                    <h3 class="growth-category-title">分類策略看板</h3>
                    <span class="growth-category-note">依近 7 天業績排序，點分類看商品</span>
                </div>
                <div class="growth-category-list">
                    <div class="text-center py-3 text-muted">整理分類中...</div>
                </div>
            </div>
            <div class="growth-playbook-board" id="growthPlaybookBoard" aria-label="銷售策略建議">
                <div class="growth-playbook-head">
                    <h3 class="growth-playbook-title">銷售策略建議</h3>
                    <span class="growth-playbook-note">把比價結果轉成可執行動作</span>
                </div>
                <div class="growth-playbook-grid">
                    <div class="text-center py-3 text-muted">整理策略中...</div>
                </div>
            </div>
            <div class="growth-decision-panel" id="growthDecisionSummary">
                <div>
                    <h3 class="growth-decision-title">正在整理處理建議</h3>
                    <p class="growth-decision-copy">先整理業績、比價與資料狀態，再決定下一步。</p>
                </div>
                <button type="button" class="btn btn-sm btn-outline-primary table-row-action" onclick="showGrowthDetail('all')">查看明細</button>
            </div>
            <div class="growth-detail-controls" aria-label="商品明細工具">
                <label class="growth-detail-field">
                    <span>搜尋商品</span>
                    <input type="search" class="growth-detail-control" id="growthDetailSearch" placeholder="商品、分類、編號" oninput="setGrowthDetailSearch(this.value)">
                </label>
                <label class="growth-detail-field">
                    <span>排序</span>
                    <select class="growth-detail-control" id="growthDetailSort" onchange="setGrowthDetailSort(this.value)">
                        <option value="priority">優先級高到低</option>
                        <option value="sales">近 7 天業績高到低</option>
                        <option value="gap">價差大到小</option>
                        <option value="decline">下滑多到少</option>
                        <option value="quality">可信度高到低</option>
                    </select>
                </label>
                <button type="button" class="btn btn-sm btn-outline-secondary table-row-action" onclick="clearGrowthDetailFilters()">清除</button>
            </div>
            <div class="growth-product-panel is-hidden" id="growthProductDecisionPanel" aria-live="polite"></div>
            <div class="growth-detail-result" id="growthDrilldownResult">
                <div class="text-center py-4 text-muted">
                    <div class="spinner-border spinner-border-sm me-2"></div>整理商品明細中...
                </div>
            </div>
        </div>
    </section>

    <!-- ── 今日處理清單 ── -->
    <section class="card shadow-sm ai-panel" id="growthOpsPanel">
        <div class="card-header d-flex justify-content-between align-items-center py-2 bg-white border-bottom">
            <span class="ai-panel-title">
                <i class="fas fa-compass"></i>今日處理清單
                <small class="text-muted fw-normal ms-2">先處理最可能影響業績的商品</small>
            </span>
            <button class="btn btn-outline-secondary btn-sm ai-action-btn" onclick="loadGrowthOps(true)">
                <i class="fas fa-redo me-1"></i>更新清單
            </button>
        </div>
        <div class="card-body">
            <div class="growth-ops-grid">
                <div>
                    <div class="growth-metric-row">
                        <div class="growth-metric is-clickable" role="button" tabindex="0" onclick="showGrowthDetail('all')" onkeydown="handleGrowthDetailKey(event, 'all')">
                            <strong id="growthCandidateCount">—</strong>
                            <span>追蹤商品</span>
                        </div>
                        <div class="growth-metric is-clickable" role="button" tabindex="0" onclick="showGrowthDetail('ready')" onkeydown="handleGrowthDetailKey(event, 'ready')">
                            <strong id="growthMappedCount">—</strong>
                            <span>可立即處理</span>
                        </div>
                        <div class="growth-metric is-clickable" role="button" tabindex="0" onclick="showGrowthDetail('needs')" onkeydown="handleGrowthDetailKey(event, 'needs')">
                            <strong id="growthNeedsMapping">—</strong>
                            <span>無法比價</span>
                        </div>
                        <div class="growth-metric is-clickable" role="button" tabindex="0" onclick="showGrowthDetail('review')" onkeydown="handleGrowthDetailKey(event, 'review')">
                            <strong id="growthReviewCandidateCount">—</strong>
                            <span>待確認</span>
                        </div>
                    </div>
                    <p class="growth-action-hint" id="growthActionHint">正在判斷今天優先處理順序...</p>
                    <p class="growth-data-summary" id="growthDataSourceSummary">來源整理中...</p>
                    <p class="growth-source-note is-compact" id="growthSourceNote">業績來源整理中</p>
                    <div class="growth-source-list" id="growthSourceReadiness">
                        <div class="growth-source-chip">
                            <div class="growth-source-name">
                                <span>外部資料來源</span>
                                <span class="growth-source-status">整理中</span>
                            </div>
                            <p class="growth-source-detail">正在確認哪些來源可用。</p>
                        </div>
                    </div>
                </div>
                <div>
                    <div class="growth-list" id="growthOpsList">
                        <div class="text-center py-4 text-muted">
                            <div class="spinner-border spinner-border-sm me-2"></div>整理今日清單中...
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>

    <!-- ── MOMO 待確認候選 ── -->
    <section class="card shadow-sm ai-panel" id="growthReviewPanel">
        <div class="card-header d-flex justify-content-between align-items-center py-2 bg-white border-bottom">
            <span class="ai-panel-title">
                <i class="fas fa-list-check"></i>MOMO 待確認候選
                <small class="text-muted fw-normal ms-2">並排看兩家賣場，確認同款後才進作戰</small>
            </span>
            <button class="btn btn-outline-secondary btn-sm ai-action-btn" onclick="loadGrowthReviewCandidates(true)">
                <i class="fas fa-redo me-1"></i>更新候選
            </button>
        </div>
        <div class="card-body">
            <div class="review-candidate-panel">
                <div class="review-candidate-summary">
                    <div class="growth-metric">
                        <strong id="reviewCandidateTotal">—</strong>
                        <span>待確認</span>
                    </div>
                    <div class="growth-metric">
                        <strong id="reviewCandidateReadyHint">0</strong>
                        <span>確認後可進作戰</span>
                    </div>
                    <div class="review-candidate-flow" aria-label="待確認候選處理流程">
                        <div class="review-candidate-flow-step">
                            <span class="review-candidate-flow-index">1</span>
                            <span><strong>同時看賣場</strong><span>先開 PChome 與 MOMO 商品頁</span></span>
                        </div>
                        <div class="review-candidate-flow-step">
                            <span class="review-candidate-flow-index">2</span>
                            <span><strong>確認規格</strong><span>比對品名、容量、色號與組合</span></span>
                        </div>
                        <div class="review-candidate-flow-step">
                            <span class="review-candidate-flow-index">3</span>
                            <span><strong>送進作戰</strong><span>同款才進價格壓力與主推判斷</span></span>
                        </div>
                    </div>
                </div>
                <div class="review-candidate-result" id="growthReviewCandidateList">
                    <div class="text-center py-4 text-muted">
                        <div class="spinner-border spinner-border-sm me-2"></div>整理待確認候選中...
                    </div>
                </div>
            </div>
        </div>
    </section>

    <!-- ── KPI 卡片 ── -->
    <div class="row g-3 mb-4" id="kpiRow">
        <div class="col-6 col-md-3">
            <div class="card border-0 shadow-sm h-100 is-clickable" role="button" tabindex="0" onclick="showGrowthDetail('all')" onkeydown="handleGrowthDetailKey(event, 'all')">
                <div class="card-body text-center py-3">
                    <div class="fs-2 fw-bold text-primary" id="kpiSkus">—</div>
                    <div class="small text-muted mt-1"><i class="fas fa-box me-1"></i>監控商品數</div>
                </div>
            </div>
        </div>
        <div class="col-6 col-md-3">
            <div class="card border-0 shadow-sm h-100 is-clickable" role="button" tabindex="0" onclick="showGrowthDetail('ready')" onkeydown="handleGrowthDetailKey(event, 'ready')">
                <div class="card-body text-center py-3">
                    <div class="fs-2 fw-bold text-success" id="kpiCompetitors">—</div>
                    <div class="small text-muted mt-1">
                        <i class="fas fa-store me-1"></i>可立即處理
                        <span id="kpiMatchRate" class="text-muted" style="font-size:0.7rem"></span>
                    </div>
                </div>
            </div>
        </div>
        <div class="col-6 col-md-3">
            <!-- 高風險卡 — 數值來自全量 CTE，非前端截斷的 200 筆 -->
            <div class="card border-0 shadow-sm h-100 is-clickable" id="kpiHighRiskCard" role="button" tabindex="0" onclick="showPriceRiskDetail('HIGH')" onkeydown="handlePriceRiskKey(event, 'HIGH')">
                <div class="card-body text-center py-3">
                    <div class="fs-2 fw-bold text-danger" id="kpiHighRisk">—</div>
                    <div class="small text-muted mt-1">
                        <i class="fas fa-exclamation-triangle me-1"></i>需檢查價格
                    </div>
                </div>
            </div>
        </div>
        <div class="col-6 col-md-3">
            <div class="card border-0 shadow-sm h-100 is-clickable" role="button" tabindex="0" onclick="scrollToPanel('aiRecsPanel')" onkeydown="handleDrilldownKey(event, 'aiRecsPanel')">
                <div class="card-body text-center py-3">
                    <div class="fs-2 fw-bold text-info" id="kpiAiRecs">—</div>
                    <div class="small text-muted mt-1"><i class="fas fa-clipboard-check me-1"></i>處理紀錄</div>
                </div>
            </div>
        </div>
    </div>

    <!-- ── 主體分兩欄（競品比價 + AI 決策） ── -->
    <div class="row g-3">

        <!-- ── 左：外部價格參考 ── -->
        <div class="col-xl-7">
            <div class="card shadow-sm h-100 ai-panel" id="externalPricePanel">
                <div class="card-header d-flex justify-content-between align-items-center py-2 bg-white border-bottom">
                    <span class="ai-panel-title">
                        <i class="fas fa-balance-scale text-warning me-2"></i>MOMO 外部價格參考
                    </span>
                    <div class="d-flex gap-2 align-items-center">
                        <select class="form-select form-select-sm" id="riskFilter" onchange="filterTable()" style="width:100px">
                            <option value="all">全部</option>
                            <option value="HIGH">高風險</option>
                            <option value="MED">中風險</option>
                            <option value="LOW">低風險</option>
                        </select>
                        <input type="text" class="form-control form-control-sm" id="searchInput"
                               placeholder="搜尋商品..." oninput="filterTable()" style="width:130px">
                    </div>
                </div>
                <div class="price-risk-board" id="priceRiskBoard" aria-label="價格風險分佈">
                    <div class="price-risk-row" role="button" tabindex="0" onclick="showPriceRiskDetail('HIGH')" onkeydown="handlePriceRiskKey(event, 'HIGH')">
                        <div class="price-risk-meta"><span>需檢查價格</span><strong id="priceRiskHighText">—</strong></div>
                        <div class="price-risk-track"><span class="price-risk-bar is-high" id="priceRiskHighBar"></span></div>
                    </div>
                    <div class="price-risk-row" role="button" tabindex="0" onclick="showPriceRiskDetail('MED')" onkeydown="handlePriceRiskKey(event, 'MED')">
                        <div class="price-risk-meta"><span>留意價差</span><strong id="priceRiskMediumText">—</strong></div>
                        <div class="price-risk-track"><span class="price-risk-bar is-medium" id="priceRiskMediumBar"></span></div>
                    </div>
                    <div class="price-risk-row" role="button" tabindex="0" onclick="showPriceRiskDetail('LOW')" onkeydown="handlePriceRiskKey(event, 'LOW')">
                        <div class="price-risk-meta"><span>價格有利</span><strong id="priceRiskLowText">—</strong></div>
                        <div class="price-risk-track"><span class="price-risk-bar is-low" id="priceRiskLowBar"></span></div>
                    </div>
                </div>
                <!-- 熱力圖圖例 -->
                <div class="px-3 pt-2 pb-1 d-flex gap-3 small text-muted ai-legend" style="font-size:0.73rem">
                    <span><span style="display:inline-block;width:12px;height:12px;background:#fee2e2;border-radius:2px" class="me-1"></span>PChome 貴 &gt;15%</span>
                    <span><span style="display:inline-block;width:12px;height:12px;background:#fef9c3;border-radius:2px" class="me-1"></span>PChome 貴 5~15%</span>
                    <span><span style="display:inline-block;width:12px;height:12px;background:#dcfce7;border-radius:2px" class="me-1"></span>PChome 便宜</span>
                </div>
                <div class="card-body p-0 ai-table-scroll">
                    <table class="table table-sm table-hover mb-0 align-middle" id="competitorTable">
                        <thead class="table-light sticky-top" style="font-size:0.78rem;">
                            <tr>
                                <th class="ps-3" style="min-width:86px">風險</th>
                                <th style="min-width:210px">商品</th>
                                <th class="text-end" style="min-width:82px">PChome</th>
                                <th class="text-end" style="min-width:82px">MOMO</th>
                                <th class="text-end" style="min-width:106px">差距</th>
                                <th class="text-center" style="min-width:86px">可信度</th>
                                <th class="text-muted" style="min-width:80px">更新</th>
                                <th class="text-end" style="min-width:96px">下一步</th>
                            </tr>
                        </thead>
                        <tbody id="competitorTbody">
                            <tr><td colspan="8" class="text-center py-5 text-muted">
                                <div class="spinner-border spinner-border-sm me-2"></div>載入中...
                            </td></tr>
                        </tbody>
                    </table>
                </div>
                <div class="card-footer bg-white py-2 d-flex justify-content-between small text-muted">
                    <span id="compCount">—</span>
                    <span id="compSourceSummary">僅顯示已確認同款的商品</span>
                </div>
            </div>
        </div>

        <!-- ── 右：最近處理紀錄 ── -->
        <div class="col-xl-5">
            <div class="card shadow-sm h-100 ai-panel" id="aiRecsPanel">
                <div class="card-header py-2 bg-white border-bottom">
                    <span class="ai-panel-title">
                        <i class="fas fa-clipboard-check text-danger me-2"></i>最近處理紀錄
                        <small class="text-muted fw-normal ms-2">挑品、比價與人工覆核</small>
                    </span>
                </div>
                <div class="card-body p-0 ai-recs-scroll">
                    <div id="aiRecsList" class="p-2">
                        <div class="text-center py-5 text-muted">
                            <div class="spinner-border spinner-border-sm me-2"></div>載入中...
                        </div>
                    </div>
                </div>
                <div class="card-footer bg-white py-2 d-flex justify-content-between small text-muted">
                    <span id="aiRecsCount">—</span>
                    <span>可手動產生今日清單</span>
                </div>
            </div>
        </div>

    </div><!-- /row -->

    <!-- ── 備援資料檢查 ── -->
    <section class="card shadow-sm ai-panel" id="externalOfferDryRunPanel">
        <div class="card-header d-flex justify-content-between align-items-center py-2 bg-white border-bottom">
            <span class="ai-panel-title">
                <i class="fas fa-file-circle-check"></i>備援資料檢查
                <small class="text-muted fw-normal ms-2">自動來源不足時才使用</small>
            </span>
            <button class="btn btn-outline-secondary btn-sm ai-action-btn" onclick="fillExternalOfferSample()">
                <i class="fas fa-table me-1"></i>填入範例
            </button>
        </div>
        <div class="card-body">
            <div class="offer-dryrun-grid">
                <div class="offer-dryrun-field">
                    <label for="externalOfferCsvFile">備用資料檔案</label>
                    <input class="form-control form-control-sm" type="file" id="externalOfferCsvFile" accept=".csv,text/csv">
                    <label for="externalOfferCsvText">或貼上備援資料</label>
                    <textarea class="form-control" id="externalOfferCsvText" spellcheck="false"
                              placeholder="來源,平台商品編號,商品名稱,售價,資料時間,取得方式,PChome商品編號,是否同款,可信度"></textarea>
                    <button class="btn btn-primary btn-sm ai-action-btn" id="btnExternalOfferDryRun" onclick="previewExternalOfferCsv()">
                        <i class="fas fa-magnifying-glass me-1"></i>檢查備援資料
                    </button>
                </div>
                <div>
                    <div class="offer-dryrun-summary">
                        <div class="growth-metric">
                            <strong id="offerDryRunReady">—</strong>
                            <span>可使用</span>
                        </div>
                        <div class="growth-metric">
                            <strong id="offerDryRunReview">—</strong>
                            <span>待確認</span>
                        </div>
                        <div class="growth-metric">
                            <strong id="offerDryRunBlocked">—</strong>
                            <span>不能使用</span>
                        </div>
                    </div>
                    <div class="offer-dryrun-result" id="offerDryRunResult">
                        <div class="text-center py-4 text-muted">
                            <i class="fas fa-circle-info d-block mb-2"></i>
                            自動來源不足時，先檢查備援資料品質。
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>

    <!-- ── Trigger 進度 Toast ── -->
    <div class="position-fixed bottom-0 end-0 p-3" style="z-index:9999">
        <div id="triggerToast" class="toast align-items-center text-white border-0" role="alert">
            <div class="d-flex">
                <div class="toast-body" id="triggerToastMsg">
                    <i class="fas fa-bolt me-1"></i>分析已啟動...
                </div>
                <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
            </div>
        </div>
    </div>

</div><!-- /ai-intel-page -->

<script>
// ── 全域資料 ────────────────────────────────────────
let allCompetitors = [];
let latestGrowthStats = {};
let latestGrowthRows = [];
let activeGrowthDetailKind = 'all';
let growthDetailSearchText = '';
let activeGrowthDetailSort = 'priority';
let activeGrowthProductKey = '';
let activeGrowthCategoryFilter = '';

// ── 頁面載入 ────────────────────────────────────────
document.addEventListener('DOMContentLoaded', () => {
    loadDashboard();
    loadGrowthOps();
    bindActionDelegation();
});

function bindActionDelegation() {
    document.addEventListener('click', (event) => {
        const priceButton = event.target.closest('[data-table-action="focus"]');
        if (priceButton) {
            focusPriceTable(priceButton.dataset.productKey || '');
            return;
        }

        const growthButton = event.target.closest('[data-growth-action]');
        if (!growthButton) return;
        if (growthButton.dataset.growthAction === 'backfill') {
            backfillPchomeMatches();
            return;
        }
        if (growthButton.dataset.growthAction === 'show-product-detail') {
            showGrowthProductDetail(growthButton.dataset.productKey || '');
            return;
        }
        if (growthButton.dataset.growthAction === 'show-category-detail') {
            showGrowthCategoryDetail(growthButton.dataset.categoryName || '');
            return;
        }
        if (growthButton.dataset.growthAction === 'show-playbook-detail') {
            showGrowthPlaybookDetail(growthButton.dataset.playbookKind || '');
            return;
        }
        if (growthButton.dataset.growthAction === 'review-candidate') {
            focusReviewCandidate(growthButton.dataset.productKey || '');
            return;
        }
        focusPriceTable(growthButton.dataset.productKey || '');
    });
}

function focusPriceTable(keyword) {
    const input = document.getElementById('searchInput');
    if (input && keyword) {
        input.value = keyword;
        filterTable();
    }
    scrollToPanel('externalPricePanel');
}

function focusReviewCandidate(productKey) {
    scrollToPanel('growthReviewPanel');
    const key = String(productKey || '').trim();
    if (!key) return;
    setTimeout(() => {
        document.querySelectorAll('.review-candidate-row.is-highlight')
            .forEach((row) => row.classList.remove('is-highlight'));
        const target = Array.from(document.querySelectorAll('.review-candidate-row'))
            .find((row) => row.dataset.pchomeId === key);
        if (target) {
            target.classList.add('is-highlight');
            target.scrollIntoView({ behavior: 'smooth', block: 'center' });
        }
    }, 220);
}

async function loadDashboard() {
    try {
        const res  = await fetch('/api/ai/icaim/dashboard');
        const data = await readJsonResponse(res);
        if (!data.success) throw new Error(data.error || '載入失敗');

        const stats = data.stats || {};
        const competitors = Array.isArray(data.competitors) ? data.competitors : [];
        const aiRecs = Array.isArray(data.ai_recs) ? data.ai_recs : [];
        renderKPIs(stats);
        allCompetitors = competitors;
        renderCompetitorTable(allCompetitors);
        renderAiRecs(aiRecs);

        document.getElementById('lastUpdateBadge').innerHTML =
            '<i class="fas fa-check-circle me-1"></i>上次更新 ' + new Date().toLocaleTimeString('zh-TW');
        document.getElementById('lastUpdateBadge').className = 'ai-status-badge is-success';
    } catch (e) {
        document.getElementById('lastUpdateBadge').innerHTML =
            '<i class="fas fa-exclamation-circle me-1"></i>載入失敗';
        document.getElementById('lastUpdateBadge').className = 'ai-status-badge is-error';
        console.error(e);
    }
}

// ── KPI（high_risk_count 來自後端全量 CTE）─────────
function renderKPIs(stats) {
    document.getElementById('kpiSkus').textContent        = (stats.total_skus || 0).toLocaleString();
    document.getElementById('kpiCompetitors').textContent = (stats.valid_competitor_prices || 0).toLocaleString();
    document.getElementById('kpiAiRecs').textContent      = (stats.total_ai_recs || 0).toLocaleString();
    document.getElementById('kpiMatchRate').textContent   = stats.match_rate ? `(${stats.match_rate}%)` : '';
    renderCompetitorSourceSummary(stats);

    const hr = stats.high_risk_count || 0;
    document.getElementById('kpiHighRisk').textContent = hr;
    // 高風險卡：數值 > 0 加紅底強調
    document.getElementById('kpiHighRiskCard').className =
        hr > 0
            ? 'card border-2 border-danger shadow-sm h-100 is-clickable'
            : 'card border-0 shadow-sm h-100 is-clickable';
}

function formatMoney(value) {
    const num = Number(value || 0);
    return 'NT$ ' + Math.round(num).toLocaleString();
}

function formatPriceAmount(value, options = {}) {
    if (value === null || value === undefined || value === '') return '待補';
    const num = Number(value);
    if (!Number.isFinite(num) || num <= 0) return '待補';
    const decimals = options.decimals ?? (num < 100 ? 2 : 0);
    return 'NT$ ' + num.toLocaleString('zh-TW', {
        minimumFractionDigits: decimals,
        maximumFractionDigits: decimals,
    });
}

function formatGrowthDetailPrice(price, side) {
    if (!price) return '待補';
    const isUnit = price.price_basis === 'unit_price';
    const unit = price.unit_label ? ` / ${price.unit_label}` : '';
    if (isUnit) {
        const value = side === 'pchome' ? price.pchome_unit_price : price.momo_unit_price;
        return `${formatPriceAmount(value, { decimals: 2 })}${unit}`;
    }
    const value = side === 'pchome' ? price.pchome_price : price.momo_price;
    return formatPriceAmount(value);
}

function formatGapDisplay(gap) {
    if (gap === null || gap === undefined || !Number.isFinite(Number(gap))) return '待判斷';
    const num = Number(gap);
    if (num < 0) return `PChome 貴 ${Math.abs(num).toFixed(1)}%`;
    if (num > 0) return `PChome 便宜 ${num.toFixed(1)}%`;
    return '價格接近';
}

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

function safeHttpUrl(value) {
    try {
        const url = new URL(String(value || ''), window.location.origin);
        return ['http:', 'https:'].includes(url.protocol) ? url.href : '';
    } catch (_) {
        return '';
    }
}

function formatDataSourceLabel(value) {
    const sourceMap = Object.fromEntries([
        ['momo' + '_reference', 'MOMO 參考價'],
        ['external' + '_offers', '外部參考價'],
        ['legacy' + '_competitor' + '_cache', '既有比價資料'],
        ['targeted' + '_momo' + '_search', 'MOMO 補抓'],
        ['targeted' + '_momo' + '_review', 'MOMO 待確認'],
        ['manual' + '_csv', '手動匯入'],
        ['official' + '_api', '官方來源'],
        ['provider' + '_api', '供應商來源'],
    ]);
    const key = String(value || '').trim();
    if (!key) return '未填來源';
    return sourceMap[key] || key.replace(/[_-]+/g, ' ');
}

function formatReviewCandidateGap(gapPct) {
    if (gapPct === null || gapPct === undefined || !Number.isFinite(Number(gapPct))) {
        return { label: '待比價', className: 'is-review' };
    }
    const num = Number(gapPct);
    if (num > 5) return { label: `PChome 便宜 ${num.toFixed(1)}%`, className: 'is-win' };
    if (num < -5) return { label: `PChome 貴 ${Math.abs(num).toFixed(1)}%`, className: 'is-risk' };
    return { label: '價格接近', className: 'is-review' };
}

function renderReasonChips(labels) {
    const clean = (Array.isArray(labels) ? labels : [])
        .map((label) => String(label || '').trim())
        .filter(Boolean)
        .slice(0, 3);
    const chips = clean.length ? clean : ['確認品名、容量、色號與組合'];
    return chips.map((label) => `<span class="review-candidate-pill is-review">${escapeHtml(label)}</span>`).join('');
}

function renderProductThumb(imageUrl, altText) {
    const safeUrl = safeHttpUrl(imageUrl);
    if (!safeUrl) {
        return '<span class="review-candidate-thumb is-missing"><i class="fas fa-image"></i><span>待補圖片</span></span>';
    }
    return `<span class="review-candidate-thumb"><img src="${escapeHtml(safeUrl)}" alt="${escapeHtml(altText || '商品圖')}" loading="lazy" onerror="this.closest('.review-candidate-thumb').outerHTML='<span class=&quot;review-candidate-thumb is-missing&quot;><i class=&quot;fas fa-image&quot;></i><span>待補圖片</span></span>'"></span>`;
}

function scrollToPanel(panelId) {
    const panel = document.getElementById(panelId);
    if (!panel) return;
    panel.scrollIntoView({ behavior: 'smooth', block: 'start' });
}

function handleDrilldownKey(event, panelId) {
    if (event.key !== 'Enter' && event.key !== ' ') return;
    event.preventDefault();
    scrollToPanel(panelId);
}

function handleGrowthDetailKey(event, kind) {
    if (event.key !== 'Enter' && event.key !== ' ') return;
    event.preventDefault();
    showGrowthDetail(kind);
}

function handlePriceRiskKey(event, risk) {
    if (event.key !== 'Enter' && event.key !== ' ') return;
    event.preventDefault();
    showPriceRiskDetail(risk);
}

function clampPercent(value) {
    return Math.max(0, Math.min(100, Number(value || 0)));
}

function setWidth(elementId, pct) {
    const element = document.getElementById(elementId);
    if (!element) return;
    element.style.width = `${clampPercent(pct)}%`;
}

function formatCount(value) {
    return Number(value || 0).toLocaleString();
}

function setActionBusy(actionName, busy) {
    document.querySelectorAll(`[data-action="${actionName}"]`).forEach((button) => {
        button.disabled = busy;
        button.classList.toggle('is-busy', busy);
    });
}

function showToast(type, message, delay = 5000) {
    const toast = document.getElementById('triggerToast');
    const msg = document.getElementById('triggerToastMsg');
    if (!toast || !msg) return;

    const isSuccess = type === 'success';
    toast.className = 'toast align-items-center text-white border-0 ' + (isSuccess ? 'bg-success' : 'bg-danger');
    msg.textContent = message || (isSuccess ? '已完成' : '操作失敗，請稍後再試。');

    if (window.bootstrap && window.bootstrap.Toast) {
        new window.bootstrap.Toast(toast, { delay }).show();
    }
}

async function readJsonResponse(response) {
    const contentType = response.headers.get('content-type') || '';
    if (contentType.includes('application/json')) {
        return response.json();
    }

    await response.text();
    if (response.status === 401 || response.status === 403) {
        throw new Error('登入狀態已過期，請重新登入。');
    }
    throw new Error(`伺服器回傳非預期內容，狀態碼 ${response.status || '未知'}。`);
}

function renderOpsCommandDashboard(stats, scope = {}) {
    latestGrowthStats = stats || {};
    const candidateCount = Number(stats.candidate_count || 0);
    const mappedCount = Number(stats.mapped_count || 0);
    const needsMapping = Number(stats.needs_mapping_count || 0);
    const reviewCandidateCount = Number(stats.review_candidate_count || 0);
    const readyRate = candidateCount ? Math.round((mappedCount / candidateCount) * 100) : 0;

    document.getElementById('opsReadyRate').textContent = `${readyRate}%`;
    document.getElementById('opsFunnelCandidateText').textContent = `${formatCount(candidateCount)} 件`;
    document.getElementById('opsFunnelMappedText').textContent = `${formatCount(mappedCount)} 件`;
    document.getElementById('opsFunnelNeedsText').textContent = `${formatCount(needsMapping)} 件`;
    document.getElementById('opsDashboardStatus').textContent = candidateCount
        ? `資料就緒率 ${readyRate}%`
        : '等待 PChome 業績資料';
    renderNextAction(candidateCount, mappedCount, needsMapping, reviewCandidateCount);

    setWidth('opsFunnelCandidateBar', candidateCount ? 100 : 0);
    setWidth('opsFunnelMappedBar', candidateCount ? (mappedCount / candidateCount) * 100 : 0);
    setWidth('opsFunnelNeedsBar', candidateCount ? (needsMapping / candidateCount) * 100 : 0);
    renderOpsSourceBars(stats.external_data_source_counts || {}, scope);
    renderGrowthExecutiveSummary(stats);
    renderGrowthCommandCenter(stats, latestGrowthRows);
}

function setText(id, value) {
    const element = document.getElementById(id);
    if (element) element.textContent = value;
}

function setCommandTask(title, copy, label, action) {
    setText('commandTaskTitle', title);
    setText('commandTaskCopy', copy);
    const button = document.getElementById('commandTaskButton');
    if (!button) return;
    button.textContent = label || '查看清單';
    button.onclick = action || (() => scrollToPanel('growthOpsPanel'));
}

function renderGrowthCommandCenter(stats = {}, rows = []) {
    const safeRows = Array.isArray(rows) ? rows : [];
    const candidateCount = Number(stats.candidate_count || safeRows.length || 0);
    const mappedCount = Number(stats.mapped_count || 0);
    const needsMapping = Number(stats.needs_mapping_count || 0);
    const reviewCandidateCount = Number(stats.review_candidate_count || 0);
    const decliningCount = Number(stats.declining_product_count || 0);
    const activeCount = Number(stats.active_product_count || 0);
    const sales7d = Number(stats.overall_sales_7d || stats.total_sales_7d || 0);
    const prevSales7d = Number(stats.overall_sales_prev_7d || 0);
    const salesDeltaPct = Number(stats.overall_sales_delta_pct || 0);
    const matchRate = Number(stats.mapping_rate || (candidateCount ? (mappedCount / candidateCount) * 100 : 0));
    const latestSalesDate = String(stats.overall_latest_sales_date || stats.latest_sales_date || '').slice(0, 10);
    const actionCounts = stats.action_code_counts || {};
    const pressureCount = Number(actionCounts.review_price_or_promo || 0);
    const advantageCount = Number(actionCounts.amplify_price_advantage || 0);
    const pendingCount = Math.max(0, needsMapping + reviewCandidateCount);
    const readyShare = candidateCount ? (advantageCount / candidateCount) * 100 : 0;
    const pressureShare = candidateCount ? (pressureCount / candidateCount) * 100 : 0;

    setText('commandSales7d', sales7d ? formatMoney(sales7d) : '—');
    setText('commandSalesDelta', prevSales7d
        ? `較前 7 天 ${salesDeltaPct >= 0 ? '+' : ''}${salesDeltaPct.toFixed(1)}%`
        : '等待前期比較');
    setText('commandMatchRate', `${Math.round(matchRate * 10) / 10}%`);
    setText('commandMappedRatio', `${formatCount(mappedCount)} / ${formatCount(candidateCount)} 個高業績商品已對應`);
    setText('commandDecliningProducts', formatCount(decliningCount));
    setText('commandActiveProducts', `${formatCount(activeCount)} 個有銷售商品`);
    setText('commandNeedMapping', formatCount(needsMapping));
    setText('commandReviewCount', formatCount(reviewCandidateCount));
    setText('commandTopCategory', stats.top_category || '—');
    setText('commandTopCategorySales', stats.top_category_sales_7d ? formatMoney(stats.top_category_sales_7d) : '等待分類業績');
    setText('commandStatusReady', formatCount(mappedCount));
    setText('commandStatusReview', formatCount(reviewCandidateCount));
    setText('commandStatusNeeds', formatCount(needsMapping));
    setText('commandLatestSalesDate', latestSalesDate || '—');
    setText('commandPriceAdvantage', `${formatCount(advantageCount)} 件`);
    setText('commandPricePressure', `${formatCount(pressureCount)} 件`);
    setText('commandPricePending', `${formatCount(pendingCount)} 件`);
    setText('commandDonutText', `${Math.round(matchRate)}%`);

    const status = document.getElementById('growthCommandStatus');
    if (status) {
        const text = latestSalesDate ? `上次更新 ${latestSalesDate}` : '等待資料';
        status.innerHTML = `<i class="fas fa-circle-check"></i><span>${escapeHtml(text)}</span>`;
    }
    const rateBar = document.getElementById('commandMatchRateBar');
    if (rateBar) rateBar.style.width = `${clampPercent(matchRate)}%`;
    const donut = document.getElementById('commandPriceDonut');
    if (donut) {
        donut.style.setProperty('--ready', clampPercent(readyShare));
        donut.style.setProperty('--review', clampPercent(pressureShare));
    }

    if (!candidateCount) {
        setCommandTask(
            '今天先做：更新 PChome 業績',
            '先確認最新業績已匯入。',
            '查看業績',
            () => { window.location.href = '/daily_sales'; }
        );
    } else if (reviewCandidateCount > 0) {
        setCommandTask(
            `今天先做：確認 ${formatCount(reviewCandidateCount)} 筆 MOMO 候選`,
            '確認同款後才進價格判斷。',
            '確認候選',
            () => scrollToPanel('growthReviewPanel')
        );
    } else if (needsMapping > mappedCount) {
        setCommandTask(
            `今天先做：補齊 ${formatCount(needsMapping)} 件比價`,
            '先補 MOMO 對應，再判斷價格。',
            '補齊比價',
            () => backfillPchomeMatches()
        );
    } else {
        setCommandTask(
            '今天先做：檢查 MOMO 低價壓力商品',
            `可處理 ${formatCount(mappedCount)} 件，先看 MOMO 價格較低的商品。`,
            '檢查價格',
            () => showGrowthDetail('risk')
        );
    }

    renderGrowthCommandTopDecliners(safeRows);
}

function renderGrowthCommandTopDecliners(rows) {
    const tbody = document.getElementById('commandTopDecliners');
    if (!tbody) return;
    const sortedRows = [...rows]
        .filter((row) => Number(row.sales_7d || 0) > 0)
        .sort((a, b) => Number(b.sales_7d || 0) - Number(a.sales_7d || 0))
        .slice(0, 5);

    if (!sortedRows.length) {
        tbody.innerHTML = '<tr><td class="text-muted">目前沒有可顯示的下滑商品。</td></tr>';
        return;
    }

    tbody.innerHTML = sortedRows.map((row, index) => {
        const delta = Number(row.sales_delta_pct || 0);
        const action = row.recommended_action?.label || '查看商品';
        const trendText = delta
            ? `${delta > 0 ? '+' : ''}${delta.toFixed(1)}%`
            : '趨勢待補';
        return `<tr>
            <td>
                <span class="growth-command-meta momo-mono">#${index + 1} · ${escapeHtml(row.category || '未分類')}</span>
                <button type="button" class="growth-command-panel-link growth-command-product" data-growth-action="show-product-detail" data-product-key="${escapeHtml(row.pchome_product_id || '')}">
                    ${escapeHtml(row.product_name || row.pchome_product_id || '未命名商品')}
                </button>
            </td>
            <td class="text-end">
                <strong class="momo-mono">${escapeHtml(formatMoney(row.sales_7d || 0))}</strong>
                <span class="growth-command-meta">${escapeHtml(trendText)}</span>
            </td>
            <td class="text-end"><span class="growth-command-badge">${escapeHtml(action)}</span></td>
        </tr>`;
    }).join('');
}

function renderNextAction(candidateCount, mappedCount, needsMapping, reviewCandidateCount = 0) {
    const title = document.getElementById('nextActionTitle');
    const reason = document.getElementById('nextActionReason');
    const button = document.getElementById('nextActionButton');
    if (!title || !reason || !button) return;

    if (!candidateCount) {
        title.textContent = '今天先做：更新 PChome 業績';
        reason.textContent = '還不能產生今日清單，請先確認最新 PChome 業績是否已更新。';
        button.textContent = '查看業績頁';
        delete button.dataset.action;
        button.onclick = () => { window.location.href = '/daily_sales'; };
        return;
    }

    if (needsMapping > 0 && mappedCount === 0) {
        if (reviewCandidateCount > 0) {
            title.textContent = `今天先做：確認 ${formatCount(reviewCandidateCount)} 筆 MOMO 候選`;
            reason.textContent = '候選已找到，先確認同款或排除，確認後才會進入價格判斷。';
            button.textContent = '確認候選';
            delete button.dataset.action;
            button.onclick = () => scrollToPanel('growthReviewPanel');
            return;
        }
        title.textContent = `今天先做：補齊 ${formatCount(needsMapping)} 件比價資料`;
        reason.textContent = '這些商品有業績，但目前還看不到 MOMO 參考價，請先補齊比價資料。';
        button.textContent = '補齊比價資料';
        button.dataset.action = 'backfill';
        button.onclick = () => backfillPchomeMatches();
        return;
    }

    if (needsMapping > mappedCount) {
        if (reviewCandidateCount > 0) {
            title.textContent = `今天先做：確認 ${formatCount(reviewCandidateCount)} 筆 MOMO 候選`;
            reason.textContent = '先把已找到的候選變成可用資料，再補剩下找不到同款的商品。';
            button.textContent = '確認候選';
            delete button.dataset.action;
            button.onclick = () => scrollToPanel('growthReviewPanel');
            return;
        }
        title.textContent = `今天先做：補齊 ${formatCount(needsMapping)} 件比價資料`;
        reason.textContent = '目前無法比價的商品比可處理商品多，請先補齊商品對應。';
        button.textContent = '補齊比價資料';
        button.dataset.action = 'backfill';
        button.onclick = () => backfillPchomeMatches();
        return;
    }

    title.textContent = '今天先做：檢查價格風險';
    reason.textContent = `已有 ${formatCount(mappedCount)} 件商品可處理，先看 PChome 價格偏高的商品，再看可以主推的價格優勢商品。`;
    button.textContent = '檢查價格';
    delete button.dataset.action;
    button.onclick = () => scrollToPanel('externalPricePanel');
}

function renderGrowthExecutiveSummary(stats = {}) {
    const candidateCount = Number(stats.candidate_count || 0);
    const mappedCount = Number(stats.mapped_count || 0);
    const needsMapping = Number(stats.needs_mapping_count || 0);
    const reviewCandidateCount = Number(stats.review_candidate_count || 0);
    const latestSalesDate = String(stats.latest_sales_date || '').slice(0, 10);

    document.getElementById('growthExecReady').textContent = formatCount(mappedCount);
    document.getElementById('growthExecGap').textContent = formatCount(needsMapping);
    document.getElementById('growthExecLatestDate').textContent = latestSalesDate || '—';
    document.getElementById('growthExecLatestDetail').textContent = latestSalesDate ? '已接到 PChome 業績' : '尚未確認業績日期';

    const task = document.getElementById('growthExecTask');
    const detail = document.getElementById('growthExecTaskDetail');
    const gapCard = document.getElementById('growthExecGapCard');
    if (!task || !detail) return;

    if (!candidateCount) {
        task.textContent = '先更新 PChome 業績';
        detail.textContent = '目前還沒有足夠資料，請確認最新業績檔是否已匯入。';
        gapCard?.classList.remove('is-gap');
        return;
    }

    if (needsMapping > 0 && mappedCount === 0) {
        if (reviewCandidateCount > 0) {
            task.textContent = `先確認 ${formatCount(reviewCandidateCount)} 筆候選`;
            detail.textContent = '候選商品已找到，確認同款後才能進入價格判斷。';
            gapCard?.classList.add('is-gap');
            return;
        }
        task.textContent = `先補 ${formatCount(needsMapping)} 件 MOMO 參考`;
        detail.textContent = '高業績商品還不能比價，先補對應資料才會有可行動建議。';
        gapCard?.classList.add('is-gap');
        return;
    }

    if (needsMapping > mappedCount) {
        if (reviewCandidateCount > 0) {
            task.textContent = `先確認 ${formatCount(reviewCandidateCount)} 筆候選`;
            detail.textContent = '先處理候選清單，再補剩下找不到同款的商品。';
            gapCard?.classList.add('is-gap');
            return;
        }
        task.textContent = `先補比價，再處理 ${formatCount(mappedCount)} 件`;
        detail.textContent = '待補比價比可處理商品多，先擴大 MOMO 對應覆蓋率。';
        gapCard?.classList.add('is-gap');
        return;
    }

    task.textContent = '先檢查價格風險';
    detail.textContent = `已有 ${formatCount(mappedCount)} 件商品可直接處理，先看 PChome 是否被外部低價壓住。`;
    gapCard?.classList.toggle('is-gap', needsMapping > 0);
}

function renderOpsSourceBars(counts, scope = {}) {
    const box = document.getElementById('opsSourceBars');
    const totalBox = document.getElementById('opsSourceTotal');
    if (!box || !totalBox) return;

    const entries = Object.entries(counts || {})
        .map(([label, count]) => [label, Number(count || 0)])
        .filter(([, count]) => count > 0);
    const total = entries.reduce((sum, [, count]) => sum + count, 0);
    totalBox.textContent = formatCount(total);

    if (!entries.length) {
        const active = (scope.active_external_sources || []).join('、') || '尚未接入';
        box.innerHTML = `<div class="ops-source-row">
            <div class="ops-source-meta"><span>${escapeHtml(active)}</span><strong>0 筆</strong></div>
            <div class="ops-source-track"><span class="ops-source-bar"></span></div>
        </div>`;
        return;
    }

    box.innerHTML = entries.slice(0, 4).map(([label, count], index) => {
        const pct = total ? (count / total) * 100 : 0;
        const cls = index % 2 ? ' is-secondary' : '';
        return `<div class="ops-source-row">
            <div class="ops-source-meta"><span>${escapeHtml(label)}</span><strong>${formatCount(count)} 筆</strong></div>
            <div class="ops-source-track"><span class="ops-source-bar${cls}" style="width:${clampPercent(pct)}%"></span></div>
        </div>`;
    }).join('');
}

async function loadGrowthOps(forceRefresh = false) {
    const list = document.getElementById('growthOpsList');
    if (forceRefresh) {
        list.innerHTML = `<div class="text-center py-4 text-muted">
            <div class="spinner-border spinner-border-sm me-2"></div>更新今日清單中...
        </div>`;
    }

    try {
        const url = '/api/ai/pchome-growth/opportunities?limit=50' + (forceRefresh ? '&refresh=1' : '');
        const res = await fetch(url);
        const data = await readJsonResponse(res);
        if (!data.success) throw new Error(data.error || '讀取失敗');

        const stats = data.stats || {};
        const scope = data.source_scope || {};
        document.getElementById('growthCandidateCount').textContent = (stats.candidate_count || 0).toLocaleString();
        document.getElementById('growthMappedCount').textContent = (stats.mapped_count || 0).toLocaleString();
        document.getElementById('growthNeedsMapping').textContent = (stats.needs_mapping_count || 0).toLocaleString();
        document.getElementById('growthReviewCandidateCount').textContent = (stats.review_candidate_count || 0).toLocaleString();
        renderOpsCommandDashboard(stats, scope);
        renderGrowthActionHint(stats);
        renderGrowthDataSourceSummary(stats);

        const active = (scope.active_external_sources || []).join('、') || '尚未接入';
        const paused = (scope.paused_external_sources || []).join('、') || '無';
        document.getElementById('growthSourceNote').textContent =
            `業績：${scope.primary_sales_source || 'PChome 後台業績'} · 外部：${active} · 暫停：${paused}`;

        renderGrowthSourceReadiness((scope.source_readiness || {}).sources || []);
        latestGrowthRows = data.opportunities || [];
        renderGrowthCommandCenter(stats, latestGrowthRows);
        renderGrowthOps(latestGrowthRows);
        renderGrowthDetail(activeGrowthDetailKind);
        loadGrowthReviewCandidates();
    } catch (error) {
        console.error(error);
        latestGrowthRows = [];
        renderGrowthCommandCenter({}, []);
        renderOpsCommandDashboard({}, {});
        renderGrowthActionHint({ candidate_count: 0, mapped_count: 0, needs_mapping_count: 0 });
        renderGrowthDataSourceSummary({});
        renderGrowthDetail('all');
        renderGrowthReviewCandidates([]);
        list.innerHTML = `<div class="text-center py-4 text-muted">
            <i class="fas fa-circle-exclamation d-block mb-2"></i>
            今日處理清單暫時讀不到，請重新整理；若仍失敗，請檢查業績資料。
        </div>`;
    }
}

function renderCompetitorSourceSummary(stats) {
    const target = document.getElementById('compSourceSummary');
    if (!target) return;

    const counts = stats.competitor_data_source_counts || {};
    const entries = Object.entries(counts)
        .filter(([, count]) => Number(count || 0) > 0)
        .map(([label, count]) => `${formatDataSourceLabel(label)} ${Number(count).toLocaleString()} 筆`);

    target.textContent = entries.length
        ? `資料來源：${entries.join('、')}`
        : '僅顯示已確認同款的商品';
}

function renderGrowthActionHint(stats) {
    const hint = document.getElementById('growthActionHint');
    if (!hint) return;

    const candidateCount = Number(stats.candidate_count || 0);
    const mappedCount = Number(stats.mapped_count || 0);
    const needsMapping = Number(stats.needs_mapping_count || 0);
    const reviewCandidateCount = Number(stats.review_candidate_count || 0);

    if (!candidateCount) {
        hint.textContent = '還不能產生今日清單，原因是缺少最新 PChome 業績。';
        return;
    }

    if (needsMapping > 0 && mappedCount === 0) {
        hint.textContent = `今天先補齊比價資料：目前 ${needsMapping.toLocaleString()} 件高業績商品還不能直接比價。`;
        return;
    }

    if (needsMapping > 0 && reviewCandidateCount > 0) {
        hint.textContent = `先處理 ${mappedCount.toLocaleString()} 件可立即處理商品；另有 ${reviewCandidateCount.toLocaleString()} 筆候選等人工確認。`;
        return;
    }

    if (needsMapping > 0) {
        hint.textContent = `先處理 ${mappedCount.toLocaleString()} 件可立即處理商品，再補 ${needsMapping.toLocaleString()} 件比價資料。`;
        return;
    }

    hint.textContent = '目前商品都可比價，先檢查售價偏高或可以放大價格優勢的項目。';
}

function renderGrowthDataSourceSummary(stats) {
    const summary = document.getElementById('growthDataSourceSummary');
    if (!summary) return;

    const counts = stats.external_data_source_counts || {};
    const entries = Object.entries(counts)
        .filter(([, count]) => Number(count || 0) > 0)
        .map(([label, count]) => `${formatDataSourceLabel(label)} ${Number(count).toLocaleString()} 筆`);

    if (!entries.length) {
        summary.textContent = '來源：尚未接到外部參考價';
        return;
    }

    summary.textContent = `來源：${entries.join('、')}`;
}

function renderGrowthSourceReadiness(sources) {
    const box = document.getElementById('growthSourceReadiness');
    if (!box) return;
    if (!sources.length) {
        box.innerHTML = `<div class="growth-source-chip">
            <div class="growth-source-name">
                <span>外部資料來源</span>
                <span class="growth-source-status">未確認</span>
            </div>
            <p class="growth-source-detail">目前還沒有可顯示的來源狀態。</p>
        </div>`;
        return;
    }

    box.innerHTML = sources.map((source) => {
        const usable = Number(source.usable_offer_count || 0);
        const detail = usable > 0
            ? `${source.data_quality_label || '資料可用'} · ${usable.toLocaleString()} 筆`
            : `${source.plain_state || source.plain_note || '等待資料接入'}`;
        const activeClass = source.status_code === 'active' ? ' is-active' : '';
        return `<div class="growth-source-chip${activeClass}">
            <div class="growth-source-name">
                <span>${escapeHtml(source.display_name || '未命名來源')}</span>
                <span class="growth-source-status">${escapeHtml(source.status_label || '待確認')}</span>
            </div>
            <p class="growth-source-detail">${escapeHtml(detail)}</p>
        </div>`;
    }).join('');
}

function resolveGrowthDetailKind(kind) {
    if (kind !== 'task') return kind || 'all';
    const reviewCandidateCount = Number(latestGrowthStats.review_candidate_count || 0);
    const needsMapping = Number(latestGrowthStats.needs_mapping_count || 0);
    const mappedCount = Number(latestGrowthStats.mapped_count || 0);
    if (reviewCandidateCount > 0) return 'review';
    if (needsMapping > mappedCount) return 'needs';
    return 'risk';
}

function growthDetailConfig(kind) {
    const topCategory = latestGrowthStats.top_category || '';
    const selectedCategory = activeGrowthCategoryFilter || topCategory;
    const configs = {
        all: ['今日商品明細', '依優先級排序，先看高業績、下滑或價格有壓力的商品。'],
        ready: ['可立即處理商品', '已有 MOMO 參考價，可以直接檢查售價、活動或曝光。'],
        needs: ['缺比價商品', '有 PChome 業績，但還沒有可用 MOMO 參考。'],
        review: ['MOMO 候選待確認', '候選已找到，確認同款後才會進入價格判斷。'],
        risk: ['MOMO 低價壓力商品', 'MOMO 參考價較低，優先檢查 PChome 售價、券或組合。'],
        advantage: ['PChome 價格優勢商品', 'PChome 目前較有價格優勢，適合檢查曝光與主推位置。'],
        bundle: ['組合 / 單位價商品', '需要檢查組合包、入數、容量或單位價，避免只看總價誤判。'],
        source: ['外部價格來源明細', '只列出已接到 MOMO 外部參考價的商品。'],
        decline: ['業績下滑商品', '近 7 天比前 7 天下滑的商品。'],
        category: [selectedCategory ? `${selectedCategory} 商品明細` : '分類商品明細', '這個分類內的商品與價格狀態。'],
    };
    return configs[kind] || configs.all;
}

function growthDetailRows(kind) {
    const rows = Array.isArray(latestGrowthRows) ? [...latestGrowthRows] : [];
    const selectedCategory = activeGrowthCategoryFilter || latestGrowthStats.top_category || '';
    let filtered = rows.filter((row) => {
        const actionCode = row.recommended_action?.code || '';
        const price = row.external_price || null;
        const gap = price && price.gap_pct !== null && price.gap_pct !== undefined ? Number(price.gap_pct) : null;
        if (kind === 'ready') return Boolean(price);
        if (kind === 'needs') return !price && !row.review_candidate;
        if (kind === 'review') return Boolean(row.review_candidate) || actionCode === 'review_external_candidate';
        if (kind === 'risk') return Boolean(price) && (actionCode === 'review_price_or_promo' || (gap !== null && gap < -5));
        if (kind === 'advantage') return Boolean(price) && (actionCode === 'amplify_price_advantage' || (gap !== null && gap > 5));
        if (kind === 'bundle') return isBundleOrUnitRow(row);
        if (kind === 'source') return Boolean(price);
        if (kind === 'decline') return Number(row.sales_delta_pct || 0) < 0;
        if (kind === 'category') return selectedCategory && row.category === selectedCategory;
        return true;
    });

    const search = String(growthDetailSearchText || '').trim().toLowerCase();
    if (search) {
        filtered = filtered.filter((row) => {
            const price = row.external_price || {};
            const candidate = row.review_candidate || {};
            const haystack = [
                row.product_name,
                row.category,
                row.vendor,
                row.pchome_product_id,
                row.recommended_action?.label,
                price.momo_name,
                price.momo_sku,
                candidate.momo_name,
                candidate.momo_sku,
            ].filter(Boolean).join(' ').toLowerCase();
            return haystack.includes(search);
        });
    }

    const gapValue = (row) => {
        const gap = row.external_price?.gap_pct;
        return gap === null || gap === undefined || !Number.isFinite(Number(gap)) ? null : Number(gap);
    };
    const declineValue = (row) => {
        const value = row.sales_delta_pct;
        return value === null || value === undefined || !Number.isFinite(Number(value)) ? null : Number(value);
    };
    const nullLast = (a, b, getter, direction = 'desc') => {
        const av = getter(a);
        const bv = getter(b);
        if (av === null && bv === null) return Number(b.priority_score || 0) - Number(a.priority_score || 0);
        if (av === null) return 1;
        if (bv === null) return -1;
        return direction === 'asc' ? av - bv : bv - av;
    };

    return filtered.sort((a, b) => {
        if (activeGrowthDetailSort === 'sales') return Number(b.sales_7d || 0) - Number(a.sales_7d || 0);
        if (activeGrowthDetailSort === 'gap') return nullLast(a, b, (row) => {
            const gap = gapValue(row);
            return gap === null ? null : Math.abs(gap);
        });
        if (activeGrowthDetailSort === 'decline') return nullLast(a, b, declineValue, 'asc');
        if (activeGrowthDetailSort === 'quality') return rowQualityScore(b) - rowQualityScore(a);
        return Number(b.priority_score || 0) - Number(a.priority_score || 0);
    });
}

function showGrowthDetail(kind, shouldScroll = true) {
    activeGrowthDetailKind = resolveGrowthDetailKind(kind);
    if (activeGrowthDetailKind !== 'category') {
        activeGrowthCategoryFilter = '';
    }
    renderGrowthDetail(activeGrowthDetailKind);
    if (shouldScroll) scrollToPanel('growthDrilldownPanel');
}

function setGrowthDetailSearch(value) {
    growthDetailSearchText = String(value || '').trim().toLowerCase();
    renderGrowthDetail(activeGrowthDetailKind);
}

function setGrowthDetailSort(value) {
    const allowed = new Set(['priority', 'sales', 'gap', 'decline', 'quality']);
    activeGrowthDetailSort = allowed.has(value) ? value : 'priority';
    renderGrowthDetail(activeGrowthDetailKind);
}

function clearGrowthDetailFilters() {
    growthDetailSearchText = '';
    activeGrowthDetailSort = 'priority';
    const search = document.getElementById('growthDetailSearch');
    const sort = document.getElementById('growthDetailSort');
    if (search) search.value = '';
    if (sort) sort.value = 'priority';
    renderGrowthDetail(activeGrowthDetailKind);
}

function growthRowKey(row) {
    return String(row?.pchome_product_id || row?.product_name || '').trim();
}

function findGrowthRowByKey(productKey) {
    const key = String(productKey || '').trim();
    if (!key) return null;
    return (Array.isArray(latestGrowthRows) ? latestGrowthRows : [])
        .find((row) => growthRowKey(row) === key) || null;
}

function growthRowNextAction(row) {
    const code = row?.recommended_action?.code || '';
    if (code === 'review_external_candidate') {
        return { action: 'review-candidate', label: '確認候選' };
    }
    if (code === 'map_external_product') {
        return { action: 'backfill', label: '補齊比價' };
    }
    return { action: 'focus-price', label: '檢查價格' };
}

function hideGrowthProductDecision() {
    activeGrowthProductKey = '';
    const panel = document.getElementById('growthProductDecisionPanel');
    if (!panel) return;
    panel.classList.add('is-hidden');
    panel.innerHTML = '';
}

function productEvidenceTile(label, value, note, className = '') {
    return `<span class="growth-price-chip${className}">
        <span class="growth-price-label">${escapeHtml(label)}</span>
        <span class="growth-price-value">${escapeHtml(value)}</span>
        <span class="growth-price-note">${escapeHtml(note)}</span>
    </span>`;
}

function showGrowthProductDetail(productKey) {
    const row = findGrowthRowByKey(productKey);
    const panel = document.getElementById('growthProductDecisionPanel');
    if (!row || !panel) return;

    activeGrowthProductKey = growthRowKey(row);
    const price = row.external_price || null;
    const reviewCandidate = row.review_candidate || null;
    const action = row.recommended_action || {};
    const next = growthRowNextAction(row);
    const gap = price && price.gap_pct !== null && price.gap_pct !== undefined ? Number(price.gap_pct) : null;
    const delta = row.sales_delta_pct === null || row.sales_delta_pct === undefined
        ? '前期不足'
        : `${Number(row.sales_delta_pct).toFixed(1)}%`;
    const qualityScore = Math.round(rowQualityScore(row));
    const qualityLabel = row.data_quality?.label || (price ? '資料可用' : reviewCandidate ? '候選待確認' : '資料待補');
    const pchomeDisplay = price
        ? formatGrowthDetailPrice(price, 'pchome')
        : reviewCandidate
            ? formatPriceAmount(reviewCandidate.pchome_price)
            : '待補';
    const momoDisplay = price
        ? formatGrowthDetailPrice(price, 'momo')
        : reviewCandidate
            ? formatPriceAmount(reviewCandidate.momo_price)
            : '待補';
    const gapText = reviewCandidate
        ? '候選待確認'
        : gap === null
            ? '缺 MOMO 參考'
            : formatGapDisplay(gap);
    const gapClass = gap !== null && gap < 0
        ? ' is-risk'
        : gap !== null && gap > 0
            ? ' is-good'
            : '';
    const reasons = Array.isArray(row.reason_lines) && row.reason_lines.length
        ? row.reason_lines.slice(0, 5)
        : [
            action.label || '依業績、比價與資料可信度排序',
            price ? '已有 MOMO 參考價，可直接檢查售價與活動' : '尚缺完整外部參考，先補齊同款資料',
        ];
    const productKeySafe = escapeHtml(activeGrowthProductKey);
    const nextButtonAttrs = next.action === 'backfill'
        ? 'data-growth-action="backfill"'
        : `data-growth-action="${next.action}" data-product-key="${productKeySafe}"`;

    panel.innerHTML = `<div class="growth-product-head">
        <div>
            <div class="growth-product-kicker">單品作戰詳情</div>
            <h3 class="growth-product-title">${escapeHtml(row.product_name || '未命名商品')}</h3>
            <p class="growth-product-meta">
                ${escapeHtml(row.category || '未分類')} · 近 7 天 ${escapeHtml(formatMoney(row.sales_7d))} · 業績變化 ${escapeHtml(delta)} · ${escapeHtml(action.label || '待判斷')}
            </p>
        </div>
        <button type="button" class="growth-product-close" onclick="hideGrowthProductDecision()">關閉</button>
    </div>
    <div class="growth-product-body">
        <div class="growth-product-evidence-grid">
            ${productEvidenceTile('PChome', pchomeDisplay, price?.price_basis_label || (reviewCandidate ? '候選價' : '待補資料'))}
            ${productEvidenceTile('MOMO', momoDisplay, price?.data_source_label || (reviewCandidate ? '候選待確認' : '等待補抓'))}
            ${productEvidenceTile('差距', gapText, action.label || '待判斷', gapClass)}
            ${productEvidenceTile('可信度', qualityScore ? `${qualityScore}%` : '待補', qualityLabel)}
        </div>
        <ul class="growth-product-reason-list">
            ${reasons.map((reason) => `<li>${escapeHtml(reason)}</li>`).join('')}
        </ul>
    </div>
    <div class="growth-product-actions">
        <button type="button" class="btn btn-sm btn-outline-primary table-row-action" ${nextButtonAttrs}>${escapeHtml(next.label)}</button>
        <button type="button" class="btn btn-sm btn-outline-secondary table-row-action" data-growth-action="focus-price" data-product-key="${productKeySafe}">查看比價表</button>
    </div>`;
    panel.classList.remove('is-hidden');
    document.querySelectorAll('.growth-detail-row.is-active')
        .forEach((item) => item.classList.remove('is-active'));
    Array.from(document.querySelectorAll('.growth-detail-row'))
        .find((rowElement) => Array.from(rowElement.querySelectorAll('[data-growth-action="show-product-detail"]'))
            .some((item) => item.dataset.productKey === activeGrowthProductKey))
        ?.classList.add('is-active');
    panel.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}

function classifyGrowthRow(row) {
    const actionCode = row?.recommended_action?.code || '';
    const price = row?.external_price || null;
    const candidate = row?.review_candidate || null;
    const gap = price && price.gap_pct !== null && price.gap_pct !== undefined ? Number(price.gap_pct) : null;
    return {
        hasPrice: Boolean(price),
        needs: !price && !candidate,
        review: Boolean(candidate) || actionCode === 'review_external_candidate',
        risk: Boolean(price) && (actionCode === 'review_price_or_promo' || (gap !== null && gap < -5)),
        advantage: Boolean(price) && (actionCode === 'amplify_price_advantage' || (gap !== null && gap > 5)),
    };
}

function summarizeGrowthCategories() {
    const groups = new Map();
    (Array.isArray(latestGrowthRows) ? latestGrowthRows : []).forEach((row) => {
        const category = String(row.category || '未分類').trim() || '未分類';
        if (!groups.has(category)) {
            groups.set(category, {
                category,
                count: 0,
                sales: 0,
                risk: 0,
                advantage: 0,
                needs: 0,
                review: 0,
            });
        }
        const item = groups.get(category);
        const flags = classifyGrowthRow(row);
        item.count += 1;
        item.sales += Number(row.sales_7d || 0);
        if (flags.risk) item.risk += 1;
        if (flags.advantage) item.advantage += 1;
        if (flags.needs) item.needs += 1;
        if (flags.review) item.review += 1;
    });
    return Array.from(groups.values())
        .sort((a, b) => b.sales - a.sales || b.count - a.count)
        .slice(0, 6);
}

function categoryActionText(item) {
    if (item.risk > 0) return `先檢查 ${formatCount(item.risk)} 件價格壓力`;
    if (item.advantage > 0) return `放大 ${formatCount(item.advantage)} 件價格優勢`;
    if (item.review > 0) return `先確認 ${formatCount(item.review)} 筆候選`;
    if (item.needs > 0) return `補齊 ${formatCount(item.needs)} 件比價`;
    return '維持追蹤';
}

function renderGrowthCategoryBoard() {
    const board = document.getElementById('growthCategoryBoard');
    if (!board) return;
    const list = board.querySelector('.growth-category-list');
    if (!list) return;

    const categories = summarizeGrowthCategories();
    if (!categories.length) {
        list.innerHTML = `<div class="text-center py-3 text-muted">還沒有可整理的分類資料。</div>`;
        return;
    }

    const maxSales = Math.max(1, ...categories.map((item) => item.sales));
    list.innerHTML = categories.map((item) => {
        const pct = clampPercent((item.sales / maxSales) * 100);
        const activeClass = activeGrowthDetailKind === 'category' && item.category === activeGrowthCategoryFilter
            ? ' is-active'
            : '';
        return `<button type="button" class="growth-category-row${activeClass}" data-growth-action="show-category-detail" data-category-name="${escapeHtml(item.category)}">
            <span>
                <span class="growth-category-name">${escapeHtml(item.category)}</span>
                <span class="growth-category-meta">${formatCount(item.count)} 件 · 近 7 天 ${escapeHtml(formatMoney(item.sales))}</span>
            </span>
            <span class="growth-category-bars">
                <span class="growth-category-track"><span class="growth-category-bar" style="width:${pct}%"></span></span>
                <span class="growth-category-stats">
                    <span>壓力 <strong>${formatCount(item.risk)}</strong></span>
                    <span>優勢 <strong>${formatCount(item.advantage)}</strong></span>
                    <span>缺比價 <strong>${formatCount(item.needs)}</strong></span>
                    <span>待確認 <strong>${formatCount(item.review)}</strong></span>
                </span>
            </span>
            <span class="growth-category-cta">${escapeHtml(categoryActionText(item))}</span>
        </button>`;
    }).join('');
}

function showGrowthCategoryDetail(category) {
    activeGrowthCategoryFilter = String(category || '').trim();
    if (!activeGrowthCategoryFilter) return;
    showGrowthDetail('category');
}

function isBundleOrUnitRow(row) {
    const price = row?.external_price || null;
    const name = String(row?.product_name || '');
    return Boolean(price) && (
        price.price_basis === 'unit_price'
        || /組|入|套|盒|包|ml|g|kg|公升|毫升/.test(name)
    );
}

function growthPlaybookRows(kind) {
    const rows = Array.isArray(latestGrowthRows) ? latestGrowthRows : [];
    if (kind === 'defense') return rows.filter((row) => classifyGrowthRow(row).risk);
    if (kind === 'boost') return rows.filter((row) => classifyGrowthRow(row).advantage);
    if (kind === 'bundle') return rows.filter(isBundleOrUnitRow);
    if (kind === 'data') return rows.filter((row) => {
        const flags = classifyGrowthRow(row);
        return flags.needs || flags.review;
    });
    return [];
}

function playbookTargetKind(kind) {
    if (kind === 'defense') return 'risk';
    if (kind === 'boost') return 'advantage';
    if (kind === 'bundle') return 'bundle';
    if (kind === 'data') return 'needs';
    return 'all';
}

function renderGrowthPlaybookBoard() {
    const board = document.getElementById('growthPlaybookBoard');
    if (!board) return;
    const grid = board.querySelector('.growth-playbook-grid');
    if (!grid) return;

    const playbooks = [
        {
            kind: 'defense',
            className: 'is-defense',
            label: '價格防守',
            action: '檢查售價 / 券 / 活動',
        },
        {
            kind: 'boost',
            className: 'is-boost',
            label: '主推曝光',
            action: '加強主推位置與文案',
        },
        {
            kind: 'bundle',
            className: 'is-bundle',
            label: '組合 / 單位價',
            action: '檢查組合包與單位價',
        },
        {
            kind: 'data',
            className: 'is-data',
            label: '資料補齊',
            action: '補候選或確認同款',
        },
    ];

    const summaries = playbooks.map((item) => {
        const rows = growthPlaybookRows(item.kind).sort((a, b) => Number(b.sales_7d || 0) - Number(a.sales_7d || 0));
        const sales = rows.reduce((sum, row) => sum + Number(row.sales_7d || 0), 0);
        return { ...item, rows, sales, topProduct: rows[0]?.product_name || '目前沒有商品' };
    });
    const maxSales = Math.max(1, ...summaries.map((item) => item.sales));

    grid.innerHTML = summaries.map((item) => {
        const pct = clampPercent((item.sales / maxSales) * 100);
        const targetKind = playbookTargetKind(item.kind);
        const activeClass = activeGrowthDetailKind === targetKind ? ' is-active' : '';
        return `<button type="button" class="growth-playbook-card ${item.className}${activeClass}" data-growth-action="show-playbook-detail" data-playbook-kind="${escapeHtml(item.kind)}">
            <span class="growth-playbook-label">${escapeHtml(item.label)}</span>
            <span class="growth-playbook-value">${formatCount(item.rows.length)}</span>
            <span class="growth-playbook-sales">近 7 天 ${escapeHtml(formatMoney(item.sales))}</span>
            <span class="growth-playbook-track"><span class="growth-playbook-bar" style="width:${pct}%"></span></span>
            <span class="growth-playbook-product">${escapeHtml(item.topProduct)}</span>
            <span class="growth-playbook-action">${escapeHtml(item.action)}</span>
        </button>`;
    }).join('');
}

function showGrowthPlaybookDetail(kind) {
    const target = playbookTargetKind(kind);
    showGrowthDetail(target);
}

function growthActionPlanForRow(row) {
    const flags = classifyGrowthRow(row);
    const next = growthRowNextAction(row);
    const price = row?.external_price || null;
    const gap = price && price.gap_pct !== null && price.gap_pct !== undefined ? Number(price.gap_pct) : null;
    if (flags.risk) {
        return {
            label: '價格防守',
            title: '檢查售價、券或活動',
            reason: gap === null ? 'MOMO 參考價較低，先檢查價格壓力。' : formatGapDisplay(gap),
            next,
        };
    }
    if (flags.advantage) {
        return {
            label: '主推曝光',
            title: '放大價格優勢',
            reason: gap === null ? 'PChome 有外部價格優勢，適合提高曝光。' : formatGapDisplay(gap),
            next,
        };
    }
    if (isBundleOrUnitRow(row)) {
        return {
            label: '組合 / 單位價',
            title: '檢查組合包與單位價',
            reason: '先確認容量、入數與單位價，再決定組合或券。',
            next,
        };
    }
    if (flags.review) {
        return {
            label: '候選確認',
            title: '確認 MOMO 候選是否同款',
            reason: '同款確認後才會進入價格判斷。',
            next,
        };
    }
    if (flags.needs) {
        return {
            label: '補資料',
            title: '補齊 MOMO 參考商品',
            reason: '目前缺外部參考價，先補資料才有可行動建議。',
            next,
        };
    }
    if (Number(row?.sales_delta_pct || 0) < 0) {
        return {
            label: '找回動能',
            title: '檢查下滑商品',
            reason: `近 7 天業績變化 ${Number(row.sales_delta_pct || 0).toFixed(1)}%。`,
            next,
        };
    }
    return {
        label: '追蹤',
        title: '維持追蹤',
        reason: '目前沒有明確價格或資料風險。',
        next,
    };
}

function growthActionEvidence(row) {
    const price = row?.external_price || null;
    const candidate = row?.review_candidate || null;
    const gap = price && price.gap_pct !== null && price.gap_pct !== undefined ? Number(price.gap_pct) : null;
    const quality = Math.round(rowQualityScore(row));
    return [
        {
            label: 'PChome',
            value: price ? formatGrowthDetailPrice(price, 'pchome') : candidate ? formatPriceAmount(candidate.pchome_price) : '待補',
        },
        {
            label: 'MOMO',
            value: price ? formatGrowthDetailPrice(price, 'momo') : candidate ? formatPriceAmount(candidate.momo_price) : '待補',
        },
        {
            label: '差距',
            value: candidate ? '候選待確認' : gap === null ? '待判斷' : formatGapDisplay(gap),
        },
        {
            label: '可信度',
            value: quality ? `${quality}%` : '待補',
        },
    ];
}

function renderGrowthActionEvidence(row) {
    return `<div class="growth-action-evidence">
        ${growthActionEvidence(row).map((item) => `<span class="growth-action-evidence-chip">
            <span class="growth-action-evidence-label">${escapeHtml(item.label)}</span>
            <span class="growth-action-evidence-value">${escapeHtml(item.value)}</span>
        </span>`).join('')}
    </div>`;
}

function renderGrowthActionBoard() {
    const board = document.getElementById('growthActionBoard');
    if (!board) return;
    const list = board.querySelector('.growth-action-list');
    if (!list) return;
    const rows = (Array.isArray(latestGrowthRows) ? latestGrowthRows : [])
        .map((row) => ({ row, plan: growthActionPlanForRow(row) }))
        .filter((item) => item.plan.label !== '追蹤')
        .sort((a, b) => Number(b.row.priority_score || 0) - Number(a.row.priority_score || 0))
        .slice(0, 5);

    if (!rows.length) {
        list.innerHTML = `<div class="text-center py-3 text-muted">目前沒有需要立即處理的商品。</div>`;
        return;
    }

    list.innerHTML = rows.map((item, index) => {
        const row = item.row;
        const plan = item.plan;
        const key = escapeHtml(growthRowKey(row));
        const nextAttrs = plan.next.action === 'backfill'
            ? 'data-growth-action="backfill"'
            : `data-growth-action="${plan.next.action}" data-product-key="${key}"`;
        return `<article class="growth-action-row">
            <span class="growth-action-rank">${String(index + 1).padStart(2, '0')}</span>
            <div>
                <p class="growth-action-title">${escapeHtml(plan.title)}</p>
                <p class="growth-action-meta">${escapeHtml(row.product_name || '未命名商品')}</p>
                <p class="growth-action-reason">${escapeHtml(plan.reason)}</p>
            </div>
            <div>
                <span class="growth-action-pill">${escapeHtml(plan.label)}</span>
                <p class="growth-action-meta">近 7 天 ${escapeHtml(formatMoney(row.sales_7d))}</p>
                ${renderGrowthActionEvidence(row)}
            </div>
            <div class="growth-action-buttons">
                <button type="button" class="btn btn-sm btn-outline-secondary table-row-action" data-growth-action="show-product-detail" data-product-key="${key}">詳情</button>
                <button type="button" class="btn btn-sm btn-outline-primary table-row-action" ${nextAttrs}>${escapeHtml(plan.next.label)}</button>
            </div>
        </article>`;
    }).join('');
}

function renderGrowthStrategySummary() {
    const box = document.getElementById('growthStrategyGrid');
    if (!box) return;

    const total = Math.max(1, (Array.isArray(latestGrowthRows) ? latestGrowthRows.length : 0));
    const strategies = [
        {
            kind: 'risk',
            className: 'is-risk',
            label: '價格壓力',
            note: '檢查售價、券或組合',
        },
        {
            kind: 'advantage',
            className: 'is-advantage',
            label: '價格優勢',
            note: '放大曝光與主推位置',
        },
        {
            kind: 'review',
            className: 'is-review',
            label: '待確認',
            note: '先確認同款再判斷價格',
        },
        {
            kind: 'needs',
            className: 'is-needs',
            label: '缺比價',
            note: '補抓 MOMO 候選',
        },
    ];

    box.innerHTML = strategies.map((strategy) => {
        const rows = growthDetailRows(strategy.kind);
        const sales = rows.reduce((sum, row) => sum + Number(row.sales_7d || 0), 0);
        const pct = clampPercent((rows.length / total) * 100);
        const activeClass = activeGrowthDetailKind === strategy.kind ? ' is-active' : '';
        return `<button type="button" class="growth-strategy-card ${strategy.className}${activeClass}" onclick="showGrowthDetail('${strategy.kind}')">
            <span class="growth-strategy-label">${escapeHtml(strategy.label)}</span>
            <span class="growth-strategy-value">${formatCount(rows.length)}</span>
            <span class="growth-strategy-note">${escapeHtml(strategy.note)} · ${escapeHtml(formatMoney(sales))}</span>
            <span class="growth-strategy-track"><span class="growth-strategy-bar" style="width:${pct}%"></span></span>
        </button>`;
    }).join('');
}

function growthDecisionConfig(kind) {
    const configs = {
        all: {
            title: '先處理最有機會影響業績的商品',
            copy: '先看價格壓力與價格優勢商品，再補待確認與缺比價資料。',
            actionLabel: '看第一筆',
        },
        ready: {
            title: '這批商品可以直接進入銷售判斷',
            copy: '已有 MOMO 參考價，先處理價差大且近 7 天業績有波動的商品。',
            actionLabel: '檢查價格',
        },
        risk: {
            title: '先檢查 MOMO 低價壓力',
            copy: 'MOMO 參考價較低，優先檢查售價、折扣券、組合包與頁面曝光。',
            actionLabel: '看價格',
        },
        advantage: {
            title: '放大 PChome 價格優勢',
            copy: 'PChome 有價格優勢，適合排主推、加強文案與提高曝光位置。',
            actionLabel: '看主推清單',
        },
        bundle: {
            title: '檢查組合包與單位價',
            copy: '先確認容量、入數與單位價，再決定要做組合、加券或調整主圖文案。',
            actionLabel: '看組合商品',
        },
        review: {
            title: '先確認候選是否同款',
            copy: '確認同款後才會進入價格判斷；色號、容量或組合不一致就排除。',
            actionLabel: '確認候選',
            action: 'review-candidate',
        },
        needs: {
            title: '先補齊 MOMO 對應商品',
            copy: '這批商品有 PChome 業績但缺外部參考價，先補抓候選再判斷價格。',
            actionLabel: '補齊比價',
            action: 'backfill',
        },
        source: {
            title: '檢查已接到外部價的商品',
            copy: '這些商品已有 MOMO 參考價，可直接比較價格與銷售變化。',
            actionLabel: '檢查價格',
        },
        decline: {
            title: '先找回下滑商品的銷售動能',
            copy: '近 7 天轉弱的商品，優先檢查價格、曝光、庫存與商品頁內容。',
            actionLabel: '看下滑商品',
        },
    };
    return configs[kind] || configs.all;
}

function rowQualityScore(row) {
    if (row?.data_quality?.score !== null && row?.data_quality?.score !== undefined) {
        return Number(row.data_quality.score || 0);
    }
    if (row?.review_candidate?.quality_score !== null && row?.review_candidate?.quality_score !== undefined) {
        return Number(row.review_candidate.quality_score || 0);
    }
    if (row?.external_price?.match_score) return Number(row.external_price.match_score || 0) * 100;
    return 0;
}

function renderGrowthDecisionSummary(rows, kind) {
    const box = document.getElementById('growthDecisionSummary');
    if (!box) return;
    rows = Array.isArray(rows) ? rows : [];
    const config = growthDecisionConfig(kind);
    if (!rows.length) {
        box.innerHTML = `<div>
            <h3 class="growth-decision-title">${escapeHtml(config.title)}</h3>
            <p class="growth-decision-copy">目前沒有符合這個條件的商品。</p>
            <div class="growth-decision-metrics">
                <span class="growth-decision-metric">商品 <strong>0</strong> 件</span>
                <span class="growth-decision-metric">近 7 天 <strong>NT$ 0</strong></span>
                <span class="growth-decision-metric">可信度 <strong>待補</strong></span>
                <span class="growth-decision-metric">最大價差 <strong>待判斷</strong></span>
            </div>
        </div>
        <button type="button" class="btn btn-sm btn-outline-secondary table-row-action" disabled>無需處理</button>`;
        return;
    }
    const sales = rows.reduce((sum, row) => sum + Number(row.sales_7d || 0), 0);
    const qualityRows = rows.map(rowQualityScore).filter((score) => Number.isFinite(score) && score > 0);
    const avgQuality = qualityRows.length
        ? Math.round(qualityRows.reduce((sum, score) => sum + score, 0) / qualityRows.length)
        : 0;
    const gapValues = rows
        .map((row) => row.external_price?.gap_pct)
        .filter((gap) => gap !== null && gap !== undefined && Number.isFinite(Number(gap)))
        .map(Number);
    const largestGap = gapValues.length
        ? gapValues.reduce((best, gap) => Math.abs(gap) > Math.abs(best) ? gap : best, gapValues[0])
        : null;
    const topRow = rows[0] || {};
    const topName = topRow.product_name ? `代表商品：${topRow.product_name}` : '目前沒有符合條件的商品。';
    const action = config.action || (
        topRow.recommended_action?.code === 'review_external_candidate'
            ? 'review-candidate'
            : topRow.recommended_action?.code === 'map_external_product'
                ? 'backfill'
                : 'focus-price'
    );
    const productKey = escapeHtml(topRow.pchome_product_id || topRow.product_name || '');
    const buttonAttrs = action === 'backfill'
        ? 'data-growth-action="backfill"'
        : action === 'review-candidate'
            ? `data-growth-action="review-candidate" data-product-key="${productKey}"`
            : `data-growth-action="focus-price" data-product-key="${productKey}"`;

    box.innerHTML = `<div>
        <h3 class="growth-decision-title">${escapeHtml(config.title)}</h3>
        <p class="growth-decision-copy">${escapeHtml(config.copy)} ${escapeHtml(topName)}</p>
        <div class="growth-decision-metrics">
            <span class="growth-decision-metric">商品 <strong>${formatCount(rows.length)}</strong> 件</span>
            <span class="growth-decision-metric">近 7 天 <strong>${escapeHtml(formatMoney(sales))}</strong></span>
            <span class="growth-decision-metric">可信度 <strong>${avgQuality ? avgQuality + '%' : '待補'}</strong></span>
            <span class="growth-decision-metric">最大價差 <strong>${escapeHtml(largestGap === null ? '待判斷' : formatGapDisplay(largestGap))}</strong></span>
        </div>
    </div>
    <button type="button" class="btn btn-sm btn-outline-primary table-row-action" ${buttonAttrs}>${escapeHtml(config.actionLabel)}</button>`;
}

function renderGrowthDetail(kind = activeGrowthDetailKind) {
    const titleBox = document.getElementById('growthDrilldownTitle');
    const metaBox = document.getElementById('growthDrilldownMeta');
    const resultBox = document.getElementById('growthDrilldownResult');
    if (!titleBox || !metaBox || !resultBox) return;

    activeGrowthDetailKind = resolveGrowthDetailKind(kind);
    document.querySelectorAll('.growth-detail-tab').forEach((tab) => {
        tab.classList.toggle('is-active', tab.dataset.detailKind === activeGrowthDetailKind);
    });
    renderGrowthStrategySummary();
    renderGrowthCategoryBoard();
    renderGrowthPlaybookBoard();
    renderGrowthActionBoard();

    const [title, subtitle] = growthDetailConfig(activeGrowthDetailKind);
    const rows = growthDetailRows(activeGrowthDetailKind);
    if (activeGrowthProductKey && !rows.some((row) => growthRowKey(row) === activeGrowthProductKey)) {
        hideGrowthProductDecision();
    }
    const visibleRows = rows.slice(0, 50);
    const salesTotal = rows.reduce((sum, row) => sum + Number(row.sales_7d || 0), 0);
    titleBox.textContent = title;
    metaBox.textContent = `${rows.length.toLocaleString()} 件 · 近 7 天業績 ${formatMoney(salesTotal)} · ${subtitle} · 先列 ${visibleRows.length.toLocaleString()} 件`;
    renderGrowthDecisionSummary(rows, activeGrowthDetailKind);

    if (!rows.length) {
        resultBox.innerHTML = `<div class="text-center py-4 text-muted">
            <i class="fas fa-circle-info d-block mb-2"></i>
            目前沒有符合這個條件的商品。
        </div>`;
        return;
    }

    resultBox.innerHTML = visibleRows.map((row) => {
        const action = row.recommended_action || {};
        const price = row.external_price || null;
        const reviewCandidate = row.review_candidate || null;
        const gap = price && price.gap_pct !== null && price.gap_pct !== undefined ? Number(price.gap_pct) : null;
        const gapText = reviewCandidate
            ? '候選待確認'
            : gap === null
                ? '缺 MOMO 參考'
                : formatGapDisplay(gap);
        const delta = row.sales_delta_pct === null || row.sales_delta_pct === undefined
            ? '前期不足'
            : `${Number(row.sales_delta_pct).toFixed(1)}%`;
        const qualityScore = row.data_quality?.score !== null && row.data_quality?.score !== undefined
            ? Math.round(Number(row.data_quality.score || 0))
            : reviewCandidate?.quality_score !== null && reviewCandidate?.quality_score !== undefined
                ? Math.round(Number(reviewCandidate.quality_score || 0))
                : price?.match_score
                    ? Math.round(Number(price.match_score || 0) * 100)
                    : 0;
        const pchomeDisplay = price
            ? formatGrowthDetailPrice(price, 'pchome')
            : reviewCandidate
                ? formatPriceAmount(reviewCandidate.pchome_price)
                : '待補';
        const momoDisplay = price
            ? formatGrowthDetailPrice(price, 'momo')
            : reviewCandidate
                ? formatPriceAmount(reviewCandidate.momo_price)
                : '待補';
        const priceBasis = price?.price_basis_label || (reviewCandidate ? '候選價' : '待補資料');
        const gapChipClass = gap !== null && gap < 0
            ? ' is-risk'
            : gap !== null && gap > 0
                ? ' is-good'
                : '';
        const productKey = escapeHtml(row.pchome_product_id || row.product_name || '');
        const nextAction = action.code === 'review_external_candidate'
            ? 'review-candidate'
            : action.code === 'map_external_product'
                ? 'backfill'
                : 'focus-price';
        const nextLabel = action.code === 'review_external_candidate'
            ? '確認候選'
            : action.code === 'map_external_product'
                ? '補齊比價'
                : '看價格';
        const activeClass = productKey && productKey === escapeHtml(activeGrowthProductKey) ? ' is-active' : '';
        return `<article class="growth-detail-row${activeClass}">
            <div>
                <h3 class="growth-detail-name">
                    <button type="button" class="growth-detail-name-button" data-growth-action="show-product-detail" data-product-key="${productKey}">
                        ${escapeHtml(row.product_name || '未命名商品')}
                    </button>
                </h3>
                <p class="growth-detail-line">
                    ${escapeHtml(row.category || '未分類')} · 近 7 天 ${escapeHtml(formatMoney(row.sales_7d))} · 變化 ${escapeHtml(delta)}
                </p>
                <p class="growth-detail-line">
                    ${escapeHtml(action.label || '待判斷')} · ${escapeHtml(gapText)}
                </p>
            </div>
            <div class="growth-detail-action">
                <div class="growth-detail-price-grid">
                    <span class="growth-price-chip">
                        <span class="growth-price-label">PChome</span>
                        <span class="growth-price-value">${escapeHtml(pchomeDisplay)}</span>
                        <span class="growth-price-note">${escapeHtml(priceBasis)}</span>
                    </span>
                    <span class="growth-price-chip">
                        <span class="growth-price-label">MOMO</span>
                        <span class="growth-price-value">${escapeHtml(momoDisplay)}</span>
                        <span class="growth-price-note">${escapeHtml(price?.data_source_label || (reviewCandidate ? '候選待確認' : '等待補抓'))}</span>
                    </span>
                    <span class="growth-price-chip${gapChipClass}">
                        <span class="growth-price-label">差距</span>
                        <span class="growth-price-value">${escapeHtml(gapText)}</span>
                        <span class="growth-price-note">${escapeHtml(action.label || '待判斷')}</span>
                    </span>
                    <span class="growth-price-chip">
                        <span class="growth-price-label">可信度</span>
                        <span class="growth-price-value">${qualityScore ? `${qualityScore}%` : '待補'}</span>
                        <span class="growth-price-note">${escapeHtml(row.data_quality?.label || '資料待確認')}</span>
                    </span>
                </div>
                <div class="growth-detail-action-row">
                    <button type="button" class="btn btn-sm btn-outline-secondary table-row-action" data-growth-action="show-product-detail" data-product-key="${productKey}">查看判斷</button>
                    <button type="button" class="btn btn-sm btn-outline-primary table-row-action" data-growth-action="${nextAction}" data-product-key="${productKey}">${nextLabel}</button>
                </div>
            </div>
        </article>`;
    }).join('');
}

function renderGrowthOps(rows) {
    const list = document.getElementById('growthOpsList');
    if (!rows.length) {
        list.innerHTML = `<div class="text-center py-4 text-muted">
            <i class="fas fa-circle-info d-block mb-2"></i>
            目前沒有足夠資料，請先確認 PChome 業績檔已匯入。
        </div>`;
        return;
    }

    const visibleRows = rows.slice(0, 12);
    const body = visibleRows.map((row, index) => {
        const action = row.recommended_action || {};
        const reason = (row.reason_lines || []).slice(0, 2).join(' ');
        const price = row.external_price;
        const reviewCandidate = row.review_candidate || null;
        const gap = price && price.gap_pct !== null && price.gap_pct !== undefined ? Number(price.gap_pct) : null;
        const basisLabel = price?.price_basis_label || '商品總價';
        const priceText = reviewCandidate
            ? '候選待確認'
            : gap === null
            ? '資料不足，先補比價'
            : gap < 0
                ? `${basisLabel} PChome 貴 ${Math.abs(gap).toFixed(1)}%`
                : gap > 0
                    ? `${basisLabel} PChome 便宜 ${gap.toFixed(1)}%`
                    : `${basisLabel}差不多`;
        const priorityScore = Number(row.priority_score || 0);
        const priority = priorityScore >= 55 ? 'P1' : priorityScore >= 35 ? 'P2' : 'P3';
        const priorityLabel = priority === 'P1' ? '今天先做' : priority === 'P2' ? '接著處理' : '排程追蹤';
        const priorityClass = priority === 'P1' ? 'bg-danger' : priority === 'P2' ? 'bg-warning text-dark' : 'bg-secondary';
        const quality = row.data_quality || {};
        const qualityScore = quality.score !== undefined && quality.score !== null ? Number(quality.score) : null;
        const qualityLabel = quality.label || (price ? '可直接參考' : '資料不足');
        const qualityIssues = Array.isArray(quality.issues) ? quality.issues.join('、') : '';
        const productKey = escapeHtml(row.pchome_product_id || row.product_name || '');
        const nextLabel = action.code === 'review_external_candidate'
            ? '確認候選'
            : action.code === 'map_external_product'
                ? '補齊比價'
                : '檢查價格';
        const nextAction = action.code === 'review_external_candidate'
            ? 'review-candidate'
            : action.code === 'map_external_product'
                ? 'backfill'
                : 'focus-price';
        return `<tr>
            <td data-label="優先級">
                <span class="badge ${priorityClass}">${priority}</span>
                <div class="growth-ops-muted">${priorityLabel} · ${priorityScore.toFixed(0)}分</div>
            </td>
            <td data-label="建議動作">
                <span class="growth-action-pill">${escapeHtml(action.label || '待判斷')}</span>
            </td>
            <td data-label="商品">
                <div class="growth-ops-name">${escapeHtml(row.product_name)}</div>
                <div class="growth-ops-muted">${escapeHtml(reason || '依業績與外部價格排序')}</div>
            </td>
            <td class="text-end fw-bold" data-label="近 7 天業績">${formatMoney(row.sales_7d)}</td>
            <td class="text-end" data-label="比價結果">${escapeHtml(priceText)}</td>
            <td data-label="資料可信度">
                <span class="${price ? 'text-success' : 'text-warning'} fw-bold">${escapeHtml(qualityLabel)}</span>
                <div class="growth-ops-muted">${qualityScore === null ? escapeHtml(qualityIssues || '等待補齊資料') : `${qualityScore.toFixed(0)}%${qualityIssues ? ' · ' + escapeHtml(qualityIssues) : ''}`}</div>
            </td>
            <td class="text-end" data-label="下一步">
                <button type="button" class="btn btn-sm btn-outline-primary table-row-action" data-growth-action="${nextAction}" data-product-key="${productKey}">${nextLabel}</button>
            </td>
        </tr>`;
    }).join('');

    list.innerHTML = `<div class="growth-ops-table-wrap">
        <table class="growth-ops-table">
            <thead>
                <tr>
                    <th>優先級</th>
                    <th>建議動作</th>
                    <th>商品</th>
                    <th class="text-end">近 7 天業績</th>
                    <th class="text-end">比價結果</th>
                    <th>資料可信度</th>
                    <th class="text-end">下一步</th>
                </tr>
            </thead>
            <tbody>${body}</tbody>
        </table>
    </div>`;
}

async function loadGrowthReviewCandidates(forceRefresh = false) {
    const box = document.getElementById('growthReviewCandidateList');
    if (!box) return;
    if (forceRefresh) {
        box.innerHTML = `<div class="text-center py-4 text-muted">
            <div class="spinner-border spinner-border-sm me-2"></div>更新待確認候選中...
        </div>`;
    }

    try {
        const response = await fetch('/api/ai/pchome-growth/review-candidates?limit=20');
        const data = await readJsonResponse(response);
        if (!data.success) throw new Error(data.error || data.message || '讀取失敗');
        renderGrowthReviewCandidates(data.rows || []);
    } catch (error) {
        console.error(error);
        box.innerHTML = `<div class="text-center py-4 text-muted">
            <i class="fas fa-circle-exclamation d-block mb-2"></i>
            待確認候選暫時讀不到，請稍後再試。
        </div>`;
    }
}

function renderGrowthReviewCandidates(rows) {
    const box = document.getElementById('growthReviewCandidateList');
    const total = document.getElementById('reviewCandidateTotal');
    const readyHint = document.getElementById('reviewCandidateReadyHint');
    if (!box) return;

    rows = Array.isArray(rows) ? rows : [];
    if (total) total.textContent = rows.length.toLocaleString();
    if (readyHint) readyHint.textContent = rows.length.toLocaleString();

    if (!rows.length) {
        box.innerHTML = `<div class="text-center py-4 text-muted">
            <i class="fas fa-circle-check d-block mb-2"></i>
            目前沒有待確認候選。
        </div>`;
        return;
    }

    box.innerHTML = rows.map((row) => {
        const reasonLabels = Array.isArray(row.match_reason_labels) && row.match_reason_labels.length
            ? row.match_reason_labels.slice(0, 3)
            : ['請比對兩個賣場的品名、容量、色號與組合'];
        const gap = formatReviewCandidateGap(row.gap_pct);
        const score = Number(row.quality_score || 0).toFixed(0);
        const momoPrice = row.momo_price ? formatMoney(row.momo_price) : '未取得 MOMO 價格';
        const pchomePrice = row.pchome_price ? formatMoney(row.pchome_price) : '未取得 PChome 價格';
        const pchomeUrl = safeHttpUrl(row.pchome_url);
        const momoUrl = safeHttpUrl(row.momo_url || row.product_url);
        const pchomeLink = pchomeUrl ? `<a href="${escapeHtml(pchomeUrl)}" target="_blank" rel="noopener noreferrer">PChome 賣場</a>` : '<span class="text-muted">待補連結</span>';
        const momoLink = momoUrl ? `<a href="${escapeHtml(momoUrl)}" target="_blank" rel="noopener noreferrer">MOMO 賣場</a>` : '<span class="text-muted">待補連結</span>';
        const pchomeThumb = renderProductThumb(row.pchome_image_url, `${row.pchome_product_name || 'PChome'} 商品圖`);
        const momoThumb = renderProductThumb(row.momo_image_url || row.image_url, `${row.momo_title || 'MOMO'} 商品圖`);
        const compareButton = pchomeUrl && momoUrl
            ? `<button type="button" class="btn btn-sm review-candidate-primary" data-pchome-url="${escapeHtml(pchomeUrl)}" data-momo-url="${escapeHtml(momoUrl)}" onclick="openReviewCandidateStores(this)"><i class="fas fa-up-right-from-square me-1"></i>同時開兩家賣場</button>`
            : '';
        return `<article class="review-candidate-row" data-pchome-id="${escapeHtml(row.pchome_product_id || '')}">
            <div>
                <h3 class="review-candidate-title">${escapeHtml(row.pchome_product_name || row.pchome_product_id || 'PChome 商品')}</h3>
                <div class="review-candidate-meta">
                    <span class="review-candidate-pill ${escapeHtml(gap.className)}">${escapeHtml(gap.label)}</span>
                    <span class="review-candidate-pill">同款可信度 ${score}%</span>
                </div>
                <div class="review-candidate-compare" aria-label="兩家賣場比對">
                    <section class="review-candidate-store is-pchome">
                        <div class="review-candidate-store-head">
                            ${pchomeThumb}
                            <div>
                                <span class="review-candidate-store-label"><i class="fas fa-bolt"></i>PChome</span>
                                <span class="review-candidate-store-price">${escapeHtml(pchomePrice)}</span>
                            </div>
                        </div>
                        <p class="review-candidate-store-title" title="${escapeHtml(row.pchome_product_name || '')}">${escapeHtml(row.pchome_product_name || row.pchome_product_id || 'PChome 商品')}</p>
                        <div class="review-candidate-store-meta"><span>商品ID ${escapeHtml(row.pchome_product_id || '待補')}</span></div>
                        <div class="mt-2">${pchomeLink}</div>
                    </section>
                    <section class="review-candidate-store is-momo">
                        <div class="review-candidate-store-head">
                            ${momoThumb}
                            <div>
                                <span class="review-candidate-store-label"><i class="fas fa-store"></i>MOMO</span>
                                <span class="review-candidate-store-price">${escapeHtml(momoPrice)}</span>
                            </div>
                        </div>
                        <p class="review-candidate-store-title" title="${escapeHtml(row.momo_title || '')}">${escapeHtml(row.momo_title || row.momo_sku || '未命名候選')}</p>
                        <div class="review-candidate-store-meta"><span>商品ID ${escapeHtml(row.momo_sku || '待補')}</span></div>
                        <div class="mt-2">${momoLink}</div>
                    </section>
                </div>
                <div class="review-candidate-reason-chips" aria-label="需要確認的重點">
                    ${renderReasonChips(reasonLabels)}
                </div>
            </div>
            <div class="review-candidate-actions">
                ${compareButton}
                ${pchomeUrl ? `<a class="btn btn-sm btn-outline-secondary" href="${escapeHtml(pchomeUrl)}" target="_blank" rel="noopener noreferrer">開 PChome</a>` : ''}
                ${momoUrl ? `<a class="btn btn-sm btn-outline-secondary" href="${escapeHtml(momoUrl)}" target="_blank" rel="noopener noreferrer">開 MOMO</a>` : ''}
                <button type="button" class="btn btn-sm btn-success" onclick="updateGrowthReviewCandidate(${Number(row.id)}, 'confirm', this)">確認同款</button>
                <button type="button" class="btn btn-sm btn-outline-secondary" onclick="updateGrowthReviewCandidate(${Number(row.id)}, 'reject', this)">不是同款</button>
            </div>
        </article>`;
    }).join('');
}

function openReviewCandidateStores(button) {
    const pchomeUrl = safeHttpUrl(button?.dataset?.pchomeUrl);
    const momoUrl = safeHttpUrl(button?.dataset?.momoUrl);
    const openStoreWindow = (url, name) => {
        const win = window.open(url, name);
        if (win) {
            try { win.opener = null; } catch (_) {}
        }
        return win;
    };
    const opened = [];
    if (pchomeUrl) opened.push(openStoreWindow(pchomeUrl, `pchome_${Date.now()}`));
    if (momoUrl) opened.push(openStoreWindow(momoUrl, `momo_${Date.now()}`));
    if (opened.some((win) => !win)) {
        showToast('warning', '瀏覽器擋住了其中一個視窗；請用旁邊的 PChome / MOMO 按鈕分別開啟。', 4500);
    }
}

async function updateGrowthReviewCandidate(id, action, button) {
    if (!id || !action) return;
    const actionText = action === 'confirm' ? '確認同款' : '排除候選';
    const originalText = button ? button.textContent : '';
    if (button) {
        button.disabled = true;
        button.textContent = '處理中';
    }

    try {
        const response = await fetch(`/api/ai/pchome-growth/review-candidates/${id}`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ action }),
        });
        const data = await readJsonResponse(response);
        if (!data.success) throw new Error(data.error || data.message || `${actionText}失敗`);
        showToast('success', data.message || `${actionText}完成`, 3500);
        await loadGrowthReviewCandidates(true);
        await loadGrowthOps(true);
        loadDashboard();
    } catch (error) {
        console.error(error);
        showToast('error', `${actionText}失敗：${error.message}`, 5000);
    } finally {
        if (button) {
            button.disabled = false;
            button.textContent = originalText;
        }
    }
}

function fillExternalOfferSample() {
    const sample = [
        '來源,平台商品編號,商品名稱,售價,資料時間,取得方式,PChome商品編號,是否同款,可信度',
        'MOMO,MOMO-1001,範例保養商品,899,2026-06-15T10:00:00,備用資料,PCH-1001,是,88',
        'MOMO,MOMO-2001,待確認商品,790,2026-06-15T10:05:00,備用資料,,待確認,60'
    ].join('\\n');
    document.getElementById('externalOfferCsvText').value = sample;
}

async function previewExternalOfferCsv() {
    const fileInput = document.getElementById('externalOfferCsvFile');
    const textInput = document.getElementById('externalOfferCsvText');
    const resultBox = document.getElementById('offerDryRunResult');
    const button = document.getElementById('btnExternalOfferDryRun');
    const formData = new FormData();

    if (fileInput.files && fileInput.files[0]) {
        formData.append('file', fileInput.files[0]);
    }
    if (textInput.value.trim()) {
        formData.append('csv_text', textInput.value.trim());
    }

    if (!formData.has('file') && !formData.has('csv_text')) {
        resultBox.innerHTML = `<div class="text-center py-4 text-muted">
            <i class="fas fa-circle-exclamation d-block mb-2"></i>
            請先上傳備援檔案或貼上內容。
        </div>`;
        return;
    }

    button.disabled = true;
    button.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>檢查中';
    resultBox.innerHTML = `<div class="text-center py-4 text-muted">
        <div class="spinner-border spinner-border-sm me-2"></div>檢查資料品質中...
    </div>`;

    try {
        const response = await fetch('/api/ai/pchome-growth/external-offers/csv-dry-run', {
            method: 'POST',
            body: formData,
        });
        const data = await readJsonResponse(response);
        renderExternalOfferDryRun(data);
    } catch (error) {
        console.error(error);
        resultBox.innerHTML = `<div class="text-center py-4 text-muted">
            <i class="fas fa-circle-exclamation d-block mb-2"></i>
            備援資料檢查暫時失敗，請稍後再試。
        </div>`;
    } finally {
        button.disabled = false;
        button.innerHTML = '<i class="fas fa-magnifying-glass me-1"></i>檢查備援資料';
    }
}

function renderExternalOfferDryRun(data) {
    const resultBox = document.getElementById('offerDryRunResult');
    const summary = data.summary || {};
    document.getElementById('offerDryRunReady').textContent = (summary.ready_count || 0).toLocaleString();
    document.getElementById('offerDryRunReview').textContent = (summary.review_count || 0).toLocaleString();
    document.getElementById('offerDryRunBlocked').textContent = (summary.blocked_count || 0).toLocaleString();

    if (!data.success) {
        const errors = (data.errors || [data.error || '備援資料格式需要修正']).slice(0, 4);
        resultBox.innerHTML = `<div class="text-center py-4 text-muted">
            <i class="fas fa-circle-exclamation d-block mb-2"></i>
            ${escapeHtml(errors.join('；'))}
        </div>`;
        return;
    }

    const rows = (data.rows || []).slice(0, 8);
    if (!rows.length) {
        resultBox.innerHTML = `<div class="text-center py-4 text-muted">
            <i class="fas fa-circle-info d-block mb-2"></i>
            備援資料裡沒有可檢查的資料列。
        </div>`;
        return;
    }

    resultBox.innerHTML = rows.map((row) => {
        const statusClass = row.status_code === 'ready'
            ? ' is-ready'
            : row.status_code === 'blocked'
                ? ' is-blocked'
                : '';
        const reasons = (row.reasons || []).join('、');
        const price = row.price ? formatMoney(row.price) : '未填價格';
        const sourceLabel = formatDataSourceLabel(row['source' + '_code']);
        return `<article class="offer-dryrun-row">
            <div>
                <h3 class="offer-dryrun-title">${escapeHtml(row.title || '未命名商品')}</h3>
                <p class="offer-dryrun-meta">
                    第 ${escapeHtml(row.row_number || '-')} 列 · ${escapeHtml(sourceLabel)} · ${escapeHtml(price)}
                </p>
                <p class="offer-dryrun-reason">${escapeHtml(reasons)}</p>
            </div>
            <span class="offer-status-pill${statusClass}">${escapeHtml(row.status_label || '待確認')}</span>
        </article>`;
    }).join('');
}

// ── 競品比價表格（熱力圖底色）──────────────────────
function renderPriceRiskBoard(rows) {
    rows = Array.isArray(rows) ? rows : [];
    const counts = rows.reduce((acc, row) => {
        if (row.gap_pct > 15 || row.risk === 'HIGH') acc.high += 1;
        else if (row.gap_pct > 5 || row.risk === 'MED') acc.medium += 1;
        else acc.low += 1;
        return acc;
    }, { high: 0, medium: 0, low: 0 });
    const total = Math.max(1, counts.high + counts.medium + counts.low);

    document.getElementById('priceRiskHighText').textContent = `${formatCount(counts.high)} 筆`;
    document.getElementById('priceRiskMediumText').textContent = `${formatCount(counts.medium)} 筆`;
    document.getElementById('priceRiskLowText').textContent = `${formatCount(counts.low)} 筆`;
    setWidth('priceRiskHighBar', (counts.high / total) * 100);
    setWidth('priceRiskMediumBar', (counts.medium / total) * 100);
    setWidth('priceRiskLowBar', (counts.low / total) * 100);
}

function showPriceRiskDetail(risk) {
    const select = document.getElementById('riskFilter');
    if (select) {
        select.value = risk || 'all';
        filterTable();
    }
    scrollToPanel('externalPricePanel');
}

function renderCompetitorTable(rows) {
    rows = Array.isArray(rows) ? rows : [];
    const tbody = document.getElementById('competitorTbody');
    renderPriceRiskBoard(rows);
    if (!rows.length) {
        tbody.innerHTML = `<tr><td colspan="8" class="text-center py-5 text-muted">
            <i class="fas fa-info-circle me-2"></i>暫無競品比價資料
        </td></tr>`;
        document.getElementById('compCount').textContent = '0 筆';
        return;
    }

    tbody.innerHTML = rows.map(r => {
        // 行底色
        let rowBg = '';
        if (r.gap_pct > 15)       rowBg = 'background:#fee2e2';   // 淺紅 — 嚴重貴
        else if (r.gap_pct > 5)  rowBg = 'background:#fef9c3';   // 淺黃 — 有風險
        else if (r.gap_pct < 0)   rowBg = 'background:#f0fdf4';   // 淺綠 — 我便宜

        // 價差文字顏色
        const gapClass = r.gap_pct > 15 ? 'text-danger fw-bold'
                       : r.gap_pct > 5  ? 'text-warning fw-bold'
                       : r.gap_pct < 0  ? 'text-success fw-bold'
                       : 'text-muted';
        const gapSign  = r.gap_pct > 0 ? '+' : '';

        const pchomePrice = Number(r.pchome_price || 0);
        const momoPrice = Number(r.momo_price || 0);
        const gapAmount = pchomePrice - momoPrice;
        const gapAmountText = gapAmount > 0
            ? `貴 ${formatMoney(gapAmount)}`
            : gapAmount < 0
                ? `便宜 ${formatMoney(Math.abs(gapAmount))}`
                : '差不多';
        const riskLabel = r.gap_pct > 15 || r.risk === 'HIGH'
            ? '需檢查'
            : r.gap_pct > 5 || r.risk === 'MED'
                ? '留意'
                : r.gap_pct < 0
                    ? '價格有利'
                    : '觀察';
        const riskBadge = r.gap_pct > 15 || r.risk === 'HIGH'
            ? 'bg-danger text-white'
            : r.gap_pct > 5 || r.risk === 'MED'
                ? 'bg-warning text-dark'
                : r.gap_pct < 0
                    ? 'bg-success text-white'
                    : 'bg-secondary text-white';

        const tagHtml = (r.tags || []).map(t => {
            const tagMap = {
                'on_sale':        ['bg-info text-dark',      '促銷中'],
                'discount_30pct': ['bg-danger text-white',   '折30%+'],
                'discount_20pct': ['bg-warning text-dark',   '折20%+'],
                'discount_10pct': ['bg-secondary text-white','折10%+'],
                'low_stock':      ['bg-dark text-white',     '低庫存'],
                'high_rating':    ['bg-primary text-white',  '高評分'],
                'identity_v2':     ['bg-success text-white',  '同款確認'],
                'match_type_exact':['bg-success text-white',  '同款確認'],
                'price_alert_exact':['bg-danger text-white',  '價差告警'],
                'external_offers':  ['bg-success text-white',  '自動同步'],
                'legacy_competitor_cache':['bg-light text-dark','舊資料'],
                'evidence_brand':  ['bg-light text-dark',     '品牌一致'],
                'evidence_identity':['bg-light text-dark',    '同款證據'],
                'match_shared_model_token':['bg-light text-dark','型號一致'],
                'match_product_line':['bg-light text-dark',   '品線一致'],
                'alert_tier_price':['bg-warning text-dark',   '優先追蹤'],
            };
            const mapped = tagMap[t];
            if (!mapped) return '';
            const [cls, label] = mapped;
            return `<span class="badge ${cls} me-1" style="font-size:0.68rem">${label}</span>`;
        }).filter(Boolean).slice(0, 3).join('');

        const matchScore = Number(r.match_score || 0);
        const scoreColor = matchScore >= 0.7  ? 'text-success'
                         : matchScore >= 0.55 ? 'text-warning'
                         : 'text-danger';
        const scorePct = Math.round(matchScore * 100);
        const scoreLabel = scorePct >= 70 ? '高' : scorePct >= 55 ? '中' : '低';
        const name = escapeHtml(r.name || '未命名商品');
        const category = escapeHtml(r.category || '未分類');
        const sku = escapeHtml(r.sku || '');
        const dataSource = escapeHtml(r.data_source || '');
        const crawledAt = escapeHtml(r.crawled_at || '未更新');

        return `<tr data-risk="${escapeHtml(r.risk || '')}" data-source="${dataSource}" data-name="${escapeHtml(String(r.name || '').toLowerCase())}" style="${rowBg}">
            <td class="ps-3" data-label="風險"><span class="badge ${riskBadge}">${riskLabel}</span></td>
            <td data-label="商品">
                <div style="font-size:0.82rem;font-weight:700" title="${name}">${name}</div>
                <small class="text-muted">${category} · ${sku}</small>
            </td>
            <td class="text-end text-dark fw-bold" data-label="PChome">${formatMoney(pchomePrice)}</td>
            <td class="text-end text-secondary" data-label="MOMO">${formatMoney(momoPrice)}</td>
            <td class="text-end ${gapClass}" data-label="差距">
                <strong>${escapeHtml(gapAmountText)}</strong>
                <div class="growth-ops-muted">${gapSign}${Number(r.gap_pct || 0).toFixed(1)}%</div>
            </td>
            <td class="text-center ${scoreColor}" style="font-size:0.8rem" data-label="可信度">
                ${scoreLabel} · ${scorePct}%
                <div>${tagHtml || '<span class="text-muted small">—</span>'}</div>
            </td>
            <td class="text-muted" style="font-size:0.75rem" data-label="更新">${crawledAt}</td>
            <td class="text-end" data-label="下一步">
                <button type="button" class="btn btn-sm btn-outline-primary table-row-action" data-table-action="focus" data-product-key="${sku}">鎖定商品</button>
            </td>
        </tr>`;
    }).join('');

    document.getElementById('compCount').textContent = `共 ${rows.length} 筆`;
}

// ── 篩選 ─────────────────────────────────────────────
function filterTable() {
    const risk   = document.getElementById('riskFilter').value;
    const search = document.getElementById('searchInput').value.toLowerCase().trim();

    const filtered = allCompetitors.filter(r => {
        const riskOk   = risk === 'all' || r.risk === risk;
        const searchText = `${r.name || ''} ${r.sku || ''}`.toLowerCase();
        const searchOk = !search || searchText.includes(search);
        return riskOk && searchOk;
    });
    renderCompetitorTable(filtered);
}

// ── 最近處理紀錄 ────────────────────────
function renderAiRecs(recs) {
    recs = Array.isArray(recs) ? recs : [];
    const container = document.getElementById('aiRecsList');
    document.getElementById('aiRecsCount').textContent =
        recs.length ? `共 ${recs.length} 筆紀錄` : '尚無紀錄';

    if (!recs.length) {
        container.innerHTML = `
        <div class="text-center py-5">
            <i class="fas fa-clipboard-check fa-3x text-muted mb-3 d-block"></i>
            <p class="text-muted mb-1">目前還沒有處理紀錄</p>
            <p class="small text-muted mb-3">
                每天固定整理，也可以手動更新。
            </p>
            <button class="btn btn-sm btn-outline-danger" onclick="triggerAnalysis()">
                    <i class="fas fa-bolt me-1"></i>更新今日建議
            </button>
        </div>`;
        return;
    }

    const strategyMap = {
        'price_cut':  ['bg-danger',              '降價'],
        'promote':    ['bg-primary',             '主推'],
        'product_pick':['bg-success',            'AI挑品'],
        'monitor':    ['bg-secondary',           '觀察'],
        'flag':       ['bg-warning text-dark',   '覆核'],
    };

    container.innerHTML = recs.map(r => {
        const [sBg, sLabel] = strategyMap[r.strategy] || ['bg-secondary', r.strategy || '觀察'];
        const confidence = Number(r.confidence || 0);
        const gapPct = Number(r.gap_pct || 0);
        const confPct = Math.max(0, Math.min(100, Math.round(confidence * 100)));
        const confColor     = confPct >= 80 ? 'bg-success' : confPct >= 60 ? 'bg-warning' : 'bg-danger';
        const gapSign       = gapPct > 0 ? '+' : '';
        const name = escapeHtml(r.name || '未命名商品');
        const reason = String(r.reason || '');
        const trimmedReason = reason.substring(0, 90) + (reason.length > 90 ? '…' : '');
        const createdAt = escapeHtml(r.created_at || '未更新');

        return `<div class="border rounded mb-2 p-2" style="font-size:0.83rem">
            <div class="d-flex justify-content-between align-items-start mb-1">
                <span class="fw-bold text-truncate me-2" style="max-width:200px" title="${name}">${name}</span>
                <span class="badge ${sBg} flex-shrink-0">${escapeHtml(sLabel)}</span>
            </div>
                <div class="d-flex gap-3 mb-1 text-muted small">
                <span>MOMO <strong class="text-dark">${formatMoney(r.momo_price)}</strong></span>
                <span>PChome <strong class="text-secondary">${formatMoney(r.pchome_price)}</strong></span>
                <span class="${gapPct > 10 ? 'text-danger fw-bold' : 'text-muted'}">${gapSign}${gapPct.toFixed(1)}%</span>
            </div>
            <div class="mb-1 text-muted" style="font-size:0.78rem;line-height:1.4">
                ${escapeHtml(trimmedReason)}
            </div>
            <div class="d-flex justify-content-between align-items-center">
                <div class="d-flex align-items-center gap-1">
                    <div class="progress" style="width:60px;height:6px">
                        <div class="progress-bar ${confColor}" style="width:${confPct}%"></div>
                    </div>
                    <small class="text-muted">${confPct}%</small>
                </div>
                <small class="text-muted">
                    ${createdAt}
                </small>
            </div>
        </div>`;
    }).join('');
}

// ── 產生 AI 建議挑品清單 ───────────────────────────
async function generatePickList() {
    const btn = document.getElementById('btnPickList');

    if (btn && btn.disabled) return;
    if (btn) btn.disabled = true;
    setActionBusy('generate-picks', true);
    if (btn) btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>整理中...';

    try {
        const res = await fetch('/api/ai/product-picks/generate', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ limit: 50 })
        });
        const data = await readJsonResponse(res);

        showToast(
            data.success ? 'success' : 'error',
            data.success ? (data.message || '已開始整理今日清單。完成後請先看第一步。') : (data.error || '產生失敗，資料沒有變更。'),
            6000
        );

        if (data.success) loadDashboard();
    } catch (e) {
        showToast('error', `整理失敗：${e.message}`, 4000);
    } finally {
        if (btn) btn.disabled = false;
        setActionBusy('generate-picks', false);
        if (btn) btn.innerHTML = '<i class="fas fa-list-check me-1"></i>產生今日清單';
    }
}

// ── 補抓 PChome 尚未搜尋商品 ───────────────────────
async function backfillPchomeMatches() {
    const btn = document.getElementById('btnBackfill');

    if (btn && btn.disabled) return;
    if (btn) btn.disabled = true;
    setActionBusy('backfill', true);
    if (btn) btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>整理中...';

    try {
        const res = await fetch('/api/ai/pchome-match/backfill', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ limit: 60 })
        });
        const data = await readJsonResponse(res);

        showToast(
            data.success ? 'success' : 'error',
            data.success ? (data.message || '已開始補齊比價資料，約 60 秒後更新。') : (data.error || '商品對應啟動失敗'),
            6000
        );

        if (data.success) setTimeout(loadDashboard, 90000);
    } catch (e) {
        showToast('error', `商品對應失敗：${e.message}`, 4000);
    } finally {
        if (btn) btn.disabled = false;
        setActionBusy('backfill', false);
        if (btn) btn.innerHTML = '<i class="fas fa-link me-1"></i>補齊比價資料';
    }
}

// ── 手動觸發分析 ────────────────────────────────────
async function triggerAnalysis() {
    const btn   = document.getElementById('btnTrigger');

    if (btn && btn.disabled) return;
    if (btn) btn.disabled = true;
    setActionBusy('trigger-analysis', true);
    if (btn) btn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>整理中...';

    try {
        const res  = await fetch('/api/ai/icaim/trigger', { method: 'POST' });
        const data = await readJsonResponse(res);

        showToast(
            data.success ? 'success' : 'error',
            data.success ? (data.message || '已開始更新今日建議，約 60 秒後更新。') : (data.error || '整理失敗，資料沒有變更。'),
            6000
        );

        if (data.success) {
            // 60 秒後自動重新整理儀表板
            setTimeout(loadDashboard, 60000);
        }
    } catch (e) {
        showToast('error', `整理失敗：${e.message}`, 4000);
    } finally {
        if (btn) btn.disabled = false;
        setActionBusy('trigger-analysis', false);
        if (btn) btn.innerHTML = '<i class="fas fa-sparkles me-1"></i>更新建議';
    }
}
</script>
{% endblock %}
