Compare commits

...
Author SHA1 Message Date
总工andmultica-agent 225fcb49ea fix(ui): edited-draft marker uses amber warn per HEL-658 ruling (HEL-657)
Co-authored-by: multica-agent <github@multica.ai>
2026-09-30 08:49:53 +08:00
施工员andmultica-agent 70a860ce2a fix(ui): split running/awaiting-confirm colors and neutral kind tags (HEL-651 A batch)
- project card stage segments: running -> --run, awaiting confirm -> --warn
- running counters/dots and 'testing' states -> --run; warn-named notes -> --warn
- image/video/usage kind tags and plain entry/card icons -> neutral tokens

Co-authored-by: multica-agent <github@multica.ai>
2026-09-30 08:38:11 +08:00
施工员andmultica-agent e42212a16f feat(ui): fusion design tokens and shared component reskin (HEL-651 A batch)
- app.css: HEL-649 §一 tokens (bg/line/accent/status/text/type/space/radius/shadow/motion), legacy names aliased, reduced-motion via prefers-reduced-motion and data-reduce
- shared .a-btn/.status-chip/.a-panel/inputs reskinned; new .a-tag/.a-progress/.a-sprocket/.a-stage/.a-float
- tailwind colors/radii reference the same CSS variables
- ui primitives (StatusChip/ProgressBar/Modal/Button/ConfirmDialog/Skeleton/Switch/Card/Toast) on tokens
- running state -> --run, awaiting confirm -> --warn, --cloud purple removed, old palette literals -> tokens

