Merge remote-tracking branch 'origin/master' into codex/generation-task-reliability

This commit is contained in:
2026-06-08 15:08:26 +08:00
20 changed files with 876 additions and 67 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 685 KiB

+2 -2
View File
@@ -199,7 +199,7 @@ function createWorkflowFromResult(payload: WorkbenchResultActionPayload): WebCan
description: payload.prompt || "从生成结果进入画布继续创作。",
source: "blank",
settings: {
model: payload.resultType === "video" ? "Seedance 2.0" : "Nano Banana Pro",
model: payload.resultType === "video" ? "Seedance 2.0" : "omni-水果 Pro",
ratio: payload.resultType === "video" ? "16:9" : "1:1",
duration: payload.resultType === "video" ? "6s" : "0s",
resolution: payload.resultType === "video" ? "720p" : "2K",
@@ -1011,7 +1011,7 @@ function App() {
previewUrl: payload.resultUrl,
params: payload.resultType === "video"
? { model: "Kling V3 Omni", aspectRatio: "16:9", resolution: "720p", duration: "6s", videoMode: "text-to-video" }
: { model: "Nano Banana Pro", aspectRatio: "1:1", imageSize: "2K" },
: { model: "omni-水果 Pro", aspectRatio: "1:1", imageSize: "2K" },
assetRef: payload.resultOssKey ? { url: payload.resultUrl, ossKey: payload.resultOssKey, mediaType: payload.resultType === "video" ? "video/mp4" : "image/png", sourceTaskId: payload.taskId } : undefined,
},
];
+1 -1
View File
@@ -481,7 +481,7 @@ function migrateLegacyWorkflowData(old: Record<string, unknown>, wrapper: Record
description: String(wrapper.description || ""),
source: (wrapper.source as WebCanvasWorkflow["source"]) || "blank",
settings: {
model: String(isRecord(old.settings) ? old.settings.model || "Nano Banana Pro" : "Nano Banana Pro"),
model: String(isRecord(old.settings) ? old.settings.model || "omni-水果 Pro" : "omni-水果 Pro"),
ratio: String(isRecord(old.settings) ? old.settings.ratio || "1:1" : "1:1"),
duration: String(isRecord(old.settings) ? old.settings.duration || "0s" : "0s"),
resolution: String(isRecord(old.settings) ? old.settings.resolution || "2K" : "2K"),
+12 -1
View File
@@ -7,6 +7,7 @@ import { ossAssets } from "../data/ossAssets";
import { canManageCommunityCases, canReviewCommunity } from "../features/community-review/communityPermissions";
import type { WebNavItem, WebNotification, WebUsageSummary, WebUserSession, WebViewKey } from "../types";
import NotificationCenter from "./NotificationCenter";
import BetaApplicationModal from "./BetaApplicationModal";
import { AnimatedPanel } from "./AnimatedPanel";
import AdminMonitor from "./AdminMonitor";
import CookieConsentBanner from "./CookieConsentBanner";
@@ -85,6 +86,7 @@ function AppShell({
const [rechargeOpen, setRechargeOpen] = useState(false);
const [RechargeModal, setRechargeModal] = useState<RechargeModalComponent | null>(null);
const [infoOpen, setInfoOpen] = useState(false);
const [betaOpen, setBetaOpen] = useState(false);
const infoRef = useRef<HTMLDivElement>(null);
const [openSubmenuKey, setOpenSubmenuKey] = useState<WebViewKey | null>(null);
const [publicConfig, setPublicConfig] = useState<WebPublicConfig>({});
@@ -343,6 +345,15 @@ function AppShell({
<span className="brand-lockup__name">OmniAI</span>
</button>
<div className="web-topbar__actions">
<button
type="button"
className="beta-apply-button"
title="内测申请"
aria-label="内测申请"
onClick={() => setBetaOpen(true)}
>
</button>
{session && (
<NotificationCenter
items={notifications}
@@ -502,7 +513,7 @@ function AppShell({
{rechargeOpen && RechargeModal ? (
<RechargeModal open={rechargeOpen} onClose={() => setRechargeOpen(false)} currentBalance={displayedBalanceCents} />
) : null}
<CookieConsentBanner />
<BetaApplicationModal open={betaOpen} onClose={() => setBetaOpen(false)} />
</div>
);
}
+283
View File
@@ -0,0 +1,283 @@
import { CloseOutlined, ExperimentOutlined } from "@ant-design/icons";
import { useState } from "react";
interface BetaApplicationModalProps {
open: boolean;
onClose: () => void;
}
/* ── Form state ── */
interface BetaFormData {
name: string;
phone: string;
wechat: string;
industry: string;
company: string;
city: string;
aiTools: string;
aiDuration: string;
aiTrack: string;
aiDirection: string[];
weeklyUsage: string;
feedbackWilling: string;
wantFeature: string[];
selfStatement: string;
signature: string;
agreeRules: boolean;
}
const INITIAL_FORM: BetaFormData = {
name: "",
phone: "",
wechat: "",
industry: "",
company: "",
city: "",
aiTools: "",
aiDuration: "",
aiTrack: "",
aiDirection: [],
weeklyUsage: "",
feedbackWilling: "",
wantFeature: [],
selfStatement: "",
signature: "",
agreeRules: false,
};
/* ── Option groups (from the docx) ── */
const AI_DURATION_OPTIONS = ["1年以内", "1-3年", "3-5年", "5年以上"];
const AI_TRACK_OPTIONS = ["是,长期承接相关业务", "业余创作", "新手学习"];
const AI_DIRECTION_OPTIONS = [
"AI短剧批量制作", "漫剧剧情生成", "自媒体短视频", "电商图文及视频素材",
"MCN商业内容", "企业宣传视频", "个人兴趣创作", "其他",
];
const WEEKLY_USAGE_OPTIONS = ["7次及以上", "1-3次", "空闲时间使用"];
const FEEDBACK_OPTIONS = ["全力配合深度反馈", "简单体验留言", "仅使用不反馈"];
const WANT_FEATURE_OPTIONS = [
"一站式短剧漫剧完整AIGC工作流", "电商素材自动化创作流程",
"多模态智能中枢全能创作", "批量自动化创作流程", "全新未公开AI创作玩法",
];
/* ── Helper: single-select radio group ── */
function RadioGroup({
name, options, value, onChange,
}: {
name: string;
options: string[];
value: string;
onChange: (v: string) => void;
}) {
return (
<div className="beta-radio-group">
{options.map((opt) => (
<label key={opt} className="beta-radio">
<input
type="radio"
name={name}
checked={value === opt}
onChange={() => onChange(opt)}
/>
<span>{opt}</span>
</label>
))}
</div>
);
}
/* ── Helper: multi-select checkbox group ── */
function CheckboxGroup({
options, value, onChange,
}: {
options: string[];
value: string[];
onChange: (v: string[]) => void;
}) {
return (
<div className="beta-checkbox-group">
{options.map((opt) => (
<label key={opt} className="beta-checkbox">
<input
type="checkbox"
checked={value.includes(opt)}
onChange={() => {
if (value.includes(opt)) {
onChange(value.filter((item) => item !== opt));
} else {
onChange([...value, opt]);
}
}}
/>
<span>{opt}</span>
</label>
))}
</div>
);
}
/* ── Helper: text field ── */
function TextField({
label, value, onChange, placeholder,
}: {
label: string;
value: string;
onChange: (v: string) => void;
placeholder?: string;
}) {
return (
<div className="beta-text-field">
<span className="beta-text-field__label">{label}</span>
<input
type="text"
className="beta-text-field__input"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder ?? "请填写"}
/>
</div>
);
}
const BetaApplicationModal = ({ open, onClose }: BetaApplicationModalProps) => {
const [form, setForm] = useState<BetaFormData>(INITIAL_FORM);
const update = <K extends keyof BetaFormData>(key: K, value: BetaFormData[K]) => {
setForm((prev) => ({ ...prev, [key]: value }));
};
if (!open) return null;
return (
<div className="beta-application-modal" role="dialog" aria-modal="true" aria-labelledby="beta-modal-title">
<button type="button" className="beta-application-modal__backdrop" onClick={onClose} aria-label="关闭内测申请弹窗" />
<section className="beta-application-modal__panel">
{/* ── Header ── */}
<header className="beta-modal-header">
<div className="beta-modal-header__left">
<ExperimentOutlined className="beta-modal-header__icon" />
<div>
<h2 id="beta-modal-title">OmniAI </h2>
<p className="beta-modal-header__subtitle"> · <strong>30 </strong> · <strong>500 </strong> </p>
</div>
</div>
<button type="button" className="beta-modal-header__close" onClick={onClose} aria-label="关闭">
<CloseOutlined />
</button>
</header>
{/* ── Body (scrollable document) ── */}
<div className="beta-modal-body">
{/* 一、个人基础信息 */}
<section className="beta-doc-section">
<h3 className="beta-doc-section__title"></h3>
<div className="beta-doc-grid">
<TextField label="姓名 / 常用昵称" value={form.name} onChange={(v) => update("name", v)} />
<TextField label="联系手机号码" value={form.phone} onChange={(v) => update("phone", v)} />
<TextField label="微信账号" value={form.wechat} onChange={(v) => update("wechat", v)} />
<TextField label="所在行业 / 职业" value={form.industry} onChange={(v) => update("industry", v)} />
<TextField label="所属公司 / 机构" value={form.company} onChange={(v) => update("company", v)} />
<TextField label="所在城市" value={form.city} onChange={(v) => update("city", v)} />
</div>
</section>
{/* 二、AI从业与使用经历 */}
<section className="beta-doc-section">
<h3 className="beta-doc-section__title">AI 使</h3>
<div className="beta-doc-grid">
<TextField label="日常常用 AI 创作工具有哪些" value={form.aiTools} onChange={(v) => update("aiTools", v)} placeholder="例如:Midjourney / Stable Diffusion / ChatGPT 等" />
<div className="beta-form-group">
<span className="beta-form-group__label">AI </span>
<RadioGroup name="aiDuration" options={AI_DURATION_OPTIONS} value={form.aiDuration} onChange={(v) => update("aiDuration", v)} />
</div>
<div className="beta-form-group">
<span className="beta-form-group__label"> AI </span>
<RadioGroup name="aiTrack" options={AI_TRACK_OPTIONS} value={form.aiTrack} onChange={(v) => update("aiTrack", v)} />
</div>
<div className="beta-form-group beta-form-group--full">
<span className="beta-form-group__label"></span>
<CheckboxGroup options={AI_DIRECTION_OPTIONS} value={form.aiDirection} onChange={(v) => update("aiDirection", v)} />
</div>
</div>
</section>
{/* 三、内测使用意向调研 */}
<section className="beta-doc-section">
<h3 className="beta-doc-section__title">使</h3>
<div className="beta-doc-grid">
<div className="beta-form-group">
<span className="beta-form-group__label">使</span>
<RadioGroup name="weeklyUsage" options={WEEKLY_USAGE_OPTIONS} value={form.weeklyUsage} onChange={(v) => update("weeklyUsage", v)} />
</div>
<div className="beta-form-group">
<span className="beta-form-group__label"> BUG</span>
<RadioGroup name="feedback" options={FEEDBACK_OPTIONS} value={form.feedbackWilling} onChange={(v) => update("feedbackWilling", v)} />
</div>
<div className="beta-form-group beta-form-group--full">
<span className="beta-form-group__label"> OmniAI </span>
<CheckboxGroup options={WANT_FEATURE_OPTIONS} value={form.wantFeature} onChange={(v) => update("wantFeature", v)} />
</div>
</div>
</section>
{/* 四、申请自述 */}
<section className="beta-doc-section">
<h3 className="beta-doc-section__title"> <em className="beta-required"></em></h3>
<p className="beta-doc-section__desc"> AI </p>
<textarea
className="beta-textarea"
value={form.selfStatement}
onChange={(e) => update("selfStatement", e.target.value)}
placeholder="请在此填写您的申请自述(必填)…"
rows={6}
/>
</section>
{/* 五、内测规则知情同意书 */}
<section className="beta-doc-section">
<h3 className="beta-doc-section__title"></h3>
<ol className="beta-rules-list">
<li> <strong>30 </strong> + </li>
<li> <strong>500 </strong> 使</li>
<li>线</li>
<li></li>
<li> <strong>48 </strong> </li>
<li>线</li>
</ol>
<label className="beta-agree-row">
<input
type="checkbox"
checked={form.agreeRules}
onChange={(e) => update("agreeRules", e.target.checked)}
/>
<span></span>
</label>
<div className="beta-doc-grid beta-doc-grid--two">
<TextField label="申请人确认签字" value={form.signature} onChange={(v) => update("signature", v)} placeholder="请签署姓名" />
<div className="beta-text-field">
<span className="beta-text-field__label"></span>
<input type="text" className="beta-text-field__input" value="2026年 月 日" readOnly />
</div>
</div>
</section>
</div>
{/* ── Footer ── */}
<footer className="beta-modal-footer">
<button type="button" className="beta-modal-footer__btn beta-modal-footer__btn--secondary" onClick={onClose}>
</button>
<button type="button" className="beta-modal-footer__btn beta-modal-footer__btn--primary" onClick={onClose}>
</button>
</footer>
</section>
</div>
);
};
export default BetaApplicationModal;
+7 -7
View File
@@ -58,13 +58,13 @@ export const defaultTextModelId = textModelOptions[0].id;
// --- Image model options ---
export const imageModelOptions: CanvasOption[] = [
{ value: "wan2.7-image", label: "wan 2.7 · 0.20 积分" },
{ value: "wan2.7-image-pro", label: "wan 2.7 Pro · 0.20 积分" },
{ value: "gpt-image-2", label: "GPT-Image-2 · 0.20 积分" },
{ value: "gpt-image-2-vip", label: "GPT-Image-2 VIP · 0.20 积分" },
{ value: "nano-banana-pro", label: "Nano Banana Pro · 0.20 积分" },
{ value: "nano-banana-2", label: "Nano Banana 2 · 0.20 积分" },
{ value: "nano-banana-fast", label: "Nano Banana · 0.20 积分" },
{ value: "wan2.7-image", label: "wan 2.7" },
{ value: "wan2.7-image-pro", label: "wan 2.7 Pro" },
{ value: "gpt-image-2", label: "omni-GPT" },
{ value: "gpt-image-2-vip", label: "omni-GPT VIP" },
{ value: "nano-banana-pro", label: "omni-水果 Pro" },
{ value: "nano-banana-2", label: "omni-水果 2" },
{ value: "nano-banana-fast", label: "omni-水果" },
];
export const imageRatioOptions: CanvasOption[] = [
+29 -4
View File
@@ -842,6 +842,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const skipInitialCloneAutoSaveRef = useRef(true);
const skipNextCloneAutoSaveRef = useRef(false);
const [activeTool, setActiveTool] = useState<ProductKitToolKey>("clone");
useEffect(() => { setPreviewZoom(1); }, [activeTool]);
const [setImages, setSetImages] = useState<CloneImageItem[]>([]);
const [productSetPlatform, setProductSetPlatform] = useState(platformOptions[0]);
const [productSetMarket, setProductSetMarket] = useState(marketOptions[0]);
@@ -882,6 +883,30 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
const [videoOutfitRefFile, setVideoOutfitRefFile] = useState<File | null>(null);
const [isCloneSettingsCollapsed, setIsCloneSettingsCollapsed] = useState(false);
const [previewZoom, setPreviewZoom] = useState(1);
const handlePreviewWheel = (event: React.WheelEvent<HTMLElement>) => {
if (!event.currentTarget) return;
event.preventDefault();
const container = event.currentTarget as HTMLElement;
const rect = container.getBoundingClientRect();
const cursorX = event.clientX - rect.left;
const cursorY = event.clientY - rect.top;
const zoomDelta = event.deltaY < 0 ? 1.08 : 0.92;
const nextZoom = Math.min(2, Math.max(0.25, previewZoom * zoomDelta));
if (nextZoom === previewZoom) return;
const contentX = (cursorX + container.scrollLeft) / previewZoom;
const contentY = (cursorY + container.scrollTop) / previewZoom;
setPreviewZoom(nextZoom);
requestAnimationFrame(() => {
container.scrollLeft = contentX * nextZoom - cursorX;
container.scrollTop = contentY * nextZoom - cursorY;
});
};
const [requirement, setRequirement] = useState("");
const [requirementImageMentionQuery, setRequirementImageMentionQuery] = useState<string | null>(null);
const [cloneSettingName, setCloneSettingName] = useState("新建创作");
@@ -2332,7 +2357,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
);
const setPreview = (
<main className="product-clone-preview product-clone-preview--set" aria-label="AI商品套图预览">
<main className="product-clone-preview product-clone-preview--set" aria-label="AI商品套图预览" onWheel={handlePreviewWheel}>
<div className="product-clone-preview__headline">
<h1></h1>
<p>
@@ -2400,7 +2425,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
);
const clonePreview = (
<main className="product-clone-preview clone-ai-preview" aria-label="电商AI作图预览">
<main className="product-clone-preview clone-ai-preview" aria-label="电商AI作图预览" onWheel={handlePreviewWheel}>
<header className="clone-ai-preview-header">
<strong></strong>
<span>
@@ -2610,7 +2635,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
);
const detailPreview = (
<main className="product-clone-preview product-clone-preview--detail" aria-label="A+详情预览">
<main className="product-clone-preview product-clone-preview--detail" aria-label="A+详情预览" onWheel={handlePreviewWheel}>
<div className="product-clone-preview__headline">
<h1>A+/</h1>
<p>
@@ -2647,7 +2672,7 @@ function ProductClonePage(_props: ProductClonePageProps = {}) {
);
const tryOnPreview = (
<main className="product-clone-preview product-clone-preview--try-on" aria-label="服饰穿戴预览">
<main className="product-clone-preview product-clone-preview--try-on" aria-label="服饰穿戴预览" onWheel={handlePreviewWheel}>
<div className="product-clone-preview__headline">
<h1>AI服饰穿戴</h1>
<p>姿</p>
+5 -5
View File
@@ -233,11 +233,11 @@ export const MODE_OPTIONS: WorkbenchOption[] = (Object.keys(MODE_META) as Workbe
export const IMAGE_MODEL_OPTIONS: WorkbenchOption[] = [
{ value: "wan2.7-image-pro", label: "wan 2.7 Pro 4K" },
{ value: "wan2.7-image", label: "wan 2.7" },
{ value: "gpt-image-2", label: "GPT-Image-2" },
{ value: "gpt-image-2-vip", label: "GPT-Image-2 VIP" },
{ value: "nano-banana-pro", label: "Nano Banana Pro" },
{ value: "nano-banana-2", label: "Nano Banana 2" },
{ value: "nano-banana-fast", label: "Nano Banana" },
{ value: "gpt-image-2", label: "omni-GPT" },
{ value: "gpt-image-2-vip", label: "omni-GPT VIP" },
{ value: "nano-banana-pro", label: "omni-水果 Pro" },
{ value: "nano-banana-2", label: "omni-水果 2" },
{ value: "nano-banana-fast", label: "omni-水果" },
];
export const VIDEO_MODEL_OPTIONS: WorkbenchOption[] = ENTERPRISE_VIDEO_MODEL_OPTIONS.map((option) => ({ ...option }));
@@ -0,0 +1,449 @@
/* ── Beta Application Modal ── */
/* Word-document style: paper-white panel, larger serif-ish typography, clear form fields */
.beta-application-modal {
position: fixed;
inset: 0;
z-index: 1000;
display: grid;
place-items: center;
}
.beta-application-modal__backdrop {
position: absolute;
inset: 0;
border: 0;
background: rgba(0, 0, 0, 0.58);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
cursor: pointer;
}
/* ── Panel: paper-like document ── */
.beta-application-modal__panel {
position: relative;
z-index: 1;
display: flex;
flex-direction: column;
width: min(800px, 94vw);
max-height: 90vh;
overflow: hidden;
border: 1px solid #d9d5cf;
border-radius: 4px;
background: #faf8f4;
color: #1e1e1e;
box-shadow:
0 2px 0 #e8e4dc,
0 4px 0 #d9d5cf,
0 8px 32px rgba(0, 0, 0, 0.2),
0 24px 80px rgba(0, 0, 0, 0.12);
font-family: "Microsoft YaHei", "微软雅黑", "PingFang SC", sans-serif;
font-size: 14px;
line-height: 1.8;
}
/* ── Header ── */
.beta-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 28px 36px 20px;
border-bottom: 2px solid #1e1e1e;
flex-shrink: 0;
background: #f5f1ea;
}
.beta-modal-header__left {
display: flex;
align-items: center;
gap: 14px;
}
.beta-modal-header__icon {
font-size: 28px;
color: #166534;
flex-shrink: 0;
}
.beta-modal-header h2 {
margin: 0;
font-size: 22px;
font-weight: 900;
color: #1e1e1e;
letter-spacing: 1px;
line-height: 1.3;
}
.beta-modal-header__subtitle {
margin: 2px 0 0;
font-size: 13px;
color: #6b7280;
font-weight: 500;
}
.beta-modal-header__subtitle strong {
color: #166534;
font-weight: 800;
}
.beta-modal-header__close {
display: grid;
width: 34px;
height: 34px;
place-items: center;
border: 1px solid #d5cfc4;
border-radius: 4px;
background: #f5f1ea;
color: #8c8276;
cursor: pointer;
font-size: 14px;
flex-shrink: 0;
transition: background 120ms ease, color 120ms ease;
}
.beta-modal-header__close:hover {
background: #ede6da;
color: #1e1e1e;
}
/* ── Scrollable body ── */
.beta-modal-body {
flex: 1;
overflow-y: auto;
padding: 32px 40px;
background: #faf8f4;
}
/* ── Document sections ── */
.beta-doc-section {
margin-bottom: 28px;
padding-bottom: 28px;
border-bottom: 1px dashed #d9d2c5;
}
.beta-doc-section:last-of-type {
border-bottom: 0;
margin-bottom: 0;
padding-bottom: 0;
}
.beta-doc-section__title {
margin: 0 0 18px;
font-size: 16px;
font-weight: 900;
color: #1e1e1e;
letter-spacing: 0.5px;
}
.beta-required {
color: #dc2626;
font-style: normal;
font-weight: 800;
}
.beta-doc-section__desc {
margin: 0 0 12px;
font-size: 14px;
color: #4b5563;
line-height: 1.7;
}
/* ── Single-column grid for form fields ── */
.beta-doc-grid {
display: flex;
flex-direction: column;
gap: 16px;
}
.beta-doc-grid--two {
margin-top: 14px;
}
/* ── Text field (Word underline style) ── */
.beta-text-field {
display: flex;
align-items: baseline;
gap: 6px;
}
.beta-text-field__label {
font-size: 14px;
font-weight: 700;
color: #1e1e1e;
white-space: nowrap;
flex-shrink: 0;
}
.beta-text-field__label::after {
content: "";
}
.beta-text-field__input {
flex: 1;
min-width: 0;
border: 0;
border-bottom: 1px solid #c5beb2;
outline: none;
background: transparent;
padding: 2px 0;
font-size: 14px;
font-family: inherit;
color: #1e1e1e;
line-height: 1.8;
transition: border-color 140ms ease;
}
.beta-text-field__input::placeholder {
color: #c4c4c4;
font-size: 13px;
}
.beta-text-field__input:focus {
border-bottom-color: #166534;
border-bottom-width: 2px;
margin-bottom: -1px;
}
.beta-text-field__input[readonly] {
color: #6b7280;
cursor: default;
}
/* ── Form group (spans full row when needed) ── */
.beta-form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.beta-form-group--full {
grid-column: 1 / -1;
}
.beta-form-group__label {
font-size: 14px;
font-weight: 700;
color: #1e1e1e;
}
.beta-form-group__label::after {
content: "";
}
/* ── Radio group ── */
.beta-radio-group {
display: flex;
flex-wrap: wrap;
gap: 6px 14px;
}
.beta-radio {
display: inline-flex;
align-items: center;
gap: 5px;
cursor: pointer;
font-size: 14px;
color: #374151;
padding: 3px 0;
user-select: none;
}
.beta-radio input[type="radio"] {
appearance: none;
width: 16px;
height: 16px;
border: 2px solid #b8b0a4;
border-radius: 50%;
margin: 0;
cursor: pointer;
flex-shrink: 0;
transition: border-color 120ms ease, background 120ms ease;
}
.beta-radio input[type="radio"]:checked {
border-color: #166534;
background: #166534;
box-shadow: inset 0 0 0 3px #ffffff;
}
.beta-radio:hover input[type="radio"] {
border-color: #166534;
}
.beta-radio span {
line-height: 1.5;
}
/* ── Checkbox group ── */
.beta-checkbox-group {
display: flex;
flex-wrap: wrap;
gap: 6px 14px;
}
.beta-checkbox {
display: inline-flex;
align-items: center;
gap: 5px;
cursor: pointer;
font-size: 14px;
color: #374151;
padding: 3px 0;
user-select: none;
}
.beta-checkbox input[type="checkbox"] {
appearance: none;
width: 16px;
height: 16px;
border: 2px solid #b8b0a4;
border-radius: 3px;
margin: 0;
cursor: pointer;
flex-shrink: 0;
transition: border-color 120ms ease, background 120ms ease;
}
.beta-checkbox input[type="checkbox"]:checked {
border-color: #166534;
background: #166534;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
background-size: 10px;
background-position: center;
background-repeat: no-repeat;
}
.beta-checkbox:hover input[type="checkbox"] {
border-color: #166534;
}
.beta-checkbox span {
line-height: 1.5;
}
/* ── Textarea ── */
.beta-textarea {
width: 100%;
min-height: 140px;
resize: vertical;
border: 1px solid #c5beb2;
border-radius: 4px;
outline: none;
background: #ffffff;
padding: 12px 14px;
font-size: 14px;
font-family: inherit;
color: #1e1e1e;
line-height: 1.8;
transition: border-color 140ms ease;
box-sizing: border-box;
}
.beta-textarea::placeholder {
color: #c4c4c4;
}
.beta-textarea:focus {
border-color: #166534;
box-shadow: 0 0 0 2px rgba(22, 101, 52, 0.1);
}
/* ── Rules list ── */
.beta-rules-list {
margin: 0 0 18px;
padding-left: 22px;
}
.beta-rules-list li {
font-size: 14px;
color: #374151;
line-height: 1.9;
margin-bottom: 4px;
}
.beta-rules-list li strong {
color: #166534;
}
/* ── Agreement checkbox row ── */
.beta-agree-row {
display: flex;
align-items: flex-start;
gap: 8px;
margin-bottom: 18px;
cursor: pointer;
font-size: 14px;
font-weight: 700;
color: #166534;
user-select: none;
}
.beta-agree-row input[type="checkbox"] {
appearance: none;
width: 18px;
height: 18px;
border: 2px solid #b8b0a4;
border-radius: 3px;
margin-top: 2px;
cursor: pointer;
flex-shrink: 0;
transition: border-color 120ms ease, background 120ms ease;
}
.beta-agree-row input[type="checkbox"]:checked {
border-color: #166534;
background: #166534;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'%3E%3C/polyline%3E%3C/svg%3E");
background-size: 12px;
background-position: center;
background-repeat: no-repeat;
}
/* ── Footer ── */
.beta-modal-footer {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
padding: 16px 36px 20px;
border-top: 1px solid #e0dbd2;
flex-shrink: 0;
background: #f5f1ea;
}
.beta-modal-footer__btn {
display: inline-flex;
align-items: center;
justify-content: center;
height: 40px;
padding: 0 24px;
border: 0;
border-radius: 4px;
font-size: 14px;
font-weight: 700;
cursor: pointer;
transition: opacity 120ms ease, transform 120ms ease;
}
.beta-modal-footer__btn:active {
transform: scale(0.97);
}
.beta-modal-footer__btn--secondary {
background: #e8e3d9;
color: #5c5348;
}
.beta-modal-footer__btn--secondary:hover {
background: #dbd4c7;
}
.beta-modal-footer__btn--primary {
background: #166534;
color: #ffffff;
}
.beta-modal-footer__btn--primary:hover {
background: #14532d;
}
+4
View File
@@ -3,6 +3,10 @@
@import "./shell/app-shell.css";
@import "./components/primitives.css";
@import "./components/legacy-components.css";
@import "./components/recharge-modal.css";
@import "./components/beta-application-modal.css";
@import "./components/dropzone.css";
@import "./components/skeleton.css";
@import "./components/toast.css";
@import "./components/page-transition.css";
@import "./components/motion.css";
+47 -47
View File
@@ -480,7 +480,7 @@
.omni-home__feature-page.is-script,
.omni-home__feature-page.is-model,
.omni-home__feature-page.is-ecommerce {
--home-showcase-page-pad-y: clamp(10px, 1.8vw, 24px);
--home-showcase-page-pad-y: clamp(4px, 0.8vw, 12px);
display: grid;
grid-template-columns: minmax(0, 1fr);
gap: 0;
@@ -965,11 +965,12 @@
position: relative;
z-index: 2;
display: flex;
align-items: center;
width: 100%;
height: 100%;
min-height: inherit;
gap: clamp(30px, 2.6cqw, 56px);
padding: clamp(24px, 2.2cqw, 46px) clamp(36px, 3.7cqw, 72px);
padding: clamp(12px, 1.2cqw, 24px) clamp(24px, 2.4cqw, 48px);
}
.omni-home-ecommerce-matrix .left-panel,
@@ -981,10 +982,10 @@
.omni-home-ecommerce-matrix .left-panel {
display: flex;
flex: 0 0 clamp(320px, 24cqw, 450px);
flex: 0 0 clamp(360px, 29cqw, 540px);
flex-direction: column;
justify-content: center;
gap: clamp(16px, 1.1cqw, 26px);
gap: clamp(22px, 1.6cqw, 34px);
min-width: 0;
}
@@ -997,7 +998,7 @@
.omni-home-ecommerce-matrix .hero-title {
color: var(--matrix-text-primary);
font-size: clamp(40px, 3cqw, 58px);
font-size: clamp(48px, 3.6cqw, 72px);
font-weight: 900;
letter-spacing: 0;
line-height: 1.08;
@@ -1009,7 +1010,7 @@
.omni-home-ecommerce-matrix .hero-desc {
color: var(--matrix-text-secondary);
font-size: clamp(16px, 1.2cqw, 22px);
font-size: clamp(19px, 1.4cqw, 26px);
font-weight: 500;
letter-spacing: 0.2px;
line-height: 1.55;
@@ -1018,20 +1019,20 @@
.omni-home-ecommerce-matrix .features {
display: flex;
flex-direction: column;
gap: clamp(12px, 0.95cqw, 18px);
gap: clamp(16px, 1.2cqw, 24px);
}
.omni-home-ecommerce-matrix .feature-item {
position: relative;
display: flex;
align-items: flex-start;
gap: 12px;
gap: 14px;
overflow: hidden;
border: 1px solid var(--matrix-border-subtle);
border-radius: var(--matrix-radius);
background: var(--matrix-card-surface);
min-height: clamp(70px, 4.6cqw, 92px);
padding: clamp(16px, 1.2cqw, 24px);
min-height: clamp(88px, 5.8cqw, 114px);
padding: clamp(20px, 1.5cqw, 30px);
box-shadow: var(--matrix-shadow-card);
backdrop-filter: var(--matrix-glass-blur);
-webkit-backdrop-filter: var(--matrix-glass-blur);
@@ -1064,15 +1065,15 @@
position: relative;
z-index: 1;
display: flex;
flex: 0 0 clamp(44px, 2.8cqw, 56px);
width: clamp(44px, 2.8cqw, 56px);
height: clamp(44px, 2.8cqw, 56px);
flex: 0 0 clamp(50px, 3.4cqw, 64px);
width: clamp(50px, 3.4cqw, 64px);
height: clamp(50px, 3.4cqw, 64px);
align-items: center;
justify-content: center;
border: 1px solid rgba(0, 255, 136, 0.2);
border-radius: var(--matrix-radius);
background: linear-gradient(135deg, rgba(0, 255, 136, 0.15), rgba(168, 85, 247, 0.15));
font-size: clamp(20px, 1.4cqw, 28px);
font-size: clamp(24px, 1.6cqw, 32px);
}
.omni-home-ecommerce-matrix .feature-text {
@@ -1082,15 +1083,15 @@
.omni-home-ecommerce-matrix .feature-text h4 {
color: var(--matrix-text-primary);
font-size: clamp(18px, 1.2cqw, 24px);
font-size: clamp(20px, 1.4cqw, 26px);
font-weight: 750;
line-height: 1.25;
}
.omni-home-ecommerce-matrix .feature-text p {
margin-top: 6px;
margin-top: 8px;
color: var(--matrix-text-dim);
font-size: clamp(13px, 0.9cqw, 17px);
font-size: clamp(15px, 1cqw, 19px);
font-weight: 500;
letter-spacing: 0.1px;
line-height: 1.5;
@@ -1100,12 +1101,11 @@
display: flex;
align-items: center;
gap: 0;
margin-top: auto;
border: 1px solid var(--matrix-border-subtle);
border-radius: var(--matrix-radius);
background: var(--matrix-card-surface);
min-height: clamp(72px, 4.6cqw, 92px);
padding: clamp(14px, 1.1cqw, 22px) clamp(16px, 1.4cqw, 26px);
min-height: clamp(88px, 5.8cqw, 114px);
padding: clamp(18px, 1.4cqw, 28px) clamp(20px, 1.7cqw, 34px);
box-shadow: var(--matrix-shadow-card);
backdrop-filter: var(--matrix-glass-blur);
-webkit-backdrop-filter: var(--matrix-glass-blur);
@@ -1117,18 +1117,18 @@
flex: 1;
flex-direction: column;
align-items: center;
gap: 6px;
gap: 8px;
min-width: 0;
}
.omni-home-ecommerce-matrix .step-icon {
font-size: clamp(18px, 1.2cqw, 24px);
font-size: clamp(22px, 1.4cqw, 28px);
line-height: 1;
}
.omni-home-ecommerce-matrix .step-label {
color: var(--matrix-text-secondary);
font-size: clamp(12px, 0.82cqw, 16px);
font-size: clamp(14px, 0.95cqw, 18px);
font-weight: 650;
line-height: 1;
white-space: nowrap;
@@ -1136,7 +1136,7 @@
.omni-home-ecommerce-matrix .step-sub {
color: var(--matrix-text-dim);
font-size: clamp(10px, 0.7cqw, 13px);
font-size: clamp(11px, 0.78cqw, 14px);
letter-spacing: 0.5px;
line-height: 1;
white-space: nowrap;
@@ -1171,22 +1171,22 @@
.omni-home-ecommerce-matrix .center-panel {
display: flex;
flex: 0 0 clamp(310px, 22cqw, 430px);
flex: 0 0 clamp(350px, 26cqw, 500px);
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
gap: 20px;
min-width: 0;
}
.omni-home-ecommerce-matrix .input-card {
position: relative;
z-index: 3;
width: min(100%, clamp(310px, 21cqw, 420px));
width: min(100%, clamp(350px, 24cqw, 480px));
border: 1px solid var(--matrix-border-default);
border-radius: var(--matrix-radius);
background: var(--matrix-card-elevated);
padding: clamp(20px, 1.45cqw, 30px);
padding: clamp(24px, 1.7cqw, 36px);
box-shadow: var(--matrix-shadow-elevated), 0 0 60px rgba(0, 255, 136, 0.08);
backdrop-filter: var(--matrix-glass-blur);
-webkit-backdrop-filter: var(--matrix-glass-blur);
@@ -1218,12 +1218,12 @@
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: clamp(16px, 1.1cqw, 24px);
margin-bottom: clamp(20px, 1.4cqw, 30px);
}
.omni-home-ecommerce-matrix .input-card-label {
color: var(--matrix-cyan);
font-size: clamp(14px, 1cqw, 18px);
font-size: clamp(16px, 1.15cqw, 22px);
font-weight: 800;
letter-spacing: 1.2px;
text-transform: uppercase;
@@ -1235,7 +1235,7 @@
border-radius: 6px;
background: rgba(255, 255, 255, 0.04);
color: var(--matrix-text-dim);
font-size: clamp(12px, 0.9cqw, 16px);
font-size: clamp(13px, 0.95cqw, 17px);
letter-spacing: 0.5px;
padding: 6px 12px;
white-space: nowrap;
@@ -1408,9 +1408,9 @@
flex: 1;
flex-direction: column;
justify-content: center;
gap: clamp(16px, 1.35cqw, 28px);
gap: clamp(22px, 1.6cqw, 34px);
min-width: 0;
padding-left: clamp(34px, 3.4cqw, 78px);
padding-left: clamp(8px, 0.8cqw, 24px);
}
.omni-home-ecommerce-matrix .ai-node {
@@ -1418,13 +1418,13 @@
z-index: 4;
display: flex;
align-items: center;
gap: clamp(12px, 1cqw, 18px);
gap: clamp(14px, 1.15cqw, 22px);
width: 100%;
border: 1px solid var(--matrix-border-default);
border-radius: var(--matrix-radius);
background: var(--matrix-card-highlight);
min-height: clamp(54px, 3.8cqw, 72px);
padding: 12px clamp(16px, 1.4cqw, 26px);
min-height: clamp(62px, 4.4cqw, 82px);
padding: 14px clamp(18px, 1.6cqw, 30px);
box-shadow: var(--matrix-shadow-card), var(--matrix-shadow-glow-purple);
backdrop-filter: var(--matrix-glass-blur);
-webkit-backdrop-filter: var(--matrix-glass-blur);
@@ -1443,7 +1443,7 @@
.omni-home-ecommerce-matrix .ai-node-title {
flex-shrink: 0;
color: var(--matrix-cyan);
font-size: clamp(12px, 0.82cqw, 16px);
font-size: clamp(14px, 0.95cqw, 18px);
font-weight: 850;
letter-spacing: 1.6px;
text-transform: uppercase;
@@ -1457,7 +1457,7 @@
.omni-home-ecommerce-matrix .ai-node-list {
display: flex;
flex: 1;
gap: clamp(8px, 0.75cqw, 14px);
gap: clamp(10px, 0.85cqw, 16px);
min-width: 0;
}
@@ -1467,10 +1467,10 @@
border-radius: 4px;
background: rgba(255, 255, 255, 0.03);
color: var(--matrix-text-secondary);
font-size: clamp(11px, 0.78cqw, 15px);
font-size: clamp(12px, 0.85cqw, 16px);
letter-spacing: 0.2px;
line-height: 1.3;
padding: 7px clamp(10px, 0.9cqw, 16px);
padding: 8px clamp(12px, 1cqw, 18px);
text-overflow: ellipsis;
white-space: nowrap;
}
@@ -1478,28 +1478,28 @@
.omni-home-ecommerce-matrix .output-group {
display: flex;
align-items: center;
gap: clamp(14px, 1.1cqw, 22px);
gap: clamp(16px, 1.3cqw, 26px);
min-width: 0;
}
.omni-home-ecommerce-matrix .output-label {
flex: 0 0 clamp(96px, 6.5cqw, 132px);
flex: 0 0 clamp(72px, 5cqw, 100px);
padding-right: 4px;
text-align: right;
}
.omni-home-ecommerce-matrix .output-label h4 {
color: var(--matrix-text-primary);
font-size: clamp(18px, 1.3cqw, 25px);
font-size: clamp(20px, 1.45cqw, 28px);
font-weight: 850;
letter-spacing: 0.3px;
line-height: 1.2;
}
.omni-home-ecommerce-matrix .output-label p {
margin-top: 6px;
margin-top: 8px;
color: var(--matrix-text-dim);
font-size: clamp(11px, 0.82cqw, 15px);
font-size: clamp(12px, 0.9cqw, 16px);
letter-spacing: 0.8px;
line-height: 1.2;
text-transform: uppercase;
@@ -1516,14 +1516,14 @@
.omni-home-ecommerce-matrix .output-cards {
display: flex;
flex: 1;
gap: clamp(14px, 1.15cqw, 24px);
gap: clamp(10px, 0.85cqw, 18px);
min-width: 0;
}
.omni-home-ecommerce-matrix .output-card {
position: relative;
flex: 1;
min-width: 0;
min-width: clamp(80px, 7cqw, 140px);
overflow: hidden;
border: 1px solid var(--matrix-border-subtle);
border-radius: var(--matrix-radius);
+37
View File
@@ -248,6 +248,43 @@
color: var(--accent);
}
.beta-apply-button {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
min-width: 0;
height: 36px;
padding: 0 14px;
border: 1px solid rgba(var(--accent-rgb), 0.3);
border-radius: 12px;
background: rgba(var(--accent-rgb), 0.1);
color: var(--accent);
font-size: 12px;
font-weight: 850;
cursor: pointer;
transition:
transform 160ms ease,
border-color 160ms ease,
background 160ms ease;
animation: beta-pulse 2.5s ease-in-out infinite;
}
.beta-apply-button:hover {
border-color: var(--accent);
background: rgba(var(--accent-rgb), 0.18);
}
.beta-apply-button:active {
transform: scale(0.96);
}
@keyframes beta-pulse {
0%, 100% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0.25); }
50% { box-shadow: 0 0 0 6px rgba(var(--accent-rgb), 0); }
}
.member-button {
color: var(--accent);
font-weight: 600;