优化脚本
This commit is contained in:
@@ -374,6 +374,7 @@ export const api = {
|
||||
prompt?: string;
|
||||
model_config_id?: string;
|
||||
selling_point_ids?: string[];
|
||||
persona?: string;
|
||||
base_version_id?: string;
|
||||
aspect_ratio?: string;
|
||||
total_duration?: number;
|
||||
|
||||
@@ -81,16 +81,18 @@
|
||||
--honey-bg: rgba(236, 183, 48, .08);
|
||||
--honey-bd: rgba(236, 183, 48, .20);
|
||||
|
||||
/* ===== Heat · 单 hue + 8 档 alpha · 丁香蓝(对齐 YZ 主 CTA) ===== */
|
||||
--heat: #A5B4FC;
|
||||
--heat-hover: #8ea0f8;
|
||||
--heat-90: rgba(165, 180, 252, .90);
|
||||
--heat-40: rgba(165, 180, 252, .40);
|
||||
--heat-20: rgba(165, 180, 252, .20);
|
||||
--heat-16: rgba(165, 180, 252, .16);
|
||||
--heat-12: rgba(165, 180, 252, .12);
|
||||
--heat-8: rgba(165, 180, 252, .08);
|
||||
--heat-4: rgba(165, 180, 252, .04);
|
||||
/* ===== Heat · 单 hue + 8 档 alpha · 克莱因蓝(对齐 YZ 主 CTA,别用浅紫以免像禁用) ===== */
|
||||
--klein: #002fa7;
|
||||
--klein-hover: #002680;
|
||||
--heat: var(--klein);
|
||||
--heat-hover: var(--klein-hover);
|
||||
--heat-90: rgba(0, 47, 167, .90);
|
||||
--heat-40: rgba(0, 47, 167, .40);
|
||||
--heat-20: rgba(0, 47, 167, .20);
|
||||
--heat-16: rgba(0, 47, 167, .16);
|
||||
--heat-12: rgba(0, 47, 167, .12);
|
||||
--heat-8: rgba(0, 47, 167, .08);
|
||||
--heat-4: rgba(0, 47, 167, .04);
|
||||
|
||||
/* ===== Black-alpha 阶梯 (20 档) ===== */
|
||||
--black-alpha-1: rgba(0, 0, 0, .01);
|
||||
@@ -133,13 +135,13 @@
|
||||
|
||||
/* ===== Shadows ===== */
|
||||
--shadow-cta:
|
||||
0 1px 2px rgba(129, 140, 248, .16),
|
||||
0 6px 16px rgba(165, 180, 252, .28);
|
||||
0 1px 2px rgba(0, 47, 167, .16),
|
||||
0 6px 16px rgba(0, 47, 167, .24);
|
||||
--shadow-cta-hover:
|
||||
0 2px 4px rgba(129, 140, 248, .20),
|
||||
0 10px 22px rgba(165, 180, 252, .34);
|
||||
0 2px 4px rgba(0, 47, 167, .20),
|
||||
0 10px 22px rgba(0, 47, 167, .28);
|
||||
--shadow-cta-active:
|
||||
0 1px 2px rgba(129, 140, 248, .18);
|
||||
0 1px 2px rgba(0, 47, 167, .18);
|
||||
--shadow-floating: 0 8px 28px rgba(17, 19, 24, .06);
|
||||
}
|
||||
|
||||
@@ -220,11 +222,11 @@ img, svg, video { display: block; max-width: 100%; }
|
||||
/* ─── App shell ─── */
|
||||
.app {
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-width) 1fr;
|
||||
grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
|
||||
min-height: 100vh;
|
||||
transition: grid-template-columns var(--t-base);
|
||||
}
|
||||
body.sidebar-collapsed .app { grid-template-columns: 96px 1fr; }
|
||||
body.sidebar-collapsed .app { grid-template-columns: 96px minmax(0, 1fr); }
|
||||
|
||||
/* ─── Sidebar ─── */
|
||||
aside.sidebar {
|
||||
@@ -758,7 +760,17 @@ body.sidebar-collapsed .user .em,
|
||||
body.sidebar-collapsed .user::after { display: none; }
|
||||
|
||||
/* ─── Main + grid background ─── */
|
||||
main { position: relative; background: #fff; min-width: 0; }
|
||||
main { position: relative; background: #fff; min-width: 0; overflow-x: hidden; }
|
||||
.app:has(.pipeline-page) {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
main:has(.pipeline-page) {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.grid-bg {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -817,12 +829,13 @@ main { position: relative; background: #fff; min-width: 0; }
|
||||
/* ─── Topbar ─── */
|
||||
.topbar {
|
||||
display: flex; align-items: center; gap: 16px;
|
||||
padding: 0 24px;
|
||||
padding: 0 clamp(36px, 3.2vw, 68px);
|
||||
border-bottom: 1px solid rgba(27, 32, 40, 0.08);
|
||||
background: #fff;
|
||||
position: sticky; top: 0; z-index: 50;
|
||||
height: 116px;
|
||||
min-height: 116px;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
@@ -926,7 +939,7 @@ main { position: relative; background: #fff; min-width: 0; }
|
||||
color: var(--black-alpha-40);
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
.topbar .right { margin-left: auto; display: flex; align-items: center; gap: 10px; }
|
||||
.topbar .right { margin-left: auto; display: flex; align-items: center; gap: 10px; min-width: 0; flex: 0 1 auto; }
|
||||
.balance-chip {
|
||||
display: inline-flex; align-items: center; gap: 7px;
|
||||
height: 40px;
|
||||
@@ -1007,6 +1020,14 @@ main { position: relative; background: #fff; min-width: 0; }
|
||||
min-height: calc(100vh - 116px);
|
||||
animation: yz-page-enter 280ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
.content:has(.pipeline-page) {
|
||||
padding: 0;
|
||||
flex: 1 1 auto;
|
||||
height: auto;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
animation: none;
|
||||
}
|
||||
@keyframes yz-page-enter {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
|
||||
@@ -7,17 +7,14 @@
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
box-sizing: border-box;
|
||||
margin: -24px -28px -60px;
|
||||
margin: 0;
|
||||
padding: 28px clamp(40px, 3.2vw, 68px) 32px;
|
||||
height: calc(100vh - 116px);
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
@media (max-width: 1100px) {
|
||||
.pipeline-page { margin: -28px -24px -48px; }
|
||||
}
|
||||
|
||||
.pipeline-page .pl-head {
|
||||
min-height: 54px;
|
||||
@@ -1223,9 +1220,10 @@
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--border-faint);
|
||||
border: 1px solid rgba(0, 47, 167, 0.13);
|
||||
border-radius: 12px;
|
||||
background: var(--surface);
|
||||
box-shadow: 0 12px 28px rgba(20, 27, 38, 0.07);
|
||||
cursor: pointer;
|
||||
}
|
||||
.video-card:hover { border-color: rgba(0, 47, 167, 0.28); }
|
||||
@@ -1235,7 +1233,55 @@
|
||||
aspect-ratio: 9 / 16;
|
||||
max-height: 280px;
|
||||
overflow: hidden;
|
||||
background: #e5e6e9;
|
||||
background: #fff;
|
||||
}
|
||||
.video-thumb.pending::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background-color: #fff;
|
||||
background-image:
|
||||
radial-gradient(ellipse 4px 1px at center, rgba(0, 47, 167, 0.28) 98%, transparent),
|
||||
radial-gradient(ellipse 1px 4px at center, rgba(0, 47, 167, 0.28) 98%, transparent);
|
||||
background-position: center;
|
||||
background-size: 10px 10px;
|
||||
-webkit-mask-image: radial-gradient(ellipse 42% 28% at center, #000 0 18%, rgba(0, 0, 0, 0.78) 40%, rgba(0, 0, 0, 0.24) 72%, transparent 100%);
|
||||
mask-image: radial-gradient(ellipse 42% 28% at center, #000 0 18%, rgba(0, 0, 0, 0.78) 40%, rgba(0, 0, 0, 0.24) 72%, transparent 100%);
|
||||
}
|
||||
.video-thumb.pending::after,
|
||||
.video-thumb.generating::after {
|
||||
content: "待生成";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
z-index: 2;
|
||||
pointer-events: none;
|
||||
min-width: 72px;
|
||||
height: 32px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 0 14px;
|
||||
border: 1px solid rgba(0, 47, 167, 0.26);
|
||||
border-radius: 8px;
|
||||
color: var(--klein);
|
||||
background: #fff;
|
||||
box-shadow: 0 8px 22px rgba(0, 47, 167, 0.11);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.video-thumb.generating::after { content: "生成中"; }
|
||||
.video-thumb .video-loader {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
background:
|
||||
linear-gradient(120deg, transparent 20%, rgba(255, 255, 255, 0.88) 43%, transparent 66%) 0 0 / 220% 100%,
|
||||
linear-gradient(145deg, #eaf1fb, #f8fbff 48%, #e9eef7);
|
||||
animation: videoShimmer 2.2s linear infinite;
|
||||
}
|
||||
.video-thumb video {
|
||||
position: absolute;
|
||||
@@ -1531,8 +1577,265 @@
|
||||
.vd-history-thumb.tri .placeholder { aspect-ratio: 16/9; }
|
||||
}
|
||||
|
||||
/* 弹窗 portal 到 body,选择器必须顶层,否则会被工作区 overflow 裁掉顶栏 */
|
||||
.asset-modal-bg {
|
||||
--klein: #002fa7;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1800;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32px 24px;
|
||||
background: rgba(21, 20, 15, 0.42);
|
||||
}
|
||||
.asset-modal {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: 12px;
|
||||
width: min(960px, 100%);
|
||||
max-height: calc(100vh - 48px);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.18);
|
||||
}
|
||||
.asset-modal-h {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 56px;
|
||||
padding: 12px 20px;
|
||||
border-bottom: 1px solid var(--border-faint);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.asset-modal-h h2 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-black);
|
||||
}
|
||||
.asset-modal-h .ad-tag,
|
||||
.asset-modal-h .mono {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--black-alpha-48);
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
.asset-modal-h .x {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
color: var(--black-alpha-56);
|
||||
border-radius: var(--r-sm);
|
||||
margin-left: auto;
|
||||
}
|
||||
.asset-modal-h .x:hover { background: var(--black-alpha-08); color: var(--accent-black); }
|
||||
.asset-modal-body { padding: 20px 24px 24px; overflow-y: auto; flex: 1; min-height: 0; }
|
||||
.asset-modal-f {
|
||||
padding: 14px 20px;
|
||||
border-top: 1px solid var(--border-faint);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.asset-detail-grid { display: grid; grid-template-columns: 280px minmax(0, 1fr); gap: 24px; align-items: start; }
|
||||
.asset-detail-lead { display: flex; flex-direction: column; gap: 10px; }
|
||||
.asset-detail-lead .ad-lead-wrap { position: relative; }
|
||||
.asset-detail-lead .placeholder.ad-lead-img { aspect-ratio: 9/16; border-radius: var(--r-md); }
|
||||
.ad-zoom-btn {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
bottom: 8px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
background: rgba(21, 20, 15, .7);
|
||||
color: #fff;
|
||||
border: 0;
|
||||
border-radius: var(--r-sm);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
transition: opacity var(--t-base), background var(--t-base);
|
||||
z-index: 3;
|
||||
}
|
||||
.ad-zoom-btn:hover { background: rgba(21, 20, 15, .92); }
|
||||
.ad-zoom-btn svg { width: 14px; height: 14px; }
|
||||
.asset-detail-lead .ad-lead-wrap:hover .ad-zoom-btn,
|
||||
.asset-detail-tri-row .ad-tri-wrap:hover .ad-zoom-btn { opacity: 1; }
|
||||
.asset-detail-tri-row .ad-tri-wrap { position: relative; }
|
||||
.asset-detail-lead .ad-thumbs { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.asset-detail-lead .ad-thumbs .thumb {
|
||||
flex: 0 0 56px;
|
||||
width: 56px;
|
||||
aspect-ratio: 3/4;
|
||||
border-radius: var(--r-sm);
|
||||
border: 1px solid var(--border-faint);
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
transition: border-color var(--t-base);
|
||||
}
|
||||
.asset-detail-lead .ad-thumbs .thumb:hover { border-color: var(--heat-40); }
|
||||
.asset-detail-lead .ad-thumbs .thumb.active { border-color: var(--heat); border-width: 2px; }
|
||||
.asset-detail-right .ad-section + .ad-section { margin-top: 18px; }
|
||||
.asset-detail-section-h {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--accent-black);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.asset-detail-section-h .ic { width: 14px; height: 14px; color: var(--klein); flex-shrink: 0; display: grid; place-items: center; }
|
||||
.asset-detail-section-h .ic svg { width: 14px; height: 14px; }
|
||||
.asset-detail-section-h .ad-ratio-chip {
|
||||
margin-left: auto;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--r-sm);
|
||||
background: var(--background-lighter);
|
||||
border: 1px solid var(--border-faint);
|
||||
color: var(--black-alpha-56);
|
||||
letter-spacing: .02em;
|
||||
}
|
||||
.asset-detail-section-h .ad-icon-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-sm);
|
||||
color: var(--black-alpha-56);
|
||||
cursor: pointer;
|
||||
transition: background var(--t-base), color var(--t-base), border-color var(--t-base);
|
||||
}
|
||||
.asset-detail-section-h .ad-icon-btn:hover { background: var(--klein); color: #fff; border-color: var(--klein); }
|
||||
.asset-detail-section-h .ad-icon-btn svg { width: 12px; height: 12px; }
|
||||
.asset-detail-section-h .ad-icon-gap { margin-left: 8px; }
|
||||
.asset-detail-tri-row .placeholder { aspect-ratio: 16 / 9; border-radius: var(--r-md); }
|
||||
.md-view-versions { display: flex; gap: 6px; overflow-x: auto; padding: 2px; margin-top: 8px; }
|
||||
.md-view-versions .v-thumb {
|
||||
flex: 0 0 auto;
|
||||
width: 72px;
|
||||
aspect-ratio: 16/9;
|
||||
background: var(--background-lighter);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-sm);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
overflow: hidden;
|
||||
transition: border-color var(--t-base);
|
||||
}
|
||||
.md-view-versions .v-thumb:hover { border-color: var(--heat-40); }
|
||||
.md-view-versions .v-thumb.active { border-color: var(--heat); border-width: 2px; box-shadow: 0 0 0 2px var(--heat-12); }
|
||||
.md-view-versions .v-thumb .v { font-family: var(--font-mono); font-size: 10px; color: var(--black-alpha-56); letter-spacing: .02em; }
|
||||
.md-view-versions .v-thumb.active .v { color: var(--heat); font-weight: 600; }
|
||||
.asset-detail-tip {
|
||||
margin-top: 10px;
|
||||
padding: 10px 12px;
|
||||
background: var(--heat-12);
|
||||
border: 1px solid var(--heat-20);
|
||||
border-radius: var(--r-sm);
|
||||
font-size: 12px;
|
||||
color: var(--accent-black);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.asset-detail-tip svg { width: 14px; height: 14px; color: var(--heat); flex-shrink: 0; }
|
||||
.asset-detail-tip .ai-gen-btn {
|
||||
margin-left: auto;
|
||||
height: 26px;
|
||||
padding: 0 10px;
|
||||
background: var(--heat);
|
||||
color: var(--accent-white);
|
||||
border: 1px solid var(--heat);
|
||||
border-radius: var(--r-sm);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.asset-modal-f .ad-foot-stats { display: flex; gap: 6px; margin-right: auto; }
|
||||
.asset-modal-f .ad-stat-btn {
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-sm);
|
||||
color: var(--black-alpha-72);
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
transition: background var(--t-base), color var(--t-base), border-color var(--t-base);
|
||||
}
|
||||
.asset-modal-f .ad-stat-btn:hover { background: var(--background-lighter); color: var(--accent-black); }
|
||||
.asset-modal-f .ad-stat-btn svg { width: 13px; height: 13px; }
|
||||
.ad-detail-prompt {
|
||||
width: 100%;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
color: var(--black-alpha-72);
|
||||
background: var(--background-base);
|
||||
border: 1px solid var(--border-faint);
|
||||
border-radius: var(--r-sm);
|
||||
padding: 10px 12px;
|
||||
outline: none;
|
||||
resize: none;
|
||||
height: 88px;
|
||||
transition: border-color var(--t-base), background var(--t-base), box-shadow var(--t-base);
|
||||
}
|
||||
.ad-thumbs .thumb.has-mock-media,
|
||||
.md-view-versions .v-thumb.has-mock-media,
|
||||
.prod-preview-history .h-thumb.has-mock-media {
|
||||
background-image: var(--mock-media-url);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.ad-detail-prompt:hover { border-color: var(--heat-20); }
|
||||
.ad-detail-prompt:focus { border-color: var(--heat); background: var(--surface); color: var(--accent-black); box-shadow: 0 0 0 3px var(--heat-12); }
|
||||
.vd-main-wrap { display: flex; gap: 18px; align-items: flex-start; }
|
||||
.vd-main { flex: 0 0 280px; aspect-ratio: 9/16; max-height: 460px; background: #000; border-radius: var(--r-md); overflow: hidden; position: relative; }
|
||||
.vd-main video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; }
|
||||
.vd-info { flex: 1; min-width: 0; }
|
||||
.vd-section-h { display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 600; color: var(--accent-black); margin-bottom: 10px; }
|
||||
.vd-kv { display: grid; grid-template-columns: 72px 1fr; gap: 6px 10px; font-size: 13px; }
|
||||
.vd-kv .k { font-family: var(--font-mono); font-size: 12px; color: var(--black-alpha-48); letter-spacing: .03em; padding-top: 1px; }
|
||||
.vd-kv .v { color: var(--accent-black); min-width: 0; overflow-wrap: anywhere; }
|
||||
.vd-history-h { font-family: var(--font-mono); font-size: 12px; color: var(--black-alpha-48); letter-spacing: .06em; text-transform: uppercase; margin-bottom: 10px; }
|
||||
.vd-history-row { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 6px; scrollbar-width: thin; }
|
||||
.vd-history-thumb { flex: 0 0 64px; min-width: 64px; display: flex; flex-direction: column; gap: 4px; padding: 4px; border: 1px solid var(--border-faint); border-radius: var(--r-sm); background: var(--surface); cursor: pointer; transition: border-color var(--t-base); position: relative; }
|
||||
.vd-history-thumb:hover { border-color: var(--heat); }
|
||||
.vd-history-thumb.current { border-color: var(--heat); background: var(--heat-12); }
|
||||
.vd-history-thumb .placeholder { aspect-ratio: 9/16; }
|
||||
.vd-history-thumb .ts { font-family: var(--font-mono); font-size: 12px; color: var(--black-alpha-48); text-align: center; }
|
||||
.vd-prompt-field { margin-top: 16px; }
|
||||
.vd-prompt-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 6px; }
|
||||
.vd-prompt-head .label { font-family: var(--font-mono); font-size: 12px; font-weight: 500; color: var(--black-alpha-56); letter-spacing: .04em; }
|
||||
.vd-prompt-edit { background: var(--background-base); border: 1px solid var(--border-faint); border-radius: var(--r-md); padding: 12px 14px; font-family: var(--font-mono); font-size: 12px; line-height: 1.7; color: var(--accent-black); white-space: pre-wrap; min-height: 96px; outline: none; letter-spacing: .01em; cursor: text; }
|
||||
.vd-modal-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; }
|
||||
|
||||
/* 流程步骤4 · 基础资产 loading 转圈(顶层 @keyframes 才注册) */
|
||||
@keyframes assetSpin { to { transform: rotate(360deg); } }
|
||||
@keyframes videoShimmer { to { background-position: -220% 0, 0 0; } }
|
||||
|
||||
/* 进度流逐条滚入(顶层 @keyframes 才注册) */
|
||||
@keyframes psRowIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@@ -1646,7 +1949,7 @@
|
||||
.ref-gate-mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 60;
|
||||
z-index: 1800;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Fragment, memo, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import type { ChangeEvent, CSSProperties, PointerEvent as ReactPointerEvent } from "react";
|
||||
import { ArrowLeft, ArrowRight, Check, ChevronRight, Image, Info, LayoutList, Play, RefreshCw, Route, Sparkles, Upload, UsersRound, X } from "lucide-react";
|
||||
import { api, ApiError } from "../api";
|
||||
@@ -352,7 +353,9 @@ function PromptBox({ value, onChange, className = "prompt-box", id, ariaLabel, s
|
||||
|
||||
// 分镜「旁白 / 画面」可编辑框:非受控 textarea,落库走 onBlur。
|
||||
// 聚焦中绝不覆盖,避免提交后父组件用旧值把正在输入的字冲掉。
|
||||
function ShotArea({ value, placeholder, ariaLabel, onCommit }: { value: string; placeholder: string; ariaLabel: string; onCommit: (text: string) => void }) {
|
||||
function ShotArea({ value, placeholder, ariaLabel, onCommit, rows = 2 }: {
|
||||
value: string; placeholder: string; ariaLabel: string; onCommit: (text: string) => void; rows?: number;
|
||||
}) {
|
||||
const ref = useRef<HTMLTextAreaElement | null>(null);
|
||||
useEffect(() => {
|
||||
const el = ref.current;
|
||||
@@ -366,7 +369,7 @@ function ShotArea({ value, placeholder, ariaLabel, onCommit }: { value: string;
|
||||
defaultValue={value}
|
||||
placeholder={placeholder}
|
||||
aria-label={ariaLabel}
|
||||
rows={2}
|
||||
rows={rows}
|
||||
onBlur={(e) => {
|
||||
const text = e.currentTarget.value.trim();
|
||||
if (text !== value) onCommit(text);
|
||||
@@ -404,7 +407,7 @@ function DraftShotCard({ draft, onCommit, onCancel }: {
|
||||
</label>
|
||||
<label className="shot-edit">
|
||||
<span>画面内容</span>
|
||||
<textarea className="shot-area" rows={2} placeholder="输入这一镜的画面描述…" value={visual}
|
||||
<textarea className="shot-area" rows={6} placeholder="0-3s:近景……(按秒写出每一镜)" value={visual}
|
||||
onChange={(e) => setVisual(e.target.value)}
|
||||
onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) { e.preventDefault(); commit(); } else if (e.key === "Escape") { onCancel?.(draft.id); } }} />
|
||||
</label>
|
||||
@@ -599,7 +602,7 @@ export function PipelinePage(props: {
|
||||
try { return new Set<string>(JSON.parse(localStorage.getItem(`airshelf:pending-del:${project.id}`) || "[]")); } catch { return new Set(); }
|
||||
});
|
||||
const shots = [...(currentScript?.segments ?? [])].sort((a, b) => a.sort_order - b.sort_order).filter((s) => !pendingDeletes.has(s.id));
|
||||
// 单镜时长自二期起是变长的(4–15s),缺值时按上限兜底,跟后端 script_agent 一致
|
||||
// 单镜时长主流程固定 15s;缺值时按上限兜底,跟后端 script_agent 一致
|
||||
const shotSeconds = (s: { duration_seconds?: number | null }) => s.duration_seconds || SEGMENT_DURATION_MAX;
|
||||
const scriptTotalSeconds = shots.reduce((sum, s) => sum + shotSeconds(s), 0);
|
||||
// 对白 speaker(entity id)→ 角色名;null = 旁白。entities 在 ScriptVersion.metadata
|
||||
@@ -1534,9 +1537,11 @@ export function PipelinePage(props: {
|
||||
mode: agentMode,
|
||||
prompt,
|
||||
model_config_id: activeScriptModelId || undefined,
|
||||
selling_point_ids: Array.isArray(wizard?.selling_point_ids) ? wizard.selling_point_ids : undefined,
|
||||
persona: setupPersona,
|
||||
base_version_id: baseVersionId,
|
||||
aspect_ratio: "9:16",
|
||||
// 设定卡的三个参数一路传到后端:后端据此挑套路提示词 + 按节奏切镜(不再每镜写死 15 秒)
|
||||
// 设定卡参数一路传到后端:每镜固定 15 秒,总时长 15/30/45/60
|
||||
total_duration: setupDuration,
|
||||
presentation_format: setupFormat,
|
||||
video_structure: setupStructure,
|
||||
@@ -1625,8 +1630,7 @@ export function PipelinePage(props: {
|
||||
pushMsg("ai", summaryText || "镜头脚本已生成,左侧已刷新。可继续输入修改意见,或点「确认脚本」进入下一步。");
|
||||
}
|
||||
}
|
||||
// 1.5 · 确认设定后真正发起生成。表现形式/结构/时长走结构化参数(后端挑套路),
|
||||
// 人物没有对应的后端字段,仍并进 prompt 让模型读。
|
||||
// 1.5 · 确认设定后真正发起生成。形式/结构/时长/人物/勾选卖点都走结构化参数,不再塞一句空主题。
|
||||
async function runScriptWithSetup() {
|
||||
const format = coercePresentationFormat(setupFormat);
|
||||
const structure = coerceVideoStructure(setupStructure);
|
||||
@@ -1684,12 +1688,12 @@ export function PipelinePage(props: {
|
||||
// 5.2 换商品重跑:新建项目时选了套路模板 → 把模板的镜序/节奏/转化写法并进 prompt,
|
||||
// 但明确要求文案按当前商品重写(模板里不含旧文案,转化写法可能带旧品牌词,这里再挡一道)。
|
||||
const tplLine = templateOutline
|
||||
? `\n请照这个套路重出一版:\n${templateOutline}\n`
|
||||
+ `镜数、每镜作用与时长按上面来,文案全部按我当前的商品重新写,`
|
||||
? `请照这个套路重出一版:\n${templateOutline}\n`
|
||||
+ `镜数、每镜作用按上面来(每镜 15 秒),文案全部按我当前的商品重新写,`
|
||||
+ `参考写法里出现的旧商品、品牌、价格一律不要保留。`
|
||||
: "";
|
||||
await runScriptGeneration(
|
||||
`目标人群:${personaLabel}。突出商品卖点,节奏紧凑,适合短视频投放${tplLine}`,
|
||||
tplLine,
|
||||
templateName ? `套用模板「${templateName}」:${combo}` : `${sourceLabel}:${combo}`,
|
||||
"ai",
|
||||
);
|
||||
@@ -3004,8 +3008,9 @@ export function PipelinePage(props: {
|
||||
<span>画面内容</span>
|
||||
<ShotArea
|
||||
value={visualShown}
|
||||
placeholder="点击编辑画面内容"
|
||||
placeholder="0-3s:近景……(按秒写出每一镜)"
|
||||
ariaLabel={`场景 ${index + 1} 画面内容`}
|
||||
rows={6}
|
||||
onCommit={(text) => { void onUpdateShot({ segment_id: shot.id, visual_prompt: text }); }}
|
||||
/>
|
||||
</label>
|
||||
@@ -3102,7 +3107,7 @@ export function PipelinePage(props: {
|
||||
</select>
|
||||
</label>
|
||||
<div className="setup-rec">根据商品目标人群推荐</div>
|
||||
{/* 1.9 时长自由化:5–60 秒、5 秒一档;每镜切多长由后端按表现形式的节奏决定 */}
|
||||
{/* 时长:15/30/45/60;每镜固定 15 秒 */}
|
||||
<label className="setup-field">
|
||||
<span className="sf-k">时长</span>
|
||||
<select className="setup-select" value={setupDuration} onChange={(e) => { setupTouched.current = true; setSetupDuration(clampDuration(Number(e.target.value))); }}>
|
||||
@@ -3453,7 +3458,7 @@ export function PipelinePage(props: {
|
||||
return (
|
||||
<div className="as-gen" data-asset-kind="scene" key={dk}>
|
||||
<div className={`as-gen-preview${busy ? " generating" : " pending"}`}>
|
||||
{busy ? <span className="asset-card-loading"><span className="asset-spinner" aria-hidden="true"></span><span>生成中 · 约 8s</span></span> : null}
|
||||
{busy ? <span className="asset-card-loading"><span className="asset-spinner" aria-hidden="true"></span><span>生成中</span></span> : null}
|
||||
</div>
|
||||
<div className="as-gen-meta">
|
||||
<input type="text" value={draft.title} placeholder="场景图待生成" aria-label="场景名" disabled={busy} data-stop
|
||||
@@ -3498,7 +3503,7 @@ export function PipelinePage(props: {
|
||||
onClick={() => openSeedDetail(kind, tag, promptValue)}
|
||||
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); openSeedDetail(kind, tag, promptValue); } }}
|
||||
>
|
||||
{busy ? <span className="asset-card-loading"><span className="asset-spinner" aria-hidden="true"></span><span>生成中 · 约 8s</span></span> : null}
|
||||
{busy ? <span className="asset-card-loading"><span className="asset-spinner" aria-hidden="true"></span><span>生成中</span></span> : null}
|
||||
</div>
|
||||
<div className="as-gen-meta">
|
||||
<h4 onClick={() => openSeedDetail(kind, tag, promptValue)}>{tag || (kind === "person" ? "角色图待生成" : "场景图待生成")}</h4>
|
||||
@@ -3823,11 +3828,17 @@ export function PipelinePage(props: {
|
||||
const showBusy = busy || pending;
|
||||
const verCount = seg.versions?.length ?? 0;
|
||||
return (
|
||||
<div className="video-card" key={seg.id} data-video-id={seg.id}>
|
||||
<div className="video-thumb" role="button" tabIndex={0} title="点击查看详情" onClick={() => openVideoDetail(seg.id)} onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); openVideoDetail(seg.id); } }}>
|
||||
{url
|
||||
? <video src={`${url}#t=0.1`} muted playsInline preload="metadata" />
|
||||
: <span className="ph-frame">场 {seg.sort_order + 1}</span>}
|
||||
<div className={`video-card${url ? " is-ready" : showBusy ? " is-generating" : " is-pending"}`} key={seg.id} data-video-id={seg.id}>
|
||||
<div
|
||||
className={`video-thumb${url ? " ready" : ""}${showBusy ? " generating" : ""}${!url && !showBusy ? " pending" : ""}`}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
title="点击查看详情"
|
||||
aria-label={showBusy ? `场 ${seg.sort_order + 1} 生成中` : url ? `场 ${seg.sort_order + 1}` : `场 ${seg.sort_order + 1} 待生成`}
|
||||
onClick={() => openVideoDetail(seg.id)}
|
||||
onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); openVideoDetail(seg.id); } }}
|
||||
>
|
||||
{url ? <video src={`${url}#t=0.1`} muted playsInline preload="metadata" /> : null}
|
||||
{url && !showBusy && (
|
||||
<button
|
||||
className="btn-play"
|
||||
@@ -3838,7 +3849,7 @@ export function PipelinePage(props: {
|
||||
<Play size={16} fill="currentColor" />
|
||||
</button>
|
||||
)}
|
||||
{showBusy && <span className="sb-gen-veil" aria-hidden="true"><span className="spinner" /></span>}
|
||||
{showBusy ? <span className="video-loader" aria-hidden="true" /> : null}
|
||||
</div>
|
||||
<div className="body">
|
||||
<div className="video-card-head">
|
||||
@@ -4282,7 +4293,7 @@ export function PipelinePage(props: {
|
||||
const showBusy = busy || rerunPending.has(vdSeg.id);
|
||||
const hint = (shots[vdSeg.sort_order]?.visual_prompt || shots[vdSeg.sort_order]?.narration || "").trim();
|
||||
const rerunPrompt = vdPrompt.trim() || `${videoPrompt} 第 ${vdSeg.sort_order + 1} 段,时长 ${vdSeg.target_duration_seconds} 秒`;
|
||||
return (
|
||||
return createPortal(
|
||||
<div className="asset-modal-bg" role="dialog" aria-modal="true" aria-label="视频详情" onClick={(event) => { if (event.target === event.currentTarget) setVdSegId(null); }}>
|
||||
<div className="asset-modal">
|
||||
<div className="asset-modal-h">
|
||||
@@ -4349,9 +4360,10 @@ export function PipelinePage(props: {
|
||||
{showBusy ? <><span className="spinner btn-spin" aria-hidden="true" />{busy ? "生成中…" : "提交中…"}</> : "↻ 重跑本场"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
})()}
|
||||
{/* ── 流程步骤4/5 · 兜底弹窗拦截:参考图不齐时拦住生成,让用户去补 / 仍要生成(随他便) ── */}
|
||||
@@ -4359,7 +4371,7 @@ export function PipelinePage(props: {
|
||||
// 拆两类:noref=连参考图都没有;notri=有立绘但缺三视图。文案分别告诉用户「去哪里点什么」。
|
||||
const noRef = refGate.missing.filter((m) => m.reason === "noref");
|
||||
const noTri = refGate.missing.filter((m) => m.reason === "notri");
|
||||
return (
|
||||
return createPortal(
|
||||
<div className="ref-gate-mask" onClick={() => setRefGate(null)}>
|
||||
<div className="ref-gate-modal" role="dialog" aria-modal="true" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="rg-title">资产还没齐,先补一下?</div>
|
||||
@@ -4398,11 +4410,12 @@ export function PipelinePage(props: {
|
||||
<button type="button" className="btn btn-ghost rg-force" onClick={() => { const go = refGate.proceed; setRefGate(null); go(); }}>仍要继续 <span className="rg-warn">可能跟角色对不上</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
})()}
|
||||
{/* ── 流程步骤5 · 过审闸弹窗:含真人脸的人物/分镜未过审 → 列出哪一镜/哪个,先过审再生成(火山合规要求) ── */}
|
||||
{reviewGate && (
|
||||
{reviewGate && createPortal(
|
||||
<div className="ref-gate-mask" onClick={() => setReviewGate(null)}>
|
||||
<div className="ref-gate-modal" role="dialog" aria-modal="true" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="rg-title">这些素材还没过审,先过审再生成视频</div>
|
||||
@@ -4440,7 +4453,8 @@ export function PipelinePage(props: {
|
||||
<button type="button" className="btn btn-ghost" onClick={() => setReviewGate(null)}>知道了</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
{/* ── 流程步骤4 · 人物/场景详情弹窗:立绘 + 三视图(人物)+ 提示词重获 + 版本历史 + 应用到当前项目 ── */}
|
||||
{adDetail && (() => {
|
||||
@@ -4494,12 +4508,12 @@ export function PipelinePage(props: {
|
||||
setAdTriId(null);
|
||||
}
|
||||
const zoomSvg = <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M3 8V3h5M16 3h5v5M21 16v5h-5M8 21H3v-5" /></svg>;
|
||||
return (
|
||||
return createPortal(
|
||||
<div className="asset-modal-bg" role="dialog" aria-modal="true" aria-label={`${KIND_LABEL[adDetail.kind]}详情`} onClick={(event) => { if (event.target === event.currentTarget) setAdDetail(null); }}>
|
||||
<div className="asset-modal">
|
||||
<div className="asset-modal-h">
|
||||
<h2>资产详情</h2>
|
||||
<span className="ad-tag">/ {KIND_LABEL[adDetail.kind]} · {entity.name}</span>
|
||||
<h2>{entity.name}</h2>
|
||||
<span className="ad-tag">/ {KIND_LABEL[adDetail.kind]}</span>
|
||||
<button className="x" type="button" aria-label="关闭" onClick={() => setAdDetail(null)}>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M6 6l12 12M6 18L18 6" /></svg>
|
||||
</button>
|
||||
@@ -4629,11 +4643,12 @@ export function PipelinePage(props: {
|
||||
}}>应用当前角色</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
})()}
|
||||
{/* 问题5(a):用已有素材作三视图——选商品已有图 / 上传本地图,attach 成商品采用版(不走平台 AI 生成、不计费) */}
|
||||
{assetPick && (
|
||||
{assetPick && createPortal(
|
||||
<div className="asset-modal-bg" role="dialog" aria-modal="true" aria-label={assetPick.title} onClick={(e) => { if (e.target === e.currentTarget && !assetPickBusy) setAssetPick(null); }}>
|
||||
<div className="asset-modal">
|
||||
<div className="asset-modal-h">
|
||||
@@ -4675,7 +4690,8 @@ export function PipelinePage(props: {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
{/* 流程步骤4 · 模特库:浏览 / 添加模特(AI 生成·本地上传)/ 替换回填 */}
|
||||
<ModelLibrary
|
||||
|
||||
@@ -79,7 +79,7 @@ export function ProjectWizardPage({ products, projects = [], preselectProductId,
|
||||
useEffect(() => {
|
||||
const p = products.find((item) => item.id === productId);
|
||||
const seed: Record<string, boolean> = {};
|
||||
(p?.selling_points || []).forEach((sp) => { seed[sp.title] = false; });
|
||||
(p?.selling_points || []).forEach((sp) => { seed[sp.id || sp.title] = false; });
|
||||
setPoints(seed);
|
||||
}, [productId, products]);
|
||||
|
||||
@@ -344,12 +344,15 @@ export function ProjectWizardPage({ products, projects = [], preselectProductId,
|
||||
<div className="nw-field">
|
||||
<span>关键卖点<small>可勾选要重点突出的</small></span>
|
||||
<div className="nw-pills">
|
||||
{Object.entries(points).map(([key, on]) => (
|
||||
{Object.entries(points).map(([key, on]) => {
|
||||
const label = product?.selling_points?.find((sp) => (sp.id || sp.title) === key)?.title || key;
|
||||
return (
|
||||
<button className={`nw-pill${on ? " on" : ""}`} type="button" key={key} aria-pressed={on} onClick={() => setPoints((prev) => ({ ...prev, [key]: !prev[key] }))}>
|
||||
{on && <Check size={13} />}
|
||||
<span>{key}</span>
|
||||
<span>{label}</span>
|
||||
</button>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -69,12 +69,12 @@ export function allowedStructures(format: PresentationFormat): VideoStructure[]
|
||||
return STRUCTURE_KEYS.filter((key) => !isForbidden(format, key));
|
||||
}
|
||||
|
||||
// ── 时长:总时长 5–60 秒、5 秒步进;单镜 4–15 秒由后端切,前端只管总时长 ──
|
||||
// ── 时长:总时长 15/30/45/60;主流程每镜固定 15 秒 ──
|
||||
export const SEGMENT_DURATION_MIN = 4;
|
||||
export const SEGMENT_DURATION_MAX = 15;
|
||||
export const TOTAL_DURATION_MIN = 5;
|
||||
export const TOTAL_DURATION_MIN = 15;
|
||||
export const TOTAL_DURATION_MAX = 60;
|
||||
export const TOTAL_DURATION_STEP = 5;
|
||||
export const TOTAL_DURATION_STEP = 15;
|
||||
export const DURATION_OPTIONS: number[] = Array.from(
|
||||
{ length: (TOTAL_DURATION_MAX - TOTAL_DURATION_MIN) / TOTAL_DURATION_STEP + 1 },
|
||||
(_, i) => TOTAL_DURATION_MIN + i * TOTAL_DURATION_STEP
|
||||
@@ -82,8 +82,8 @@ export const DURATION_OPTIONS: number[] = Array.from(
|
||||
|
||||
/** 表现形式推荐的默认总时长:口播短平快 / 短剧要装下三幕 / Vlog 要铺氛围。 */
|
||||
const FORMAT_DEFAULT_DURATION: Record<PresentationFormat, number> = { oral: 30, drama: 45, vlog: 30 };
|
||||
/** 各结构能压到的最短总时长 —— 低于它证据或氛围就不成立了。 */
|
||||
const STRUCTURE_MIN_DURATION: Record<VideoStructure, number> = { pain: 15, contrast: 10, review: 20, scene: 20 };
|
||||
/** 各结构能压到的最短总时长 —— 低于它证据或氛围就不成立了。须落在 15 秒步进上。 */
|
||||
const STRUCTURE_MIN_DURATION: Record<VideoStructure, number> = { pain: 15, contrast: 15, review: 30, scene: 30 };
|
||||
|
||||
export function recommendDuration(format: PresentationFormat, structure: VideoStructure): number {
|
||||
return Math.max(FORMAT_DEFAULT_DURATION[format], STRUCTURE_MIN_DURATION[structure]);
|
||||
|
||||
@@ -56,10 +56,10 @@
|
||||
--ink-2: #5A5F6A;
|
||||
--ink-3: #8B909A;
|
||||
--ink-4: #C5C9D2;
|
||||
--orange: #A5B4FC;
|
||||
--orange-soft: #E4E9FE;
|
||||
--orange-tint: #F0F3FF;
|
||||
--orange-hover: #8B9CF7;
|
||||
--orange: #002fa7;
|
||||
--orange-soft: rgba(0, 47, 167, .18);
|
||||
--orange-tint: rgba(0, 47, 167, .06);
|
||||
--orange-hover: #002680;
|
||||
--green: #3F6B3F;
|
||||
--green-bg: #EAF2EA;
|
||||
--green-bd: #D5E5D5;
|
||||
@@ -393,7 +393,7 @@ main { position: relative; overflow: hidden; background: #fff; }
|
||||
font-family: inherit;
|
||||
color: var(--ink);
|
||||
}
|
||||
.input:focus, .textarea:focus, .select:focus { border-color: var(--orange); box-shadow: 0 0 0 3px rgba(165, 180, 252, 0.18); }
|
||||
.input:focus, .textarea:focus, .select:focus { border-color: var(--orange); box-shadow: 0 0 0 3px rgba(0, 47, 167, 0.18); }
|
||||
.input::placeholder, .textarea::placeholder { color: var(--ink-3); }
|
||||
.textarea { height: auto; min-height: 80px; padding: 10px 12px; line-height: 1.55; resize: vertical; }
|
||||
.select { appearance: none; background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 16 16' fill='none'><path d='M4 6l4 4 4-4' stroke='%239C988C' stroke-width='1.4'/></svg>"); background-repeat: no-repeat; background-position: right 10px center; padding-right: 30px; }
|
||||
@@ -714,8 +714,8 @@ table.t tbody tr:hover { background: var(--bg-soft); }
|
||||
--accent-crimson: #eb3424;
|
||||
--accent-forest: #42c366;
|
||||
--accent-honey: #ecb730;
|
||||
--heat: #A5B4FC;
|
||||
--heat-12: rgba(165, 180, 252, .12);
|
||||
--heat: #002fa7;
|
||||
--heat-12: rgba(0, 47, 167, .12);
|
||||
--black-alpha-32: rgba(38, 38, 38, .32);
|
||||
--black-alpha-48: rgba(38, 38, 38, .48);
|
||||
--black-alpha-56: rgba(38, 38, 38, .56);
|
||||
@@ -1020,7 +1020,7 @@ table.t tbody tr:hover { background: var(--bg-soft); }
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.auth-exact-page .btn-cta:hover { box-shadow: 0 4px 14px rgba(165, 180, 252, .28); }
|
||||
.auth-exact-page .btn-cta:hover { box-shadow: 0 4px 14px rgba(0, 47, 167, .28); }
|
||||
.auth-exact-page .btn-cta:active { transform: translateY(1px); }
|
||||
.auth-exact-page .btn-cta:disabled { opacity: .82; cursor: wait; }
|
||||
.auth-exact-page .btn-cta .busy-copy {
|
||||
|
||||
Reference in New Issue
Block a user