/* ==========================================================================
   智乾 IP · 通用 UI 组件（v2.4.68 引入）
   --------------------------------------------------------------------------
   提供：Toast 轻提示 / Modal 模态框（替代原生 alert·confirm·prompt）/
        Overlay 加载遮罩 + Spinner / Skeleton 骨架屏。
   全部走 app.css 的 --zb-* 设计令牌，自动跟随深浅色主题。
   引入点：templates/_assets.html（全站，含不继承 base.html 的存量页）。
   依赖：无（纯原生 JS + CSS）。
   ========================================================================== */

/* ----------------------------------------------------------- Toast 轻提示 */
.zb-toast-wrap {
    position: fixed;
    right: 18px;
    bottom: 18px;
    z-index: 9000;
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-width: min(360px, calc(100vw - 36px));
    pointer-events: none;
}
.zb-toast {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: 10px;
    background: var(--zb-bg-elev);
    border: 1px solid var(--zb-border);
    border-left: 4px solid var(--zb-info);
    border-radius: var(--zb-radius-sm);
    box-shadow: var(--zb-shadow-3);
    padding: 11px 14px;
    font-size: 13.5px;
    line-height: 1.5;
    color: var(--zb-text);
    transform: translateX(120%);
    opacity: 0;
    transition: transform var(--zb-speed) ease, opacity var(--zb-speed) ease;
}
.zb-toast.is-show { transform: translateX(0); opacity: 1; }
.zb-toast.is-hide { transform: translateX(120%); opacity: 0; }
.zb-toast .zb-toast-ico { flex: 0 0 auto; font-size: 16px; line-height: 1.4; }
.zb-toast .zb-toast-body { flex: 1 1 auto; word-break: break-word; }
.zb-toast .zb-toast-close {
    flex: 0 0 auto; cursor: pointer; color: var(--zb-text-muted);
    border: none; background: none; font-size: 15px; padding: 0 2px;
}
.zb-toast.success { border-left-color: var(--zb-ok); }
.zb-toast.error   { border-left-color: var(--zb-danger); }
.zb-toast.warn    { border-left-color: var(--zb-warn); }
.zb-toast.info    { border-left-color: var(--zb-info); }

/* ------------------------------------------------------------- Modal 模态框 */
.zb-modal-mask {
    position: fixed; inset: 0;
    background: rgba(16, 24, 40, .46);
    display: flex; align-items: center; justify-content: center;
    z-index: 9100; padding: 16px;
    opacity: 0; transition: opacity var(--zb-speed) ease;
}
.zb-modal-mask.is-show { opacity: 1; }
.zb-modal {
    background: var(--zb-bg-elev);
    border: 1px solid var(--zb-border);
    border-radius: var(--zb-radius);
    box-shadow: var(--zb-shadow-3);
    width: min(440px, 100%);
    max-height: calc(100vh - 32px);
    overflow: auto;
    transform: translateY(8px) scale(.98);
    transition: transform var(--zb-speed) ease;
}
.zb-modal-mask.is-show .zb-modal { transform: translateY(0) scale(1); }
.zb-modal-head {
    display: flex; align-items: center; gap: 8px;
    padding: 15px 18px; border-bottom: 1px solid var(--zb-border);
    font-size: 15px; font-weight: 600; color: var(--zb-text);
}
.zb-modal-head .zb-modal-ico { font-size: 18px; }
.zb-modal-head.danger { color: var(--zb-danger-ink); }
.zb-modal-body { padding: 16px 18px; font-size: 14px; line-height: 1.6; color: var(--zb-text-soft); white-space: pre-wrap; word-break: break-word; }
.zb-modal-body input.zb-input {
    width: 100%; margin-top: 10px; box-sizing: border-box;
}
.zb-modal-foot {
    display: flex; justify-content: flex-end; gap: 10px;
    padding: 13px 18px; border-top: 1px solid var(--zb-border);
}

/* ------------------------------------------------------- Overlay 加载遮罩 */
.zb-overlay {
    position: fixed; inset: 0;
    background: rgba(16, 24, 40, .35);
    display: none; align-items: center; justify-content: center;
    z-index: 9200;
}
.zb-overlay.is-show { display: flex; }
.zb-overlay-card {
    background: var(--zb-bg-elev);
    border: 1px solid var(--zb-border);
    border-radius: var(--zb-radius);
    box-shadow: var(--zb-shadow-3);
    padding: 22px 28px;
    display: flex; flex-direction: column; align-items: center; gap: 14px;
    color: var(--zb-text); font-size: 14px;
    min-width: 180px;
}
.zb-spinner {
    width: 34px; height: 34px;
    border: 3px solid var(--zb-border-strong);
    border-top-color: var(--zb-primary);
    border-radius: 50%;
    animation: zb-spin .8s linear infinite;
}
.zb-spinner.sm { width: 18px; height: 18px; border-width: 2px; }
.zb-spinner.lg { width: 48px; height: 48px; border-width: 4px; }
@keyframes zb-spin { to { transform: rotate(360deg); } }

/* 区域内联 spinner（按钮内/卡片内） */
.zb-spinner-inline {
    display: inline-block; vertical-align: middle;
    width: 15px; height: 15px; margin-right: 6px;
    border: 2px solid var(--zb-border-strong);
    border-top-color: currentColor;
    border-radius: 50%;
    animation: zb-spin .7s linear infinite;
}

/* ------------------------------------------------------- Skeleton 骨架屏 */
.zb-skeleton {
    background: linear-gradient(90deg,
        var(--zb-bg-sunken) 25%, var(--zb-bg-hover) 37%, var(--zb-bg-sunken) 63%);
    background-size: 400% 100%;
    animation: zb-shimmer 1.3s ease infinite;
    border-radius: var(--zb-radius-sm);
}
@keyframes zb-shimmer { 0% { background-position: 100% 0; } 100% { background-position: -100% 0; } }

/* 移动端：Toast 占满宽度，避免溢出 */
@media (max-width: 520px) {
    .zb-toast-wrap { left: 12px; right: 12px; bottom: 12px; max-width: none; }
    .zb-modal { width: 100%; }
}

/* ------------------------------------------------- 表单字段级内联校验（G） */
/* 由 detail.js 的 markFieldError / clearFieldErrors 驱动：把出错字段描红并
   在其下方给出一行说明，取代「只能靠后端报错再 toast」的割裂体验。 */
.zb-input.zb-field-error {
    border-color: var(--zb-danger) !important;
    box-shadow: 0 0 0 2px rgba(208, 69, 63, .18);
}
.zb-field-msg {
    flex-basis: 100%;
    width: 100%;
    color: var(--zb-danger);
    font-size: 12px;
    line-height: 1.4;
    margin-top: 2px;
}
/* 错误态的「× 清空」按钮也随红框走，保持一致 */
.dt-edit-item .zb-field-error + .dt-clear { color: var(--zb-danger); }