Co-authored-by: multica-agent <github@multica.ai>
2026-09-30 08:29:37 +08:00
29 changed files with 701 additions and 465 deletions
@@ -52,15 +52,16 @@
{/if}
<style>
/* §四.5 Toast:右下角堆叠,220ms 上移入场;成功绿点 / 失败红点。 */
.toast-host {
position: fixed;
top: 72px;
right: 20px;
right: 24px;
bottom: 24px;
z-index: 1000;
display: flex;
flex-direction: column;
gap: 8px;
max-width: min(360px, calc(100vw - 32px));
max-width: min(400px, calc(100vw - 32px));
pointer-events: none;
}
.toast {
@@ -69,23 +70,10 @@
align-items: flex-start;
gap: 10px;
padding: 12px 14px;
border-radius: var(--radius-md);
border: 1px solid var(--border);
background: var(--bg-surface);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
animation: toast-in 0.22s ease-out;
}
.toast.success {
border-color: rgba(34, 197, 94, 0.45);
box-shadow:
0 12px 32px rgba(0, 0, 0, 0.45),
0 0 0 1px rgba(34, 197, 94, 0.12);
}
.toast.error {
border-color: rgba(239, 68, 68, 0.5);
}
.toast.info {
border-color: rgba(59, 130, 246, 0.45);
border-radius: var(--r-lg);
background: var(--float-bg);
box-shadow: var(--shadow-float);
animation: toast-in var(--d-enter) var(--ease-out);
}
.badge {
display: inline-flex;
@@ -101,17 +89,17 @@
background: currentColor;
}
.toast.success .badge {
color: var(--success);
color: var(--ok);
}
.toast.error .badge {
color: var(--error);
color: var(--err);
}
.toast.info .badge {
color: var(--info);
color: var(--text-2);
}
.msg {
flex: 1;
font-size: 13px;
font-size: var(--fs-body);
line-height: 1.4;
color: var(--text-primary);
font-weight: 500;
@@ -127,10 +115,12 @@
padding: 0 2px;
min-width: 28px;
min-height: 28px;
border-radius: 4px;
border-radius: var(--r-sm);
}
.dismiss:hover {
color: var(--text-primary);
@media (hover: hover) and (pointer: fine) {
.dismiss:hover {
color: var(--text-primary);
}
}
.dismiss:focus-visible {
outline: 2px solid var(--accent);
@@ -139,7 +129,7 @@
@keyframes toast-in {
from {
opacity: 0;
transform: translateY(-8px);
transform: translateY(8px);
}
to {
opacity: 1;
@@ -151,9 +141,11 @@
animation: none;
}
}
@media (max-width: 640px) {
:global(:root[data-reduce='1']) .toast {
animation: none;
}
@media (max-width: 760px) {
.toast-host {
top: auto;
bottom: 20px;
right: 16px;
left: 16px;
@@ -39,7 +39,8 @@
function segClass(key: string): string {
const state = stats.stage_status?.[key];
if (state === 'confirmed') return 'seg-confirmed';
if (state === 'running' || state === 'awaiting_confirm') return 'seg-draft';
if (state === 'running') return 'seg-running';
if (state === 'awaiting_confirm') return 'seg-draft';
return '';
}
@@ -70,7 +71,7 @@
return { cls: 'st-progress', label: `${stageName} · ${t('workbench.state.running')}` };
}
if (state === 'awaiting_confirm') {
return { cls: 'st-progress', label: `${stageName} · ${t('workbench.state.draft')}` };
return { cls: 'st-pending', label: `${stageName} · ${t('workbench.state.draft')}` };
}
if (stats.stage_confirmed > 0) {
return { cls: 'st-progress', label: `${stageName} · ${t('home.statusProgress')}` };
@@ -329,7 +330,7 @@
display: inline-flex;
align-items: center;
justify-content: center;
color: rgba(22, 19, 10, 0.82);
color: color-mix(in srgb, var(--accent-ink) 82%, transparent);
opacity: 0.9;
}
.status-tag {
@@ -340,7 +341,7 @@
border-radius: 999px;
font-size: 10.5px;
font-weight: 600;
background: rgba(13, 14, 17, 0.78);
background: var(--media-scrim);
border: 1px solid var(--border-strong);
backdrop-filter: blur(4px);
}
@@ -350,7 +351,10 @@
color: var(--text-2);
}
.st-progress {
color: var(--accent);
color: var(--run);
}
.st-pending {
color: var(--warn);
}
.st-done {
color: var(--success);
@@ -409,8 +413,11 @@
border-radius: 2px;
background: var(--surface-3);
}
.seg-running {
background: var(--run);
}
.seg-draft {
background: var(--accent);
background: var(--warn);
}
.seg-confirmed {
background: var(--success);
@@ -425,8 +432,8 @@
margin-left: auto;
padding: 1px 8px;
border-radius: 999px;
background: var(--accent-dim);
color: var(--accent);
background: var(--run-soft);
color: var(--run);
font-size: 10px;
font-weight: 600;
font-variant-numeric: tabular-nums;
@@ -462,7 +469,7 @@
height: 28px;
border: 1px solid transparent;
border-radius: 7px;
background: rgba(13, 14, 17, 0.6);
background: var(--media-scrim);
color: var(--text-2);
font-size: 15px;
line-height: 1;
@@ -341,14 +341,14 @@
white-space: nowrap;
}
/* 进行数徽章 (spec §3): accent-dim capsule with amber count */
/* 进行数徽章:进行中计数用信号青 */
.nav-badge {
margin-left: auto;
min-width: 20px;
padding: 1px 7px;
border-radius: 999px;
background: var(--accent-dim);
color: var(--accent);
background: var(--run-soft);
color: var(--run);
font-size: 10.5px;
font-weight: 600;
font-variant-numeric: tabular-nums;
@@ -384,7 +384,7 @@
color: var(--text);
}
/* §5 连接状态点: green = probe passed, amber breathing = testing,
/* 连接状态点: cyan = online, cyan breathing = testing,
red = failed, gray = no connection configured. */
.dot {
width: 7px;
@@ -396,13 +396,13 @@
margin-top: 5px;
}
.dot.ok {
background: var(--success);
background: var(--run);
}
.dot.fail {
background: var(--error);
}
.dot.testing {
background: var(--accent);
background: var(--run);
animation: shell-breathe 2s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}
@keyframes shell-breathe {
@@ -258,7 +258,7 @@
color: var(--accent);
}
.slot-empty.invalid {
border-color: rgba(229, 72, 77, 0.65);
border-color: var(--err-line);
}
.slot-empty:disabled {
cursor: default;
@@ -1897,7 +1897,7 @@
color: var(--text-3);
}
.h3-hint p.warn {
color: var(--accent);
color: var(--warn);
}
.studio-page {
display: grid;
@@ -1990,10 +1990,10 @@
}
.f-input:focus {
outline: none;
border-color: rgba(232, 163, 61, 0.6);
border-color: var(--accent-line);
}
.f-input.invalid {
border-color: rgba(229, 72, 77, 0.65);
border-color: var(--err-line);
}
.f-error {
display: block;
@@ -2139,13 +2139,10 @@
font-weight: 600;
flex-shrink: 0;
}
.tag.info {
color: var(--info);
background: var(--info-dim);
}
.tag.info,
.tag.cloud {
color: var(--cloud);
background: var(--cloud-dim);
color: var(--text-2);
background: var(--bg-4);
}
.task-body {
margin-top: 12px;
@@ -2168,9 +2165,9 @@
.progress .fill {
height: 100%;
border-radius: 999px;
background: var(--accent);
box-shadow: 0 0 10px var(--accent-glow);
transition: width 300ms cubic-bezier(0.16, 1, 0.3, 1);
background: var(--run-grad);
box-shadow: 0 0 8px var(--run-soft);
transition: width var(--d-progress) linear;
}
.task-meta {
font-size: 11px;
@@ -2238,7 +2235,7 @@
display: block;
}
.thumb:hover {
border-color: rgba(255, 255, 255, 0.24);
border-color: var(--line-2);
}
.thumb.selected {
border: 2px solid var(--accent);
@@ -2571,7 +2568,7 @@
color: var(--success);
}
.st.run {
color: var(--accent);
color: var(--run);
}
.st.queue {
color: var(--text-3);
@@ -3,8 +3,9 @@
import Spinner from './Spinner.svelte';
interface Props {
variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
size?: 'sm' | 'md';
/** `danger` = outlined (list rows); `danger-solid` = confirm dialogs only. */
variant?: 'primary' | 'secondary' | 'ghost' | 'danger' | 'danger-solid';
size?: 'sm' | 'md' | 'lg';
/** Stretch to the container width (e.g. a panel's main action). */
block?: boolean;
disabled?: boolean;
@@ -32,6 +33,7 @@
{type}
class="a-btn btn {variant}"
class:sm={size === 'sm'}
class:lg={size === 'lg'}
class:block
class:loading
disabled={disabled || loading}
@@ -49,7 +51,7 @@
</button>
<style>
/* Look comes from the shared `.a-btn` rules in app.css (§5). */
/* Look comes from the shared `.a-btn` rules in app.css (§四.1). */
.btn {
position: relative;
}
@@ -72,10 +74,14 @@
justify-content: center;
}
.btn.primary .btn-spinner {
--spinner-track: rgba(22, 19, 10, 0.3);
--spinner-arc: var(--accent-contrast);
--spinner-track: color-mix(in srgb, var(--accent-ink) 30%, transparent);
--spinner-arc: var(--accent-ink);
}
.btn.danger .btn-spinner {
--spinner-arc: var(--error);
--spinner-arc: var(--err);
}
.btn.danger-solid .btn-spinner {
--spinner-track: color-mix(in srgb, var(--on-media) 30%, transparent);
--spinner-arc: var(--on-media);
}
</style>
@@ -46,16 +46,18 @@
.card {
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
border-radius: var(--r-lg);
}
.card.hoverable {
transition:
border-color 150ms ease,
transform 150ms ease;
border-color var(--d-state) ease,
transform var(--d-state) var(--ease-out);
}
.card.hoverable:hover {
border-color: #3f3f46;
transform: translateY(-1px);
@media (hover: hover) and (pointer: fine) {
.card.hoverable:hover {
border-color: var(--line-2);
transform: translateY(-1px);
}
}
.card-header {
display: flex;
@@ -47,7 +47,7 @@
{#snippet footer()}
<Button variant="ghost" onclick={() => (open = false)}>{cancelLabel}</Button
>
<Button variant={danger ? 'danger' : 'primary'} onclick={handleConfirm}
<Button variant={danger ? 'danger-solid' : 'primary'} onclick={handleConfirm}
>{confirmLabel}</Button
>
{/snippet}
+14 -15
View File
@@ -144,22 +144,19 @@
align-items: center;
justify-content: center;
padding: 24px;
background: rgba(5, 5, 8, 0.6);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
animation: modal-fade 150ms ease-out;
background: var(--overlay);
animation: modal-fade var(--d-state) ease-out;
}
.modal-panel {
width: min(520px, 100%);
max-height: calc(100vh - 96px);
display: flex;
flex-direction: column;
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5);
background: var(--float-bg);
border-radius: var(--r-lg);
box-shadow: var(--shadow-float);
outline: none;
animation: modal-rise 180ms ease-out;
animation: modal-rise var(--d-pop) var(--ease-out);
}
.modal-panel.size-lg {
width: min(920px, 100%);
@@ -177,7 +174,7 @@
.modal-title {
margin: 0;
font-family: var(--font-display);
font-size: 16px;
font-size: var(--fs-title);
font-weight: 600;
color: var(--text-primary);
}
@@ -194,12 +191,14 @@
color: var(--text-muted);
cursor: pointer;
transition:
background-color 150ms ease,
color 150ms ease;
background-color var(--d-state) ease,
color var(--d-state) ease;
}
.modal-close:hover {
background: rgba(255, 255, 255, 0.06);
color: var(--text-primary);
@media (hover: hover) and (pointer: fine) {
.modal-close:hover {
background: var(--bg-4);
color: var(--text-primary);
}
}
.modal-close:focus-visible {
outline: 2px solid var(--accent);
@@ -4,8 +4,8 @@
indeterminate?: boolean;
label?: string;
size?: 'sm' | 'md';
/** §5 进度条: running = amber, done = green, failed = red (static). */
tone?: 'accent' | 'success' | 'error';
/** 进度条: running = 信号青, done = green, failed = red (static). */
tone?: 'run' | 'success' | 'error';
/** Track only — no label / percentage row. */
bare?: boolean;
}
@@ -15,7 +15,7 @@
indeterminate = false,
label,
size = 'md',
tone = 'accent',
tone = 'run',
bare = false,
}: Props = $props();
@@ -45,7 +45,7 @@
{#if indeterminate}
<div class="fill indeterminate"></div>
{:else}
<div class="fill" style:width={pct + '%'}></div>
<div class="fill" style:transform="scaleX({pct / 100})"></div>
{/if}
</div>
</div>
@@ -75,8 +75,8 @@
position: relative;
width: 100%;
overflow: hidden;
background: var(--surface-3);
border-radius: 999px;
background: var(--bg-4);
border-radius: var(--r-pill);
}
.progress-sm .track {
height: 4px;
@@ -86,15 +86,18 @@
}
.fill {
height: 100%;
background: var(--accent);
border-radius: 999px;
transition: width 300ms cubic-bezier(0.16, 1, 0.3, 1);
background: var(--run-grad);
box-shadow: 0 0 8px var(--run-soft);
transform-origin: left;
transition: transform var(--d-progress) linear;
}
.tone-success .fill {
background: var(--success);
background: var(--ok);
box-shadow: none;
}
.tone-error .fill {
background: var(--error);
background: var(--err);
box-shadow: none;
}
.fill.indeterminate {
position: absolute;
@@ -104,10 +107,11 @@
background: linear-gradient(
90deg,
transparent,
var(--accent) 40%,
var(--accent) 60%,
var(--run) 40%,
var(--run) 60%,
transparent
);
box-shadow: none;
transition: none;
animation: progress-slide 1.2s ease-in-out infinite;
}
@@ -19,36 +19,26 @@
></div>
<style>
/* §四.9 骨架屏:--bg-3 圆角块,1.4s 呼吸透明度;减少动态时静态。 */
.skeleton {
position: relative;
overflow: hidden;
background: var(--bg-elevated);
border-radius: var(--radius-sm);
background: var(--bg-3);
border-radius: var(--r-sm);
animation: skeleton-breath 1.4s ease-in-out infinite;
}
.skeleton.circle {
border-radius: 50%;
}
.skeleton::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.06),
transparent
);
transform: translateX(-100%);
animation: skeleton-shimmer 1.6s ease-in-out infinite;
}
@keyframes skeleton-shimmer {
to {
transform: translateX(100%);
@keyframes skeleton-breath {
50% {
opacity: 0.5;
}
}
@media (prefers-reduced-motion: reduce) {
.skeleton::after {
display: none;
.skeleton {
animation: none;
}
}
:global(:root[data-reduce='1']) .skeleton {
animation: none;
}
</style>
@@ -6,27 +6,27 @@
let { status, label }: Props = $props();
type Tone = 'success' | 'info' | 'muted' | 'error' | 'warning' | 'accent';
type Tone = 'ok' | 'run' | 'muted' | 'err' | 'warn';
const toneByStatus: Record<string, Tone> = {
ready: 'success',
done: 'success',
completed: 'success',
running: 'info',
generating: 'info',
queued: 'info',
ready: 'ok',
done: 'ok',
completed: 'ok',
running: 'run',
generating: 'run',
in_progress: 'run',
queued: 'muted',
pending: 'muted',
idle: 'muted',
draft: 'muted',
failed: 'error',
error: 'error',
paused: 'warning',
in_progress: 'accent',
failed: 'err',
error: 'err',
paused: 'warn',
};
const key = $derived(status.trim().toLowerCase());
const tone: Tone = $derived(toneByStatus[key] ?? 'muted');
const pulsing = $derived(key === 'running');
const pulsing = $derived(tone === 'run');
</script>
<span class="chip chip-{tone}">
@@ -35,59 +35,48 @@
</span>
<style>
/* §四.2 状态标签:20px 高、--r-sm、12px;语义色文字 + 13% 软底。 */
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 3px 9px;
border: 1px solid;
border-radius: 999px;
font-size: 10px;
gap: var(--sp-2);
height: 20px;
padding: 0 var(--sp-2);
border-radius: var(--r-sm);
font-size: var(--fs-aux);
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
line-height: 1.4;
white-space: nowrap;
box-sizing: border-box;
}
.dot {
width: 6px;
height: 6px;
width: 7px;
height: 7px;
border-radius: 50%;
background: currentColor;
flex-shrink: 0;
}
.chip-success {
color: var(--success);
background: color-mix(in srgb, var(--success) 12%, transparent);
border-color: color-mix(in srgb, var(--success) 35%, transparent);
.chip-ok {
color: var(--ok);
background: var(--ok-soft);
}
.chip-info {
color: var(--info);
background: color-mix(in srgb, var(--info) 12%, transparent);
border-color: color-mix(in srgb, var(--info) 35%, transparent);
.chip-run {
color: var(--run);
background: var(--run-soft);
}
.chip-error {
color: var(--error);
background: color-mix(in srgb, var(--error) 12%, transparent);
border-color: color-mix(in srgb, var(--error) 35%, transparent);
.chip-err {
color: var(--err);
background: var(--err-soft);
}
.chip-warning {
color: var(--warning);
background: color-mix(in srgb, var(--warning) 12%, transparent);
border-color: color-mix(in srgb, var(--warning) 35%, transparent);
}
.chip-accent {
color: var(--accent);
background: color-mix(in srgb, var(--accent) 12%, transparent);
border-color: color-mix(in srgb, var(--accent) 35%, transparent);
.chip-warn {
color: var(--warn);
background: var(--warn-soft);
}
.chip-muted {
color: var(--text-secondary);
background: rgba(255, 255, 255, 0.04);
border-color: var(--border);
color: var(--text-2);
background: var(--bg-4);
}
.dot.pulsing {
animation: chip-pulse 1.2s ease-in-out infinite;
animation: chip-pulse 1.4s ease-in-out infinite;
}
@keyframes chip-pulse {
0%,
@@ -103,4 +92,7 @@
animation: none;
}
}
:global(:root[data-reduce='1']) .dot.pulsing {
animation: none;
}
</style>
@@ -9,7 +9,7 @@
let { checked, label, disabled = false, onchange }: Props = $props();
</script>
<!-- §5 开关: 34×19 capsule; on = amber track + dark knob, off = --surface-3. -->
<!-- §3.10 开关: 34×19 capsule; on = --accent-lo track + ink knob, off = --bg-4. -->
<button
type="button"
class="switch"
@@ -28,38 +28,38 @@
flex-shrink: 0;
width: 34px;
height: 19px;
border-radius: 999px;
border: 1px solid var(--border-strong);
background: var(--surface-3);
border-radius: var(--r-pill);
border: 1px solid var(--line-2);
background: var(--bg-4);
position: relative;
cursor: pointer;
padding: 0;
transition:
background-color 150ms,
border-color 150ms;
background-color var(--d-state) ease,
border-color var(--d-state) ease;
}
.switch:disabled {
opacity: 0.4;
opacity: 0.45;
cursor: not-allowed;
}
.knob {
position: absolute;
top: 2px;
left: 2px;
top: 1px;
left: 1px;
width: 15px;
height: 15px;
border-radius: 999px;
border-radius: var(--r-pill);
background: var(--text-3);
transition:
transform 150ms cubic-bezier(0.16, 1, 0.3, 1),
background-color 150ms;
transform var(--d-state) var(--ease-out),
background-color var(--d-state) ease;
}
.switch.on {
background: var(--accent);
border-color: var(--accent);
background: var(--accent-lo);
border-color: var(--accent-lo);
}
.switch.on .knob {
transform: translateX(15px);
background: var(--accent-contrast);
background: var(--accent-ink);
}
</style>
@@ -369,7 +369,7 @@
}
.wb-input:focus {
outline: none;
border-color: rgba(232, 163, 61, 0.6);
border-color: var(--accent-line);
}
.wb-input::placeholder {
color: var(--text-3);
@@ -438,7 +438,7 @@
}
.btn.ghost:hover:not(:disabled) {
color: var(--text);
border-color: rgba(255, 255, 255, 0.24);
border-color: var(--line-2);
}
.btn.gen {
color: var(--accent);
@@ -60,8 +60,8 @@
background: var(--surface-3);
}
.st-running {
color: var(--accent);
background: var(--accent-dim);
color: var(--run);
background: var(--run-soft);
}
.st-done {
color: var(--success);
@@ -258,7 +258,7 @@
}
.wb-input:focus {
outline: none;
border-color: rgba(232, 163, 61, 0.6);
border-color: var(--accent-line);
}
.wb-input::placeholder {
color: var(--text-3);
@@ -294,7 +294,7 @@
}
.btn.ghost:hover:not(:disabled) {
color: var(--text);
border-color: rgba(255, 255, 255, 0.24);
border-color: var(--line-2);
}
.btn.sm {
padding: 6px 12px;
@@ -341,7 +341,7 @@
}
.wb-input:focus {
outline: none;
border-color: rgba(232, 163, 61, 0.6);
border-color: var(--accent-line);
}
.wb-input::placeholder {
color: var(--text-3);
@@ -412,7 +412,7 @@
}
.btn.ghost:hover:not(:disabled) {
color: var(--text);
border-color: rgba(255, 255, 255, 0.24);
border-color: var(--line-2);
}
.btn.gen {
color: var(--accent);
@@ -3,9 +3,9 @@
import { t } from '$lib/i18n.svelte';
/**
* Five-stage stepper (a2): one step per agent with the spec §2.1 semantic
* colors — gray=待开始, amber=进行中/待确认 (+breath while running),
* green=已确认, blue=用户改过草稿. A step unlocks when itself or any
* Five-stage stepper (a2): one step per agent with the fusion status
* colors — gray=待开始, cyan=进行中 (+breath), amber=待确认,
* green=已确认, --info=用户改过草稿. A step unlocks when itself or any
* earlier stage has content; later steps stay clickable only after the
* previous one is confirmed.
*/
@@ -159,7 +159,7 @@
justify-content: center;
font-size: 14px;
font-weight: 700;
color: #edeef0;
color: var(--text);
flex-shrink: 0;
}
@@ -188,12 +188,14 @@
.st-todo {
color: var(--text-3);
}
.st-running,
.st-running {
color: var(--run);
}
.st-draft {
color: var(--accent);
color: var(--warn);
}
.st-edited {
color: var(--info);
color: var(--warn);
}
.st-confirmed {
color: var(--success);
@@ -201,14 +203,14 @@
.step-btn.active[data-state='running'] .avatar,
.step-btn[data-state='running'] .avatar {
box-shadow: 0 0 10px rgba(232, 163, 61, 0.35);
box-shadow: 0 0 10px var(--run-soft);
}
.breath {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent);
background: var(--run);
animation: wb-breath 2s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}
@@ -297,7 +297,7 @@
}
.wb-input:focus {
outline: none;
border-color: rgba(232, 163, 61, 0.6);
border-color: var(--accent-line);
}
.wb-input::placeholder {
color: var(--text-3);
@@ -386,7 +386,7 @@
}
.btn.ghost:hover:not(:disabled) {
color: var(--text);
border-color: rgba(255, 255, 255, 0.24);
border-color: var(--line-2);
}
.btn:disabled {
opacity: 0.4;
@@ -214,7 +214,7 @@
}
.dir-card.chosen {
border-color: var(--accent);
box-shadow: 0 0 10px rgba(232, 163, 61, 0.35);
box-shadow: 0 0 10px var(--accent-glow);
}
.dir-card input[type='radio'] {
position: absolute;
@@ -310,7 +310,7 @@
}
.wb-input:focus {
outline: none;
border-color: rgba(232, 163, 61, 0.6);
border-color: var(--accent-line);
}
.wb-input::placeholder {
color: var(--text-3);
@@ -375,7 +375,7 @@
}
.btn.ghost:hover:not(:disabled) {
color: var(--text);
border-color: rgba(255, 255, 255, 0.24);
border-color: var(--line-2);
}
.btn:disabled {
opacity: 0.4;
+17 -20
View File
@@ -245,11 +245,11 @@
return parts.filter(Boolean).join(' · ');
}
function jobBar(job: Job): { value: number; tone: 'accent' | 'success' | 'error' } {
function jobBar(job: Job): { value: number; tone: 'run' | 'success' | 'error' } {
if (job.status === 'done') return { value: 100, tone: 'success' };
if (job.status === 'failed') return { value: 100, tone: 'error' };
if (job.status === 'running') return { value: progressFor(job.id)?.progress ?? 3, tone: 'accent' };
return { value: 0, tone: 'accent' };
if (job.status === 'running') return { value: progressFor(job.id)?.progress ?? 3, tone: 'run' };
return { value: 0, tone: 'run' };
}
function jobStatusLabel(status: string): string {
@@ -493,7 +493,7 @@
<aside class="tasks">
<div class="tasks-head">
<h2>{t('home.tasksTitle')}</h2>
<span class="chip" class:amber={activeJobCount > 0}>
<span class="chip" class:run={activeJobCount > 0}>
{t('home.activeJobs', { n: activeJobCount })}
</span>
</div>
@@ -580,9 +580,9 @@
font-size: 11.5px;
white-space: nowrap;
}
.chip.amber {
background: var(--accent-dim);
color: var(--accent);
.chip.run {
background: var(--run-soft);
color: var(--run);
}
.entry-btn {
@@ -616,7 +616,7 @@
color: var(--text);
}
.entry-btn.secondary:hover {
border-color: rgba(255, 255, 255, 0.24);
border-color: var(--line-2);
}
.entry-btn:active {
transform: scale(0.98);
@@ -651,7 +651,7 @@
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--accent);
color: var(--text-2);
}
.entry-card h2 {
margin: 4px 0 0;
@@ -736,10 +736,10 @@
}
.f-input:focus {
outline: none;
border-color: rgba(232, 163, 61, 0.6);
border-color: var(--accent-line);
}
.f-input.invalid {
border-color: rgba(229, 72, 77, 0.65);
border-color: var(--err-line);
}
.f-hint {
display: block;
@@ -908,13 +908,10 @@
flex-shrink: 0;
white-space: nowrap;
}
.tag-local {
background: var(--info-dim);
color: var(--info);
}
.tag-local,
.tag-cloud {
background: var(--cloud-dim);
color: var(--cloud);
background: var(--bg-4);
color: var(--text-2);
}
.job-name {
flex: 1;
@@ -950,7 +947,7 @@
color: var(--success);
}
.running-text {
color: var(--accent);
color: var(--run);
}
.queued-text {
color: var(--text-3);
@@ -966,8 +963,8 @@
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 10px var(--accent-glow);
background: var(--run);
box-shadow: 0 0 10px var(--run-soft);
}
.dot.breathe {
animation: breathe 2s ease-in-out infinite;
+5 -5
View File
@@ -1133,11 +1133,11 @@
justify-content: center;
font-size: 14px;
font-weight: 700;
color: #edeef0;
color: var(--text);
flex-shrink: 0;
}
.avatar.dark {
color: #16130a;
color: var(--accent-ink);
}
.avatar.lg {
width: 42px;
@@ -1294,8 +1294,8 @@
white-space: nowrap;
}
.status-tag.busy {
background: var(--accent-dim);
color: var(--accent);
background: var(--run-soft);
color: var(--run);
}
.chat-head-actions {
display: flex;
@@ -1618,7 +1618,7 @@
background: var(--success);
}
.dot.amber {
background: var(--accent);
background: var(--run);
}
.dot.amber.breathing {
animation: breathe 2s ease-in-out infinite;
+401 -149
View File
@@ -4,84 +4,201 @@
:root {
/* ------------------------------------------------------------------
* A「片场暗房」design tokens — spec-v2 §2 (HEL-603 confirmed).
* Single source of truth for the whole app: do not hardcode colors in
* pages or components, and do not override these with ad-hoc CSS.
* 融合视觉 design tokens — HEL-649 整站施工规范 §一(流水台结构 ×
* 放映厅配色)。Single source of truth for the whole app: pages and
* components reference these variables only — no hardcoded colors,
* no ad-hoc overrides. Gold is for key actions and the current
* selection only; status colors each carry exactly one meaning.
* ------------------------------------------------------------------ */
--bg: #0d0e11;
--surface: #14161b;
--surface-2: #1b1e25;
--surface-3: #22262f;
--border: rgba(255, 255, 255, 0.07);
--border-strong: rgba(255, 255, 255, 0.14);
--border-hover: rgba(255, 255, 255, 0.24);
--text: #edeef0;
--text-2: #9ba1ab;
--text-3: #5c626c;
--accent: #e8a33d;
--accent-hover: #f1b155; /* +8% brightness (§5 主按钮) */
--accent-contrast: #16130a;
--accent-dim: rgba(232, 163, 61, 0.14);
--accent-glow: rgba(232, 163, 61, 0.35);
--accent-focus: rgba(232, 163, 61, 0.6);
--success: #3fb97f;
--success-dim: rgba(63, 185, 127, 0.14);
--info: #5b8def;
--info-dim: rgba(91, 141, 239, 0.14);
--error: #e5484d;
--error-dim: rgba(229, 72, 77, 0.14);
--error-border: rgba(229, 72, 77, 0.4);
--cloud: #b98be0;
--cloud-dim: rgba(185, 139, 224, 0.14);
/* Avatar five-color set (§2.1) — five agents only */
/* 底色:蓝黑四阶,逐层浮起 (§1.1) */
--bg: #07090e;
--bg-2: #0c0f16;
--bg-3: #121724;
--bg-4: #1a2133;
--line: #1b2233;
--line-2: #2a3450;
--page-glow: radial-gradient(1200px 500px at 70% -10%, rgba(38, 52, 92, 0.25), transparent 60%);
--float-bg: linear-gradient(180deg, #10152a, var(--bg-2));
--overlay: rgba(4, 6, 10, 0.6);
--media-scrim: rgba(7, 9, 14, 0.65);
--on-media: #fff;
--on-media-line: rgba(255, 255, 255, 0.05);
/* 放映金:主按钮、当前选中、改过的参数、聚焦框、Toast 动作 (§1.2) */
--accent: #f2c784;
--accent-ink: #23170a;
--accent-soft: rgba(242, 199, 132, 0.13);
--accent-hi: #f7d9a2;
--accent-lo: #ecaf63;
--accent-glow: rgba(236, 175, 99, 0.28);
--accent-line: rgba(242, 199, 132, 0.55);
--accent-grad: linear-gradient(180deg, var(--accent-hi), var(--accent-lo));
--accent-grad-hover: linear-gradient(180deg, #fbe3b6, #f0b968);
--accent-ring: 0 0 0 1px rgba(242, 199, 132, 0.7), 0 0 22px var(--accent-glow);
--btn-glow: 0 2px 14px var(--accent-glow), inset 0 1px 0 rgba(255, 255, 255, 0.35);
--btn-glow-hover: 0 4px 18px var(--accent-glow), inset 0 1px 0 rgba(255, 255, 255, 0.4);
/* 信号色:绿=完成/已确认/通过,青=进行中/在线,红=失败/危险,
琥珀=待确认/文件缺失/限额 (§1.3) */
--ok: #49cb90;
--ok-soft: rgba(73, 203, 144, 0.13);
--run: #54dfd2;
--run-soft: rgba(84, 223, 210, 0.13);
--run-grad: linear-gradient(90deg, #3bbfb4, var(--run));
--err: #f0645a;
--err-soft: rgba(240, 100, 90, 0.13);
--err-line: rgba(240, 100, 90, 0.6);
--warn: #f0b45c;
--warn-soft: rgba(240, 180, 92, 0.13);
/* 文字与字体 (§1.4);--text-2 是深底可读性下限,不得再暗 */
--text: #f4f6fa;
--text-2: #aab2c5;
--text-3: #6d7690;
--font: 'PingFang SC', 'Noto Sans SC', 'Microsoft YaHei', system-ui, sans-serif;
--mono: 'JetBrains Mono', ui-monospace, monospace;
--fs-title: 15px;
--fs-section: 14px;
--fs-body: 13px;
--fs-body-sm: 12.5px;
--fs-aux: 12px;
--fs-mini: 11px;
--lh-body: 1.55;
/* 间距阶梯 4/6/8/10/12/16/20/28,不得出现阶梯外数值 (§1.5) */
--sp-1: 4px;
--sp-2: 6px;
--sp-3: 8px;
--sp-4: 10px;
--sp-5: 12px;
--sp-6: 16px;
--sp-7: 20px;
--sp-8: 28px;
/* 圆角与唯一一级阴影 (§1.5);卡片不投影,用底色差分层 */
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
--r-pill: 999px;
--shadow-float: 0 14px 44px rgba(0, 0, 0, 0.6), 0 0 0 1px var(--line-2);
/* 专属符号 (§1.7):齿孔只在记录流/输入台分隔带,光晕只在播放舞台 */
--sprocket: radial-gradient(circle 2px, var(--line-2) 98%, transparent);
--stage-bg:
radial-gradient(60% 45% at 50% 38%, rgba(242, 199, 132, 0.07), transparent 70%),
radial-gradient(80% 70% at 50% 50%, #0d1220, #05070c);
/* 动效 (§1.6,取 HEL-648 实测值);只动 transform / opacity */
--ease-out: cubic-bezier(0.23, 1, 0.32, 1);
--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
--ease-spring: cubic-bezier(0.34, 1.45, 0.44, 1);
--d-press: 120ms;
--d-state: 150ms;
--d-pop: 180ms;
--d-leave: 200ms;
--d-enter: 220ms;
--d-drawer: 280ms;
--d-flash: 900ms;
--d-progress: 400ms;
--press-scale: 0.97;
/* Avatar five-color set — five agents only; the one colorful gradient
family the spec keeps (§3.6). */
--avatar-1: linear-gradient(135deg, #e8a33d, #b96a1b);
--avatar-2: linear-gradient(135deg, #5b8def, #3a5fa9);
--avatar-3: linear-gradient(135deg, #3fb97f, #2a7a54);
--avatar-4: linear-gradient(135deg, #b98be0, #7a4fa9);
--avatar-5: linear-gradient(135deg, #d98e6a, #a95535);
/* Typography (§2.2) */
--font-ui: 'Noto Sans SC', 'Noto Sans CJK SC', sans-serif;
--font-a-mono: 'JetBrains Mono', 'IBM Plex Mono', ui-monospace, monospace;
/* Radius / spacing (§2.3) */
--radius-sm: 7px;
--radius-md: 10px;
--radius-lg: 10px;
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 32px;
/* Legacy token names used by shared primitives — aliases of the A set,
so every component renders in the same darkroom palette. */
/* ------------------------------------------------------------------
* Legacy token names (§六 映射表) — aliases only, never new values,
* so every existing component renders in the fusion palette.
* ------------------------------------------------------------------ */
--surface: var(--bg-2);
--surface-2: var(--bg-3);
--surface-3: var(--bg-4);
--border: var(--line);
--border-strong: var(--line-2);
--border-hover: var(--line-2);
--accent-hover: var(--accent-hi);
--accent-contrast: var(--accent-ink);
--accent-dim: var(--accent-soft);
--accent-focus: var(--accent-line);
--success: var(--ok);
--success-dim: var(--ok-soft);
--info: var(--run);
--info-dim: var(--run-soft);
--error: var(--err);
--error-dim: var(--err-soft);
--error-border: var(--err-line);
--warning: var(--warn);
--bg-primary: var(--bg);
--bg-surface: var(--surface);
--bg-elevated: var(--surface-2);
--bg-surface: var(--bg-2);
--bg-elevated: var(--bg-3);
--text-primary: var(--text);
--text-secondary: var(--text-2);
--text-muted: var(--text-3);
--warning: var(--accent);
--font-display: var(--font-ui);
--font-body: var(--font-ui);
--font-mono: var(--font-a-mono);
--font-ui: var(--font);
--font-a-mono: var(--mono);
--font-display: var(--font);
--font-body: var(--font);
--font-mono: var(--mono);
--radius-sm: var(--r-sm);
--radius-md: var(--r-md);
--radius-lg: var(--r-lg);
--space-xs: var(--sp-1);
--space-sm: var(--sp-3);
--space-md: var(--sp-6);
--space-lg: var(--sp-7);
--space-xl: var(--sp-8);
/* Omni Composer pill tokens */
--pill-bg: var(--surface-2);
--pill-border: var(--border-strong);
--pill-radius: 9999px;
--pill-bg: var(--bg-3);
--pill-border: var(--line-2);
--pill-radius: var(--r-pill);
--pill-height: 36px;
--pill-padding: 0 14px;
--pill-font-size: 13px;
--pill-font-size: var(--fs-body);
}
/* 减少动态效果 (§五):系统设置自动生效,顶栏开关写 data-reduce="1"。
位移/缩放/弹入/光晕全部取消,只留 ≤120ms 透明度,进度条直接跳变。 */
:root[data-reduce='1'] {
--d-press: 0ms;
--d-state: 120ms;
--d-pop: 120ms;
--d-leave: 120ms;
--d-enter: 120ms;
--d-drawer: 120ms;
--d-flash: 0ms;
--d-progress: 0ms;
--press-scale: 1;
--ease-spring: ease;
}
@media (prefers-reduced-motion: reduce) {
:root {
--d-press: 0ms;
--d-state: 120ms;
--d-pop: 120ms;
--d-leave: 120ms;
--d-enter: 120ms;
--d-drawer: 120ms;
--d-flash: 0ms;
--d-progress: 0ms;
--press-scale: 1;
--ease-spring: ease;
}
}
body {
background: var(--bg);
background: var(--page-glow), var(--bg);
color: var(--text);
font-family: var(--font-ui);
font-family: var(--font);
font-size: var(--fs-body);
line-height: var(--lh-body);
-webkit-font-smoothing: antialiased;
margin: 0;
min-height: 100vh;
}
@@ -89,20 +206,20 @@ body {
.a-darkroom {
background: var(--bg);
color: var(--text);
font-family: var(--font-ui);
font-size: 13.5px;
line-height: 1.6;
font-family: var(--font);
font-size: var(--fs-body);
line-height: var(--lh-body);
}
code,
.mono,
.kh {
font-family: var(--font-a-mono);
font-family: var(--mono);
font-variant-numeric: tabular-nums;
}
/* ------------------------------------------------------------------
* Shared A controls (§5 组件状态全表). The only definitions of these
* Shared controls (§四 组件状态通用规范). The only definitions of these
* looks — `ui/Button.svelte` renders `.a-btn`, pages use the classes
* directly for links / selects. Page styles may only tweak layout
* (width / margin), never re-declare colors.
@@ -111,91 +228,120 @@ code,
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
gap: var(--sp-2);
min-height: 34px;
padding: 0 16px;
border-radius: 8px;
padding: 0 var(--sp-6);
border-radius: var(--r-md);
border: 1px solid transparent;
font-family: inherit;
font-size: 13px;
font-size: var(--fs-body);
font-weight: 600;
white-space: nowrap;
text-decoration: none;
cursor: pointer;
box-sizing: border-box;
transition:
background-color 150ms cubic-bezier(0.16, 1, 0.3, 1),
border-color 150ms,
color 150ms,
transform 100ms;
transform var(--d-press) var(--ease-out),
background-color var(--d-state) ease,
border-color var(--d-state) ease,
box-shadow var(--d-state) ease,
color var(--d-state) ease,
opacity var(--d-state) ease;
}
.a-btn.lg {
min-height: 38px;
padding: 0 var(--sp-7);
}
.a-btn.sm {
min-height: 30px;
padding: 0 12px;
font-size: 12px;
min-height: 28px;
padding: 0 var(--sp-4);
font-size: var(--fs-aux);
border-radius: var(--r-sm);
}
.a-btn.xs {
min-height: 24px;
padding: 0 9px;
font-size: 11px;
padding: 0 var(--sp-3);
font-size: var(--fs-mini);
border-radius: var(--r-sm);
}
.a-btn.block {
width: 100%;
}
.a-btn.primary {
background: var(--accent);
color: var(--accent-contrast);
}
.a-btn.primary:hover:not(:disabled) {
background: var(--accent-hover);
background: var(--accent-grad);
color: var(--accent-ink);
box-shadow: var(--btn-glow);
}
.a-btn.secondary {
background: var(--surface-2);
border-color: var(--border-strong);
background: var(--bg-3);
border-color: var(--line);
color: var(--text);
}
.a-btn.secondary:hover:not(:disabled) {
border-color: var(--border-hover);
}
.a-btn.ghost {
background: transparent;
color: var(--text-2);
}
.a-btn.ghost:hover:not(:disabled) {
background: var(--surface-3);
color: var(--text);
}
/* 危险两级制:列表行内只用描边红 danger;实心红 danger-solid 只在确认弹层内。 */
.a-btn.danger {
background: transparent;
border-color: var(--error-border);
color: var(--error);
border-color: var(--err-line);
color: var(--err);
}
.a-btn.danger:hover:not(:disabled) {
background: var(--error-dim);
.a-btn.danger-solid {
background: var(--err);
color: var(--on-media);
}
@media (hover: hover) and (pointer: fine) {
.a-btn.primary:hover:not(:disabled) {
background: var(--accent-grad-hover);
box-shadow: var(--btn-glow-hover);
}
.a-btn.secondary:hover:not(:disabled) {
border-color: var(--line-2);
background: var(--bg-4);
}
.a-btn.ghost:hover:not(:disabled) {
background: var(--bg-4);
color: var(--text);
}
.a-btn.danger:hover:not(:disabled) {
background: var(--err-soft);
}
.a-btn.danger-solid:hover:not(:disabled) {
box-shadow:
0 0 0 1px var(--err-line),
0 0 18px var(--err-soft);
}
}
.a-btn:active:not(:disabled) {
transform: scale(0.98);
transform: scale(var(--press-scale));
}
/* 禁用必须在旁边给出中文原因(§1.2),由调用方渲染。 */
.a-btn:disabled,
.a-btn[aria-disabled='true'] {
opacity: 0.4;
opacity: 0.45;
box-shadow: none;
cursor: not-allowed;
}
/* Top-bar status capsule (counts / save state / connection). */
/* Top-bar status capsule (queue / connection / save state) — §二 顶栏胶囊. */
.status-chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 12px;
border-radius: 999px;
background: var(--surface-3);
gap: var(--sp-2);
height: 30px;
padding: 0 var(--sp-5);
border-radius: var(--r-pill);
border: 1px solid var(--line);
background: var(--bg-2);
color: var(--text-2);
font-size: 11.5px;
font-size: var(--fs-aux);
white-space: nowrap;
box-sizing: border-box;
}
.status-chip.accent {
background: var(--accent-dim);
background: var(--accent-soft);
border-color: var(--accent-line);
color: var(--accent);
}
.status-chip-dot {
@@ -206,33 +352,122 @@ code,
background: currentColor;
}
.status-chip-dot.ok {
background: var(--success);
background: var(--ok);
}
.status-chip-dot.run {
background: var(--run);
}
.status-chip-dot.fail {
background: var(--error);
background: var(--err);
}
.status-chip-dot.idle {
background: var(--text-3);
}
/* Card / panel surface (§2.3: 10px radius, 1px border, no shadow). */
/* Status label (§四.2):语义色文字 + 13% 软底;默认为中性(排队 / 未开始)。 */
.a-tag {
display: inline-flex;
align-items: center;
gap: var(--sp-1);
height: 20px;
padding: 0 var(--sp-2);
border-radius: var(--r-sm);
background: var(--bg-4);
color: var(--text-2);
font-size: var(--fs-aux);
white-space: nowrap;
box-sizing: border-box;
transition:
color var(--d-state) ease,
background-color var(--d-state) ease;
}
.a-tag.accent {
color: var(--accent);
background: var(--accent-soft);
}
.a-tag.ok {
color: var(--ok);
background: var(--ok-soft);
}
.a-tag.run {
color: var(--run);
background: var(--run-soft);
}
.a-tag.err {
color: var(--err);
background: var(--err-soft);
}
.a-tag.warn {
color: var(--warn);
background: var(--warn-soft);
}
/* Real-progress bar (§1.3):<div class="a-progress" style="--p:.42"><i></i></div>.
Only for true progress values — never fake progress. */
.a-progress {
position: relative;
height: 4px;
border-radius: var(--r-pill);
background: var(--bg-4);
overflow: hidden;
}
.a-progress > i {
position: absolute;
inset: 0;
background: var(--run-grad);
box-shadow: 0 0 8px var(--run-soft);
transform-origin: left;
transform: scaleX(var(--p, 0));
transition: transform var(--d-progress) linear;
}
/* Film sprocket divider — only between the take feed and the composer (§1.7). */
.a-sprocket {
height: 6px;
background-image: var(--sprocket);
background-size: 18px 6px;
background-repeat: repeat-x;
background-position: center;
opacity: 0.55;
}
:root[data-reduce='1'] .a-sprocket {
opacity: 0.3;
}
/* Projection stage — only behind media in the inspector (§1.7). */
.a-stage {
position: relative;
border-radius: var(--r-lg);
overflow: hidden;
background: var(--stage-bg);
box-shadow: 0 0 0 1px var(--line);
}
/* Floating surface for pop / drawer / confirm / toast / lightbox (§四.3). */
.a-float {
background: var(--float-bg);
border-radius: var(--r-lg);
box-shadow: var(--shadow-float);
}
/* Card / panel surface (§1.5:卡片不投影,用底色差分层). */
.a-panel {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 10px;
padding: 20px 22px;
background: var(--bg-2);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: var(--sp-7);
box-sizing: border-box;
}
.a-panel-title {
margin: 0;
font-size: 14.5px;
font-size: var(--fs-section);
font-weight: 600;
color: var(--text);
}
.a-panel-lead {
margin: 4px 0 0;
font-size: 12px;
line-height: 1.6;
margin: var(--sp-1) 0 0;
font-size: var(--fs-aux);
line-height: var(--lh-body);
color: var(--text-3);
}
@@ -243,39 +478,47 @@ code,
.field-textarea,
.field-select {
box-sizing: border-box;
background: var(--surface-2);
border: 1px solid var(--border-strong);
border-radius: 7px;
background: var(--bg-3);
border: 1px solid var(--line);
border-radius: var(--r-md);
color: var(--text);
font-family: inherit;
font-size: 12.5px;
transition: border-color 150ms cubic-bezier(0.16, 1, 0.3, 1);
font-size: var(--fs-body-sm);
transition: border-color var(--d-state) ease;
}
.a-select {
padding: 7px 9px;
padding: var(--sp-2) var(--sp-4);
cursor: pointer;
}
.a-input,
.field-input,
.field-select {
width: 100%;
padding: 8px 11px;
padding: var(--sp-3) var(--sp-5);
}
.a-textarea,
.field-textarea {
width: 100%;
padding: 9px 11px;
padding: var(--sp-4) var(--sp-5);
resize: vertical;
min-height: 96px;
line-height: 1.6;
line-height: var(--lh-body);
}
.a-select:hover,
.a-input:hover,
.a-textarea:hover,
.field-input:hover,
.field-textarea:hover,
.field-select:hover {
border-color: var(--border-hover);
.a-input::placeholder,
.a-textarea::placeholder,
.field-input::placeholder,
.field-textarea::placeholder {
color: var(--text-3);
}
@media (hover: hover) and (pointer: fine) {
.a-select:hover,
.a-input:hover,
.a-textarea:hover,
.field-input:hover,
.field-textarea:hover,
.field-select:hover {
border-color: var(--line-2);
}
}
.a-select:focus,
.a-input:focus,
@@ -284,12 +527,12 @@ code,
.field-textarea:focus,
.field-select:focus {
outline: none;
border-color: var(--accent-focus);
border-color: var(--accent-line);
}
.a-input.invalid,
.a-textarea.invalid,
.field-input.invalid {
border-color: var(--error);
border-color: var(--err);
}
.a-select option.unavailable {
color: var(--text-3);
@@ -297,30 +540,30 @@ code,
.field {
display: block;
margin-bottom: 14px;
margin-bottom: var(--sp-5);
}
.field-label {
display: block;
font-size: 12px;
font-size: var(--fs-aux);
font-weight: 500;
color: var(--text-2);
margin-bottom: 6px;
margin-bottom: var(--sp-2);
}
.field-hint {
margin: 6px 0 0;
font-size: 11.5px;
margin: var(--sp-2) 0 0;
font-size: var(--fs-aux);
color: var(--text-3);
}
.field-error {
margin: 6px 0 0;
font-size: 11.5px;
color: var(--error);
margin: var(--sp-2) 0 0;
font-size: var(--fs-aux);
color: var(--err);
}
/* Dark theme scrollbars (Firefox + Chromium) */
* {
scrollbar-width: thin;
scrollbar-color: var(--surface-3) transparent;
scrollbar-color: var(--bg-4) transparent;
}
*::-webkit-scrollbar {
@@ -333,8 +576,8 @@ code,
}
*::-webkit-scrollbar-thumb {
background: var(--surface-3);
border-radius: 999px;
background: var(--bg-4);
border-radius: var(--r-pill);
border: 2px solid transparent;
background-clip: padding-box;
}
@@ -349,17 +592,19 @@ code,
background: transparent;
}
/* Global keyboard focus fallback: every natively focusable element gets a ring,
even before the ui/ primitives are adopted. Component-scoped :focus-visible
rules (e.g. .field-input) override this via higher specificity. */
/* Global keyboard focus (§四.8): every natively focusable element gets the
gold ring. Component-scoped :focus-visible rules override this via higher
specificity. */
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: var(--r-sm);
}
/* Global reduced-motion baseline. Components that need a styled static fallback
(Skeleton shimmer, ProgressBar indeterminate, StatusChip pulse) also handle
this in their own scoped styles. */
/* Reduced-motion baseline for components that still carry their own
durations. Components that need a styled static fallback (Skeleton,
ProgressBar indeterminate, StatusChip pulse) also handle this in their
scoped styles. */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
@@ -369,3 +614,10 @@ code,
transition-duration: 0.01ms !important;
}
}
:root[data-reduce='1'] *,
:root[data-reduce='1'] *::before,
:root[data-reduce='1'] *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
@@ -1654,7 +1654,7 @@
transition: border-color 150ms cubic-bezier(0.16, 1, 0.3, 1);
}
.card.failed {
border-color: rgba(229, 72, 77, 0.35);
border-color: color-mix(in srgb, var(--err) 35%, transparent);
}
.card-row {
display: flex;
@@ -1671,7 +1671,7 @@
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--accent);
color: var(--text-2);
flex-shrink: 0;
}
.card-name {
@@ -1688,13 +1688,10 @@
font-weight: 600;
}
.tag-local,
.tag-lan {
color: var(--info);
background: var(--info-dim);
}
.tag-lan,
.tag-cloud {
color: var(--cloud);
background: var(--cloud-dim);
color: var(--text-2);
background: var(--bg-4);
}
.tag-auth {
color: var(--text-2);
@@ -1743,8 +1740,8 @@
background: var(--text-3);
}
.dot.amber {
background: var(--accent);
box-shadow: 0 0 10px var(--accent-glow);
background: var(--run);
box-shadow: 0 0 10px var(--run-soft);
}
.dot.breathing {
animation: breathe 2s ease-in-out infinite;
@@ -1769,7 +1766,7 @@
color: var(--error);
}
.status-text.amber {
color: var(--accent);
color: var(--run);
}
.status-text.gray {
color: var(--text-3);
@@ -1959,7 +1956,7 @@
border-color: var(--error);
}
.wf-conn-check.warn {
border-color: var(--accent);
border-color: var(--warn);
}
.wf-conn-head {
display: flex;
@@ -1977,7 +1974,7 @@
color: var(--error);
}
.wf-conn-check.warn .wf-conn-head :global(.icon) {
color: var(--accent);
color: var(--warn);
}
.wf-conn-title {
font-size: 12px;
@@ -2067,7 +2064,7 @@
cursor: help;
}
.wf-meta-item.recheck.warn .wf-recheck-text {
color: var(--accent);
color: var(--warn);
}
.wf-evidence {
margin: 0 0 12px;
@@ -2198,15 +2195,15 @@
color: var(--error);
}
.tag-usage {
color: var(--accent);
background: var(--accent-dim);
color: var(--text-2);
background: var(--bg-4);
}
.tag-disabled {
color: var(--text-3);
background: var(--surface-3);
}
.wf-pending-note {
color: var(--accent);
color: var(--warn);
}
.wf-row.pending .wf-name {
color: var(--text-3);
@@ -2252,7 +2249,7 @@
line-height: 1.5;
}
.wf-json.invalid {
border-color: rgba(229, 72, 77, 0.65);
border-color: var(--err-line);
}
.file-btn {
display: inline-flex;
@@ -2271,7 +2268,7 @@
}
.file-btn:hover {
color: var(--text);
border-color: rgba(255, 255, 255, 0.24);
border-color: var(--line-2);
}
.file-btn input {
display: none;
@@ -2302,13 +2299,10 @@
justify-content: center;
flex-shrink: 0;
}
.wf-kind.image {
color: var(--info);
background: var(--info-dim);
}
.wf-kind.image,
.wf-kind.video {
color: var(--accent);
background: var(--accent-dim);
color: var(--text-2);
background: var(--bg-4);
}
.wf-name {
flex: 1;
@@ -2460,14 +2454,14 @@
}
.f-input:focus {
outline: none;
border-color: rgba(232, 163, 61, 0.6);
border-color: var(--accent-line);
}
.f-input:disabled {
color: var(--text-3);
cursor: not-allowed;
}
.f-input.invalid {
border-color: rgba(229, 72, 77, 0.65);
border-color: var(--err-line);
}
.f-hint {
display: block;
@@ -2610,7 +2604,7 @@
position: fixed;
inset: 0;
z-index: 790;
background: rgba(5, 5, 8, 0.5);
background: var(--overlay);
}
.wf-import-grid {
grid-template-columns: 1fr;
+12 -15
View File
@@ -929,7 +929,7 @@
transition: border-color 150ms cubic-bezier(0.16, 1, 0.3, 1);
}
.card.failed {
border-color: rgba(229, 72, 77, 0.35);
border-color: color-mix(in srgb, var(--err) 35%, transparent);
}
.card-row {
display: flex;
@@ -946,7 +946,7 @@
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--accent);
color: var(--text-2);
flex-shrink: 0;
}
.card-name {
@@ -963,13 +963,10 @@
font-weight: 600;
}
.tag-local,
.tag-lan {
color: var(--info);
background: var(--info-dim);
}
.tag-lan,
.tag-cloud {
color: var(--cloud);
background: var(--cloud-dim);
color: var(--text-2);
background: var(--bg-4);
}
.tag-default {
color: var(--accent);
@@ -1010,8 +1007,8 @@
background: var(--text-3);
}
.dot.amber {
background: var(--accent);
box-shadow: 0 0 10px var(--accent-glow);
background: var(--run);
box-shadow: 0 0 10px var(--run-soft);
}
.dot.breathing {
animation: breathe 2s ease-in-out infinite;
@@ -1036,7 +1033,7 @@
color: var(--error);
}
.status-text.amber {
color: var(--accent);
color: var(--run);
}
.status-text.gray {
color: var(--text-3);
@@ -1179,7 +1176,7 @@
color: var(--text);
}
.avatar.dark {
color: #16130a;
color: var(--accent-ink);
}
.agent-name {
font-size: 12.5px;
@@ -1282,14 +1279,14 @@
}
.f-input:focus {
outline: none;
border-color: rgba(232, 163, 61, 0.6);
border-color: var(--accent-line);
}
.f-input:disabled {
color: var(--text-3);
cursor: not-allowed;
}
.f-input.invalid {
border-color: rgba(229, 72, 77, 0.65);
border-color: var(--err-line);
}
.f-hint {
display: block;
@@ -1433,7 +1430,7 @@
position: fixed;
inset: 0;
z-index: 790;
background: rgba(5, 5, 8, 0.5);
background: var(--overlay);
}
.agents-grid {
grid-template-columns: repeat(3, 1fr);
@@ -669,7 +669,7 @@
color: var(--text-2);
}
.note.warn :global(.icon) {
color: var(--accent);
color: var(--warn);
flex-shrink: 0;
margin-top: 2px;
}
+5 -8
View File
@@ -312,7 +312,7 @@
min-height: 56px;
}
.trow.failed {
border-color: rgba(229, 72, 77, 0.35);
border-color: color-mix(in srgb, var(--err) 35%, transparent);
}
.tkind {
@@ -324,13 +324,10 @@
justify-content: center;
flex-shrink: 0;
}
.tkind.image {
color: var(--info);
background: var(--info-dim);
}
.tkind.image,
.tkind.video {
color: var(--accent);
background: var(--accent-dim);
color: var(--text-2);
background: var(--bg-4);
}
.tthumb {
@@ -412,7 +409,7 @@
color: var(--success);
}
.tst.run {
color: var(--accent);
color: var(--run);
}
.tst.queue {
color: var(--text-3);
@@ -670,10 +670,13 @@
color: var(--text-2);
background: var(--surface-3);
}
.st-running,
.st-running {
color: var(--run);
background: var(--run-soft);
}
.st-draft {
color: var(--accent);
background: var(--accent-dim);
color: var(--warn);
background: var(--warn-soft);
}
.st-confirmed {
color: var(--success);
@@ -684,8 +687,8 @@
border-radius: 999px;
font-size: 11px;
font-weight: 600;
color: var(--info);
background: var(--info-dim);
color: var(--warn);
background: var(--warn-soft);
}
.error-banner {
@@ -695,7 +698,7 @@
padding: 10px 14px;
border-radius: 8px;
background: var(--error-dim);
border: 1px solid rgba(229, 72, 77, 0.35);
border: 1px solid color-mix(in srgb, var(--err) 35%, transparent);
color: var(--error);
font-size: 12.5px;
line-height: 1.6;
@@ -706,9 +709,9 @@
gap: 8px;
padding: 10px 14px;
border-radius: 8px;
background: var(--accent-dim);
border: 1px solid rgba(232, 163, 61, 0.3);
color: var(--accent);
background: var(--warn-soft);
border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
color: var(--warn);
font-size: 12.5px;
}
.running-banner {
@@ -766,7 +769,7 @@
justify-content: center;
font-size: 16px;
font-weight: 700;
color: #edeef0;
color: var(--text);
flex-shrink: 0;
}
.agent-copy {
@@ -833,13 +836,10 @@
justify-content: center;
flex-shrink: 0;
}
.kind-image {
background: var(--info-dim);
color: var(--info);
}
.kind-image,
.kind-video {
background: var(--cloud-dim);
color: var(--cloud);
background: var(--bg-4);
color: var(--text-2);
}
.job-body {
display: flex;
@@ -872,7 +872,7 @@
color: var(--success);
}
.job-state.st-running {
color: var(--accent);
color: var(--run);
}
.job-state.st-pending {
color: var(--text-2);
@@ -904,9 +904,11 @@
.flow-dot.st-confirmed {
background: var(--success);
}
.flow-dot.st-running,
.flow-dot.st-running {
background: var(--run);
}
.flow-dot.st-awaiting_confirm {
background: var(--accent);
background: var(--warn);
}
.flow-name {
font-size: 12px;
@@ -953,7 +955,7 @@
}
.wb-input:focus {
outline: none;
border-color: rgba(232, 163, 61, 0.6);
border-color: var(--accent-line);
}
.wb-input::placeholder {
color: var(--text-3);
@@ -990,7 +992,7 @@
}
.btn.ghost:hover:not(:disabled) {
color: var(--text);
border-color: rgba(255, 255, 255, 0.24);
border-color: var(--line-2);
}
.btn.sm {
padding: 6px 11px;
@@ -1024,7 +1026,7 @@
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--accent);
background: var(--run);
animation: wb-page-breath 2s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}
.breath.big {
+16 -10
View File
@@ -1,20 +1,26 @@
// Colors and radii resolve to the design tokens in src/routes/app.css so the
// Tailwind palette can never drift from the single source of truth.
module.exports = {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {
colors: {
primary: '#0a0a0c',
surface: '#141418',
elevated: '#1e1e24',
border: '#2a2a32',
muted: '#71717a',
accent: '#8b5cf6',
'accent-hover': '#7c3aed',
primary: 'var(--bg)',
surface: 'var(--bg-2)',
elevated: 'var(--bg-3)',
border: 'var(--line)',
muted: 'var(--text-3)',
accent: 'var(--accent)',
'accent-hover': 'var(--accent-hi)',
ok: 'var(--ok)',
run: 'var(--run)',
err: 'var(--err)',
warn: 'var(--warn)',
},
borderRadius: {
sm: '6px',
md: '10px',
lg: '16px',
sm: 'var(--r-sm)',
md: 'var(--r-md)',
lg: 'var(--r-lg)',
},
},
},