style: 全站使用背景分层替代装饰边框并统一直角
This commit is contained in:
@@ -94,13 +94,14 @@ Oxfmt 不负责代码质量,Oxlint 不负责 Vue 的完整类型推导;Vue S
|
||||
- 拆解页固定精简状态栏与结果标签,设置、预览及恢复操作收进“拆解设置与恢复”面板;人物、场景、道具、分镜和任务明细共用独立结果滚动区,执行记录单独滚动。结果区占满剩余高度,切换标签回到顶部,轮询刷新或开关设置不重置阅读位置;失败与校验提示在面板关闭时仍然可见。
|
||||
- 剧集正文按编号连续展示全部已写入剧集;点击目录定位正文,滚动正文同步高亮并保持当前目录项可见。目录分行显示集数和完整标题,执行记录可按需展开。切换角色/世界观等标签不会销毁阅读器或重置位置。
|
||||
- 主体身份、分镜设计和镜头生产共用 `DirectoryItem`:编号、完整标题和状态分层展示,桌面目录为 240–280px,窄屏改为可横向滚动的条目。目录标题与筛选固定,只有列表滚动。拆解结果的导出按钮与标签同排右对齐;形态图片筛选按内容区宽度自动排成一行或两列网格。
|
||||
- 侧栏导航与主体/剧集/镜头目录采用直角满行背景,移除列表两侧的内缩,悬停和选中态都铺满所在列。条目内部保留文字间距;窄屏横向目录仍保持独立滚动,不影响表单与弹窗的圆角。
|
||||
- 主体目录以母版缩略图搭配名称、编号与状态,默认保留行背景和细分隔线。人物优先复用选角接口的母版;缺少地址的条目进入可视区域后才读取正式身份图库,不为整份目录轮询。无母版或加载失败显示人物/场景/道具图标,候选与形态图不会替代母版;点击缩略图只切换主体,刷新目录可重试读取。
|
||||
- 侧栏导航与主体/剧集/镜头目录采用直角满行背景,移除列表两侧的内缩,悬停和选中态都铺满所在列。条目内部保留文字间距;窄屏横向目录仍保持独立滚动。
|
||||
- 主体目录以母版缩略图搭配名称、编号与状态,默认用行背景和轻微间距区分条目。人物优先复用选角接口的母版;缺少地址的条目进入可视区域后才读取正式身份图库,不为整份目录轮询。无母版或加载失败显示人物/场景/道具图标,候选与形态图不会替代母版;点击缩略图只切换主体,刷新目录可重试读取。
|
||||
- 分镜设计与镜头生产共用两级镜头目录:分组头展示 `BEAT 01` 和镜头数,组内仅显示“镜头 01”、标题及状态。分组头在本组内吸顶;窄屏横向模式才在条目上补回 Beat 归属。点击仍使用正式 Shot ID,不会混淆不同 Beat 的同号镜头。
|
||||
- 身份、分镜、生产与图库的批量配置、说明和诊断统一放入 `WorkspaceTools`(Naive UI Drawer),按需覆盖当前工作区,不挤压列表。面板支持 Esc 或关闭按钮;关闭不会取消任务或清除配置。历史回执可从入口查看,新回执主动打开诊断;查询错误始终在外层可见。
|
||||
- 生产步骤的操作按钮保留上下间距;`ConfirmAction` 用稳定根元素承接外部间距,避免多根弹窗组件吞掉 `class`。
|
||||
- 右上角提供浅色、暗黑、跟随系统三种模式,默认跟随系统,选择保存在本机浏览器。存储被禁用时仍可在当前会话切换。
|
||||
- 采用微信风格配色:浅色为浅灰背景与白色内容,暗色为近黑背景与深灰内容;绿色用于主操作、选中态和进度。按钮文字、目录文字与主绿色分别配置以保证对比度,错误/警告仍使用独立语义色。Naive UI 和业务内容共用主题,浏览器主题色随之切换;加载前应用偏好,减少主题闪烁。
|
||||
- 全站采用低边框、直角样式:面板、提示框、标签、图片卡片和表单用背景层次代替静态描边;表格与长记录列表使用交替底色,抽屉用灰底承托内容。保留输入框聚焦/校验反馈、复选框、选中指示和必要的浮层阴影;开关、加载等功能图形不强制改形状。
|
||||
- 窄屏默认收起侧栏,目录与正文改用紧凑布局;执行记录仍可查看。长弹窗在自身内部滚动。
|
||||
|
||||
布局样式集中在 `src/admin.css`,业务排版保留在 `src/styles.css`。新增页面应使用 `WorkspacePage`(内容优先页开启 `compact`),低频操作使用 `WorkspaceTools`;避免通过 `body` 或 `window.scrollTo` 管理滚动。
|
||||
|
||||
+1
-2
@@ -84,7 +84,6 @@ const menuOptions = computed<MenuOption[]>(() => [
|
||||
<a href="#main-content" class="skip-link">跳到主要内容</a>
|
||||
<NLayout has-sider class="admin-layout">
|
||||
<NLayoutSider
|
||||
bordered
|
||||
collapse-mode="width"
|
||||
:collapsed="collapsed"
|
||||
:width="208"
|
||||
@@ -106,7 +105,7 @@ const menuOptions = computed<MenuOption[]>(() => [
|
||||
/>
|
||||
</NLayoutSider>
|
||||
<NLayout class="admin-main">
|
||||
<NLayoutHeader bordered class="admin-topbar">
|
||||
<NLayoutHeader class="admin-topbar">
|
||||
<div class="flex min-w-0 items-center gap-3">
|
||||
<NButton
|
||||
quaternary
|
||||
|
||||
+26
-15
@@ -5,6 +5,8 @@
|
||||
--app-body: #ededed;
|
||||
--app-surface: #ffffff;
|
||||
--app-subtle: #f7f7f7;
|
||||
--app-control: #f0f0f0;
|
||||
--app-control-hover: #e5e5e5;
|
||||
--app-border: #e5e5e5;
|
||||
--app-inverse: #ffffff;
|
||||
--app-accent: #07c160;
|
||||
@@ -22,6 +24,8 @@
|
||||
--app-body: #111111;
|
||||
--app-surface: #191919;
|
||||
--app-subtle: #232323;
|
||||
--app-control: #2b2b2b;
|
||||
--app-control-hover: #353535;
|
||||
--app-border: #333333;
|
||||
--app-inverse: #111111;
|
||||
--app-accent-text: #5cd693;
|
||||
@@ -100,6 +104,21 @@ body {
|
||||
padding: 0 20px;
|
||||
gap: 12px;
|
||||
}
|
||||
/* 图标按钮保留原有方形点击热区;仅取消 circle 属性产生的圆形外观。 */
|
||||
.n-button {
|
||||
border-radius: 0;
|
||||
}
|
||||
.admin-sider .n-menu-divider {
|
||||
height: 12px;
|
||||
background: transparent;
|
||||
}
|
||||
.workspace-tools-drawer .n-drawer-header,
|
||||
.workspace-tools-drawer .n-drawer-footer {
|
||||
background: var(--app-subtle);
|
||||
}
|
||||
.workspace-tools-drawer .n-drawer-body {
|
||||
background: var(--app-body);
|
||||
}
|
||||
.theme-select {
|
||||
width: 120px;
|
||||
}
|
||||
@@ -121,7 +140,6 @@ body {
|
||||
flex-shrink: 0;
|
||||
padding: 9px 20px;
|
||||
background: var(--app-surface);
|
||||
border-bottom: 1px solid var(--app-border);
|
||||
}
|
||||
.project-header .n-page-header__main,
|
||||
.project-header .n-page-header__title {
|
||||
@@ -267,8 +285,7 @@ body {
|
||||
gap: 8px 16px;
|
||||
padding: 7px 12px;
|
||||
flex-shrink: 0;
|
||||
border: 1px solid var(--app-border);
|
||||
border-radius: 4px;
|
||||
border-radius: 0;
|
||||
background: var(--app-subtle);
|
||||
font-size: 12px;
|
||||
}
|
||||
@@ -446,7 +463,6 @@ body {
|
||||
gap: 10px;
|
||||
padding: 0 12px 12px;
|
||||
flex-shrink: 0;
|
||||
border-bottom: 1px solid var(--app-border);
|
||||
}
|
||||
.directory-filters > * {
|
||||
min-width: 0;
|
||||
@@ -470,8 +486,7 @@ body {
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
padding: 10px 14px;
|
||||
border-block: 1px solid var(--app-border);
|
||||
background: var(--app-subtle);
|
||||
background: var(--app-control);
|
||||
font:
|
||||
600 11px/1.6 ui-monospace,
|
||||
monospace;
|
||||
@@ -533,13 +548,12 @@ body {
|
||||
flex: 1;
|
||||
}
|
||||
.identity-subject-list-content {
|
||||
gap: 0;
|
||||
gap: 2px;
|
||||
padding: 0 0 8px;
|
||||
}
|
||||
.n-button.identity-subject-item {
|
||||
min-height: 86px;
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid var(--app-border);
|
||||
}
|
||||
.n-button.identity-subject-item:not(.selected) {
|
||||
background: var(--app-surface);
|
||||
@@ -616,8 +630,8 @@ body {
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 1px 5px;
|
||||
border: 1px solid var(--app-border);
|
||||
border-radius: 3px;
|
||||
border-radius: 0;
|
||||
background: var(--app-control);
|
||||
}
|
||||
.directory-status svg {
|
||||
flex-shrink: 0;
|
||||
@@ -746,7 +760,6 @@ body {
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
background: var(--app-subtle);
|
||||
border-right: 1px solid var(--app-border);
|
||||
}
|
||||
.reader-directory-heading {
|
||||
display: flex;
|
||||
@@ -827,7 +840,6 @@ body {
|
||||
}
|
||||
.reader-chapter + .reader-chapter {
|
||||
margin-top: 48px;
|
||||
border-top: 1px solid var(--app-border);
|
||||
padding-top: 36px;
|
||||
}
|
||||
.reader-chapter:last-child {
|
||||
@@ -1026,6 +1038,7 @@ body {
|
||||
height: auto;
|
||||
padding: 6px;
|
||||
white-space: normal;
|
||||
background: var(--app-subtle);
|
||||
}
|
||||
.n-button.image-history-item.selected {
|
||||
background: var(--app-selected);
|
||||
@@ -1066,7 +1079,6 @@ body {
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--app-border);
|
||||
}
|
||||
.n-button.casting-item {
|
||||
width: 100%;
|
||||
@@ -1076,7 +1088,7 @@ body {
|
||||
padding: 12px 14px;
|
||||
white-space: normal;
|
||||
text-align: left;
|
||||
background: var(--app-surface);
|
||||
background: var(--app-subtle);
|
||||
}
|
||||
.n-button.casting-item.selected {
|
||||
background: var(--app-selected);
|
||||
@@ -1223,7 +1235,6 @@ body {
|
||||
.directory-panel {
|
||||
height: 100%;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid var(--app-border);
|
||||
}
|
||||
.directory-heading {
|
||||
padding: 12px 14px 8px;
|
||||
|
||||
@@ -59,8 +59,9 @@ describe('统一目录条目', () => {
|
||||
expect(css).toMatch(/\.directory-item-title\s*\{[^}]*overflow-wrap:\s*anywhere/)
|
||||
expect(css).toMatch(/\.directory-list-content\s*\{[^}]*flex-direction:\s*row;[^}]*width:\s*max-content/)
|
||||
expect(css).toContain('grid-template-columns: clamp(240px, 22%, 280px) minmax(0, 1fr)')
|
||||
expect(css).toMatch(/\.identity-subject-list-content\s*\{[^}]*gap:\s*0;[^}]*padding:\s*0 0 8px/)
|
||||
expect(css).toMatch(/\.n-button\.identity-subject-item\s*\{[^}]*min-height:\s*86px;[^}]*border-bottom:/)
|
||||
expect(css).toMatch(/\.identity-subject-list-content\s*\{[^}]*gap:\s*2px;[^}]*padding:\s*0 0 8px/)
|
||||
expect(css).toMatch(/\.n-button\.identity-subject-item\s*\{[^}]*min-height:\s*86px;[^}]*padding:\s*10px 14px/)
|
||||
expect(css).not.toMatch(/\.n-button\.identity-subject-item\s*\{[^}]*border-bottom:/)
|
||||
expect(css).toMatch(/\.identity-thumbnail\s*\{[^}]*width:\s*48px;[^}]*height:\s*64px/)
|
||||
expect(css).toMatch(/\.n-button\.identity-subject-item:not\(\.selected\)\s*\{[^}]*var\(--app-surface\)/)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { h } from 'vue'
|
||||
import { mount, type VueWrapper } from '@vue/test-utils'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { NAlert, NButton, NCard, NConfigProvider, NInput, NSelect, NTable, NTag } from 'naive-ui'
|
||||
import { readFileSync, readdirSync } from 'node:fs'
|
||||
import { useTheme } from '../../composables/useTheme'
|
||||
|
||||
let wrapper: VueWrapper | undefined
|
||||
afterEach(() => {
|
||||
wrapper?.unmount()
|
||||
wrapper = undefined
|
||||
localStorage.clear()
|
||||
delete document.documentElement.dataset.theme
|
||||
document.documentElement.style.colorScheme = ''
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
describe('全站平面主题', () => {
|
||||
it.each([false, true])('模式 %s 的真实控件无静态描边、使用直角,并保留聚焦与错误反馈', dark => {
|
||||
localStorage.setItem('drama-studio-theme', dark ? 'dark' : 'light')
|
||||
vi.stubGlobal('matchMedia', () => ({ matches: dark, addEventListener() {}, removeEventListener() {} }))
|
||||
wrapper = mount({
|
||||
setup() {
|
||||
const { theme, overrides } = useTheme()
|
||||
return () =>
|
||||
h(NConfigProvider, { theme: theme.value, themeOverrides: overrides.value }, () => [
|
||||
h(NButton, {}, () => '生成'),
|
||||
h(NInput, { class: 'normal-input' }),
|
||||
h(NInput, { status: 'error', class: 'error-input' }),
|
||||
h(NSelect, { options: [{ label: '第一集', value: 1 }] }),
|
||||
h(NAlert, { type: 'error' }, () => '生成失败'),
|
||||
h(NCard, {}, () => '内容'),
|
||||
h(NTag, {}, () => '待设计'),
|
||||
h(NTable, { striped: true }, () => h('tbody', [h('tr', [h('td', '记录')])]))
|
||||
])
|
||||
}
|
||||
})
|
||||
const value = (selector: string, name: string) =>
|
||||
(wrapper!.get(selector).element as HTMLElement).style.getPropertyValue(name)
|
||||
const control = dark ? '#2b2b2b' : '#f0f0f0'
|
||||
for (const selector of ['.n-button', '.normal-input', '.n-base-selection', '.n-alert', '.n-card', '.n-tag']) {
|
||||
expect(value(selector, '--n-border-radius')).toBe('0px')
|
||||
}
|
||||
for (const selector of ['.n-button', '.normal-input', '.n-base-selection']) {
|
||||
expect(value(selector, '--n-border')).toBe('none')
|
||||
expect(value(selector, '--n-color')).toBe(control)
|
||||
expect(value(selector, '--n-border-focus')).toContain(dark ? '#5cd693' : '#087c42')
|
||||
}
|
||||
expect(value('.n-alert', '--n-border')).toBe('none')
|
||||
expect(value('.n-tag', '--n-border')).toBe('none')
|
||||
expect(value('.n-card', '--n-border-color')).toBe('transparent')
|
||||
expect(value('.n-table', '--n-border-color')).toBe('transparent')
|
||||
expect(value('.error-input', '--n-border-error')).toContain(dark ? '#fa7373' : '#a93232')
|
||||
expect(wrapper.get('.error-input').classes()).toContain('n-input--error-status')
|
||||
// 不全局抹掉边框色:未勾选复选框和错误态继续由 Naive 的主题处理。
|
||||
const theme = wrapper.getComponent(NConfigProvider).props('themeOverrides')!
|
||||
expect(theme.common?.borderColor).not.toBe('transparent')
|
||||
expect(theme.Checkbox).not.toHaveProperty('border')
|
||||
expect(theme.Tabs?.barColor).toBe('#07c160')
|
||||
})
|
||||
|
||||
it('业务面板、列表分组和标签以背景区分,保留键盘焦点与选中指示', () => {
|
||||
// happy-dom 不计算布局;此项保护 CSS 契约,不能替代浏览器视觉验收。
|
||||
const admin = readFileSync('src/admin.css', 'utf8')
|
||||
const css = readFileSync('src/styles.css', 'utf8')
|
||||
expect(admin + css).not.toMatch(/border-radius:\s*[1-9]/)
|
||||
expect(admin + css).not.toMatch(/border(?:-[\w]+)?:\s*1px (?:solid|dashed) var\(--(?:app-border|color-line)\)/)
|
||||
expect(css).toMatch(/\.surface-inset\s*\{\s*background:\s*var\(--app-subtle\)/)
|
||||
expect(css).toMatch(/\.record-list > article:nth-child\(even\)\s*\{\s*background:/)
|
||||
expect(admin).toMatch(/\.directory-group-heading\s*\{[^}]*background:\s*var\(--app-control\)/)
|
||||
expect(admin).toMatch(/\.directory-status\s*\{[^}]*background:\s*var\(--app-control\)/)
|
||||
expect(admin).toMatch(/\.n-button\.directory-item:focus-visible\s*\{[^}]*outline:\s*2px/)
|
||||
expect(admin).toMatch(
|
||||
/\.n-button\.directory-item\.selected\s*\{[^}]*var\(--app-selected\)[^}]*var\(--app-accent\)/
|
||||
)
|
||||
})
|
||||
|
||||
it('所有页面不再通过工具类添加装饰边框或圆角', () => {
|
||||
const files = readdirSync('src', { recursive: true, encoding: 'utf8' }).filter(path => path.endsWith('.vue'))
|
||||
for (const path of files) {
|
||||
const source = readFileSync(`src/${path}`, 'utf8')
|
||||
for (const match of source.matchAll(/\bclass="([^"]*)"/g)) {
|
||||
expect(match[1]).not.toMatch(
|
||||
/(?:^|\s)(?:rounded(?:-[\w-]+)?|border|border-[trbl](?:-\d+)?|divide-[xy])(?:\s|$)/
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -89,7 +89,7 @@ describe('微信风格明暗主题', () => {
|
||||
|
||||
it.each([false, true])('明暗模式 %s 的组件表面、语义色与业务 CSS 一致,主要文字保持可读', dark => {
|
||||
const state = setup(dark)
|
||||
const { common, Menu, Button, Layout } = state.overrides.value
|
||||
const { common, Menu, Button, Layout, Input, Select } = state.overrides.value
|
||||
const css = readFileSync('src/admin.css', 'utf8')
|
||||
const lightTokens = css.match(/:root\s*\{([^}]+)\}/)![1]!
|
||||
const darkTokens = css.match(/:root\[data-theme='dark'\]\s*\{([^}]+)\}/)![1]!
|
||||
@@ -119,8 +119,13 @@ describe('微信风格明暗主题', () => {
|
||||
itemColorActiveCollapsed: tokens.selected,
|
||||
itemTextColorActive: tokens['accent-text']
|
||||
})
|
||||
// 圆角调整只用于导航,表单、普通按钮和弹窗仍保留原有主题。
|
||||
expect(common?.borderRadius).toBe('6px')
|
||||
// 全站直角;输入区域与普通按钮共享底色,保持明暗模式一致。
|
||||
expect(common?.borderRadius).toBe('0px')
|
||||
expect(common?.borderRadiusSmall).toBe('0px')
|
||||
expect(Input?.color).toBe(tokens.control)
|
||||
expect(Button?.color).toBe(tokens.control)
|
||||
expect(Button?.colorHover).toBe(tokens['control-hover'])
|
||||
expect(Select?.peers?.InternalSelection?.color).toBe(tokens.control)
|
||||
expect(Button?.textColorPrimary).toBe(tokens['on-accent'])
|
||||
for (const background of [common!.primaryColor!, common!.primaryColorHover!, common!.primaryColorPressed!]) {
|
||||
expect(contrast(String(Button!.textColorPrimary), background)).toBeGreaterThanOrEqual(4.5)
|
||||
|
||||
@@ -39,6 +39,18 @@ export function useTheme() {
|
||||
const info = dark ? '#91a8d2' : '#576b95'
|
||||
const danger = dark ? '#fa7373' : '#a93232'
|
||||
const warning = dark ? '#e8b66a' : '#916019'
|
||||
const control = dark ? '#2b2b2b' : '#f0f0f0'
|
||||
const controlHover = dark ? '#353535' : '#e5e5e5'
|
||||
// 静态边界交给底色;聚焦与错误边框仍由控件的状态主题负责。
|
||||
const focusBorder = `1px solid ${accentText}`
|
||||
const selection = {
|
||||
color: control,
|
||||
colorActive: control,
|
||||
border: 'none',
|
||||
borderHover: 'none',
|
||||
borderFocus: focusBorder,
|
||||
borderActive: focusBorder
|
||||
}
|
||||
return {
|
||||
common: {
|
||||
primaryColor: primary,
|
||||
@@ -61,7 +73,8 @@ export function useTheme() {
|
||||
warningColorHover: warning,
|
||||
warningColorPressed: warning,
|
||||
warningColorSuppl: warning,
|
||||
borderRadius: '6px',
|
||||
borderRadius: '0px',
|
||||
borderRadiusSmall: '0px',
|
||||
bodyColor: body,
|
||||
cardColor: surface,
|
||||
modalColor: surface,
|
||||
@@ -80,6 +93,15 @@ export function useTheme() {
|
||||
"-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif"
|
||||
},
|
||||
Button: {
|
||||
color: control,
|
||||
colorHover: controlHover,
|
||||
colorPressed: controlHover,
|
||||
colorFocus: controlHover,
|
||||
border: 'none',
|
||||
borderHover: 'none',
|
||||
borderPressed: 'none',
|
||||
borderDisabled: 'none',
|
||||
borderFocus: focusBorder,
|
||||
textColorPrimary: onPrimary,
|
||||
textColorHoverPrimary: onPrimary,
|
||||
textColorPressedPrimary: onPrimary,
|
||||
@@ -88,6 +110,55 @@ export function useTheme() {
|
||||
textColorTextPrimary: accentText,
|
||||
textColorGhostPrimary: accentText
|
||||
},
|
||||
Input: {
|
||||
color: control,
|
||||
colorHover: controlHover,
|
||||
colorFocus: control,
|
||||
border: 'none',
|
||||
borderHover: 'none',
|
||||
borderDisabled: 'none',
|
||||
borderFocus: focusBorder,
|
||||
groupLabelBorder: 'none'
|
||||
},
|
||||
Select: { peers: { InternalSelection: selection } },
|
||||
Card: { borderColor: 'transparent' },
|
||||
Drawer: { headerBorderBottom: 'none', footerBorderTop: 'none' },
|
||||
Alert: {
|
||||
border: 'none',
|
||||
borderInfo: 'none',
|
||||
borderSuccess: 'none',
|
||||
borderWarning: 'none',
|
||||
borderError: 'none'
|
||||
},
|
||||
Collapse: { dividerColor: 'transparent' },
|
||||
Table: {
|
||||
borderColor: 'transparent',
|
||||
borderColorModal: 'transparent',
|
||||
borderColorPopover: 'transparent',
|
||||
thColor: control,
|
||||
thColorModal: control,
|
||||
thColorPopover: control,
|
||||
tdColorStriped: subtle,
|
||||
tdColorStripedModal: subtle,
|
||||
tdColorStripedPopover: subtle
|
||||
},
|
||||
DataTable: {
|
||||
borderColor: 'transparent',
|
||||
borderColorModal: 'transparent',
|
||||
borderColorPopover: 'transparent',
|
||||
thColor: control,
|
||||
tdColorStriped: subtle,
|
||||
tdColorHover: selected
|
||||
},
|
||||
Radio: {
|
||||
buttonBorderColor: 'transparent',
|
||||
buttonBorderColorActive: 'transparent',
|
||||
buttonBorderColorHover: 'transparent',
|
||||
buttonColor: control,
|
||||
buttonColorActive: selected,
|
||||
buttonTextColorActive: accentText,
|
||||
buttonBoxShadowFocus: `inset 0 0 0 1px ${accentText}`
|
||||
},
|
||||
Layout: {
|
||||
color: body,
|
||||
headerColor: surface,
|
||||
@@ -104,12 +175,25 @@ export function useTheme() {
|
||||
itemIconColorActive: accentText,
|
||||
itemIconColorActiveHover: accentText
|
||||
},
|
||||
Tabs: { tabTextColorActiveLine: accentText, tabTextColorHoverLine: accentText, barColor: primary },
|
||||
Tag: { textColorPrimary: accentText },
|
||||
Tabs: {
|
||||
tabTextColorActiveLine: accentText,
|
||||
tabTextColorHoverLine: accentText,
|
||||
barColor: primary,
|
||||
tabBorderColor: 'transparent'
|
||||
},
|
||||
Tag: {
|
||||
textColorPrimary: accentText,
|
||||
border: 'none',
|
||||
borderPrimary: 'none',
|
||||
borderInfo: 'none',
|
||||
borderSuccess: 'none',
|
||||
borderWarning: 'none',
|
||||
borderError: 'none'
|
||||
},
|
||||
Checkbox: { checkMarkColor: onPrimary },
|
||||
Switch: { railColorActive: primary },
|
||||
Progress: { fillColor: primary }
|
||||
}
|
||||
} satisfies GlobalThemeOverrides
|
||||
})
|
||||
/** 系统切换只更新 system 模式下的实际显示。 */
|
||||
function syncSystem(event: MediaQueryListEvent) {
|
||||
|
||||
@@ -393,7 +393,7 @@ function exportResult() {
|
||||
</div>
|
||||
<div v-if="tab === 'tasks'" class="p-5">
|
||||
<NScrollbar x-scrollable v-if="snapshot?.tasks?.length" class="table-scroll">
|
||||
<NTable :single-line="false" class="project-table"
|
||||
<NTable :bordered="false" :single-line="true" striped class="project-table"
|
||||
><thead>
|
||||
<tr>
|
||||
<th>分组</th>
|
||||
|
||||
@@ -53,7 +53,7 @@ const purposeLabels: Record<string, string> = {
|
||||
{{ episode?.beatShots.reduce((sum, beat) => sum + beat.shots.length, 0) ?? 0 }} 个镜头</span
|
||||
>
|
||||
</div>
|
||||
<div class="mb-7 border-l-2 border-accent pl-4">
|
||||
<div class="mb-7 surface-inset p-4">
|
||||
<p class="font-medium">{{ plan.storyGoal }}</p>
|
||||
<p class="mt-2 text-sm leading-6 text-muted">{{ plan.emotionalArc }}</p>
|
||||
</div>
|
||||
|
||||
@@ -30,7 +30,7 @@ const filtered = computed(() =>
|
||||
></NInput>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="filtered.length" class="divide-y divide-line">
|
||||
<div v-if="filtered.length" class="record-list">
|
||||
<article v-for="subject in filtered" :key="subject.profileId" class="py-5 first:pt-0">
|
||||
<div class="mb-3 flex flex-wrap items-center gap-3">
|
||||
<h3 class="font-semibold">{{ subject.name }}</h3>
|
||||
@@ -66,7 +66,7 @@ const filtered = computed(() =>
|
||||
<div
|
||||
v-for="form in forms.filter(item => item.profileId === subject.profileId)"
|
||||
:key="form.formId"
|
||||
class="mt-3 border-l-2 border-line pl-4"
|
||||
class="mt-3 surface-inset p-4"
|
||||
>
|
||||
<p class="text-sm font-medium">
|
||||
{{ form.formName || form.name
|
||||
|
||||
@@ -148,7 +148,7 @@ function exportScript() {
|
||||
</div>
|
||||
<NScrollbar v-if="tab !== 'episodes'" class="panel-scroll"
|
||||
><div v-if="tab === 'characters'" class="p-6 lg:p-8">
|
||||
<div v-if="project?.characters.length" class="divide-y divide-line">
|
||||
<div v-if="project?.characters.length" class="record-list">
|
||||
<article
|
||||
v-for="character in project.characters"
|
||||
:key="character.id"
|
||||
@@ -208,11 +208,7 @@ function exportScript() {
|
||||
</div>
|
||||
<div v-if="tab === 'review'" class="p-6 lg:p-8">
|
||||
<div v-if="project?.reviews.length" class="space-y-5">
|
||||
<article
|
||||
v-for="review in project.reviews"
|
||||
:key="review.id"
|
||||
class="border-b border-line pb-5"
|
||||
>
|
||||
<article v-for="review in project.reviews" :key="review.id" class="pb-5">
|
||||
<div class="mb-3 flex justify-between gap-3">
|
||||
<span
|
||||
class="text-sm font-medium"
|
||||
|
||||
@@ -464,7 +464,7 @@ watch(
|
||||
</nav>
|
||||
<article class="production-detail min-w-0">
|
||||
<NScrollbar class="panel-scroll" content-class="p-5 lg:p-7">
|
||||
<header class="mb-5 border-b border-line pb-5">
|
||||
<header class="mb-5 pb-5">
|
||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<p class="eyebrow">BEAT {{ shot.beatNo }} / SHOT {{ shot.shotNo }}</p>
|
||||
|
||||
@@ -401,7 +401,7 @@ async function inspectSpecs() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<NCollapse v-if="keyframeSpec || videoSpec" class="mt-5 border-t border-line pt-4 text-xs"
|
||||
<NCollapse v-if="keyframeSpec || videoSpec" class="mt-5 pt-4 text-xs"
|
||||
><NCollapseItem name="details"
|
||||
><template #header>本镜确定性生成规格</template>
|
||||
<div class="mt-4 grid gap-5 lg:grid-cols-2">
|
||||
|
||||
@@ -100,6 +100,9 @@ function clearFilters() {
|
||||
<div class="project-table-area">
|
||||
<NDataTable
|
||||
class="project-data-table"
|
||||
:bordered="false"
|
||||
:bottom-bordered="false"
|
||||
striped
|
||||
:columns="columns"
|
||||
:data="filtered"
|
||||
:row-key="row => row.id"
|
||||
|
||||
@@ -271,7 +271,7 @@ watch(
|
||||
修改或覆盖上游设计后,后端不会自动使旧状态、旧提示词失效;请按顺序重生成下游内容。自动修复次数仅用于
|
||||
VisualState。
|
||||
</p>
|
||||
<NCollapse class="mt-3 border-t border-line pt-3"
|
||||
<NCollapse class="mt-3 pt-3"
|
||||
><NCollapseItem name="details"
|
||||
><template #header>视频提示词批量操作</template>
|
||||
<p class="my-3 text-xs leading-6 text-muted">
|
||||
|
||||
@@ -43,7 +43,7 @@ function exportReceipt() {
|
||||
{{ receipt.result.skippedEpisodes }} · 失败 {{ receipt.result.failedEpisodes }}
|
||||
</p>
|
||||
<NScrollbar x-scrollable class="table-scroll">
|
||||
<NTable :single-line="false" class="project-table"
|
||||
<NTable :bordered="false" :single-line="true" striped class="project-table"
|
||||
><thead>
|
||||
<tr>
|
||||
<th>剧集</th>
|
||||
|
||||
@@ -40,7 +40,7 @@ defineProps<{ shot: DesignedShot }>()
|
||||
>
|
||||
<p v-else class="text-sm text-muted">此镜头尚无已保存的导演设计。</p>
|
||||
</section>
|
||||
<section class="border-t border-line pt-6">
|
||||
<section class="pt-6">
|
||||
<h3 class="mb-4 text-sm font-semibold">
|
||||
即时视觉状态 <span class="ml-2 font-mono text-[10px] font-normal text-muted">VisualState</span>
|
||||
</h3>
|
||||
@@ -60,7 +60,7 @@ defineProps<{ shot: DesignedShot }>()
|
||||
<article
|
||||
v-for="subject in shot.visualState.subjects"
|
||||
:key="subject.subjectId"
|
||||
class="mt-5 border-l-2 border-line pl-4"
|
||||
class="mt-5 surface-inset p-4"
|
||||
>
|
||||
<div class="mb-3 flex flex-wrap items-center gap-2">
|
||||
<code class="subject-ref">{{ subject.subjectRef }}</code
|
||||
|
||||
@@ -152,7 +152,7 @@ function exportResult(kind: 'spec' | 'prompt') {
|
||||
</p>
|
||||
</template>
|
||||
</section>
|
||||
<section class="border-t border-line pt-6">
|
||||
<section class="pt-6">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<h3 class="text-sm font-semibold">镜头生成规格</h3>
|
||||
<NButton
|
||||
@@ -179,7 +179,7 @@ function exportResult(kind: 'spec' | 'prompt') {
|
||||
</NScrollbar>
|
||||
</template>
|
||||
</section>
|
||||
<section class="border-t border-line pt-6">
|
||||
<section class="pt-6">
|
||||
<h3 class="text-sm font-semibold">视频提示词</h3>
|
||||
<p class="mt-2 text-xs leading-6 text-muted">
|
||||
已有提示词可直接复用,没有则依据 GenerationSpec 与参考图调用模型生成。此处只处理
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('Beat 两级镜头目录', () => {
|
||||
// happy-dom 不计算吸顶位置,保护 CSS 边界,实际滚动仍需浏览器验收。
|
||||
const css = readFileSync('src/admin.css', 'utf8')
|
||||
expect(css).toMatch(
|
||||
/\.directory-group-heading\s*\{[^}]*position:\s*sticky;[^}]*top:\s*0;[^}]*background:\s*var\(--app-subtle\)/
|
||||
/\.directory-group-heading\s*\{[^}]*position:\s*sticky;[^}]*top:\s*0;[^}]*background:\s*var\(--app-control\)/
|
||||
)
|
||||
expect(css).toMatch(/\.directory-mobile-beat\s*\{\s*display:\s*none/)
|
||||
const mobile = css.slice(css.lastIndexOf('@media (max-width: 760px)'))
|
||||
|
||||
@@ -250,7 +250,7 @@ watch(
|
||||
<dd>{{ casting.ready }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div class="mt-5 flex flex-wrap items-end gap-4 border-t border-line pt-4">
|
||||
<div class="mt-5 flex flex-wrap items-end gap-4 pt-4">
|
||||
<label class="w-28">
|
||||
<span class="field-label">并发数</span>
|
||||
<NInputNumber
|
||||
@@ -543,12 +543,12 @@ watch(
|
||||
@save="save"
|
||||
@dirty="dirty = $event"
|
||||
/>
|
||||
<div v-if="anchor" class="mt-5 flex items-center gap-4 border-t border-line pt-5">
|
||||
<div v-if="anchor" class="mt-5 flex items-center gap-4 pt-5">
|
||||
<AssetImage
|
||||
:src="anchor.imageUrl"
|
||||
:alt="`${subject.name}当前身份母版`"
|
||||
preview
|
||||
class="h-20 w-20 shrink-0 rounded"
|
||||
class="h-20 w-20 shrink-0"
|
||||
/>
|
||||
<div>
|
||||
<p class="text-sm font-medium">当前身份母版</p>
|
||||
@@ -575,7 +575,7 @@ watch(
|
||||
@anchor="chooseAnchor"
|
||||
/>
|
||||
</template>
|
||||
<div class="mt-5 border-t border-line pt-5">
|
||||
<div class="mt-5 pt-5">
|
||||
<h4 class="text-xs font-medium">此主体的形态</h4>
|
||||
<div class="mt-3 flex flex-wrap gap-2">
|
||||
<NTag v-for="form in subject.forms" :key="form.id" size="small" :bordered="false"
|
||||
|
||||
@@ -47,7 +47,7 @@ function chooseAnchor() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="mt-5 border-t border-line pt-5" aria-label="身份参考图库">
|
||||
<section class="mt-5 pt-5" aria-label="身份参考图库">
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<h3 class="text-sm font-medium">身份参考图 · {{ images.length }}</h3>
|
||||
<NTag size="small" :bordered="false">{{ anchor ? '已选母版' : '尚无母版' }}</NTag>
|
||||
|
||||
@@ -158,7 +158,7 @@ function resetFilters() {
|
||||
@confirm="generateStale"
|
||||
/></div
|
||||
></NAlert>
|
||||
<NCollapse class="mt-4 border-t border-line pt-4"
|
||||
<NCollapse class="mt-4 pt-4"
|
||||
><NCollapseItem name="details"
|
||||
><template #header>项目批量生图</template>
|
||||
<div class="mt-4 flex flex-wrap items-end gap-4">
|
||||
|
||||
@@ -80,7 +80,7 @@ function remove(image: VisualStyleImage) {
|
||||
请填写有效图片地址和整数排序。
|
||||
</p>
|
||||
<div v-if="images.length" class="mt-5 grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<article v-for="image in images" :key="image.id" class="min-w-0 rounded border border-line p-3">
|
||||
<article v-for="image in images" :key="image.id" class="surface-inset min-w-0 p-3">
|
||||
<AssetImage
|
||||
:src="image.imageUrl"
|
||||
:alt="`${categories[image.category]}风格参考图`"
|
||||
|
||||
@@ -37,7 +37,7 @@ const history = computed(() => workflowCheckpoints(props.checkpoints, props.work
|
||||
>
|
||||
</NTimelineItem>
|
||||
</NTimeline>
|
||||
<p class="mt-5 border-t border-line pt-4 text-[11px] leading-5 text-muted">
|
||||
<p class="mt-5 pt-4 text-[11px] leading-5 text-muted">
|
||||
记录在节点或批次结束后更新,长时间无新记录不一定意味着任务失败。
|
||||
</p>
|
||||
</NScrollbar>
|
||||
|
||||
+48
-61
@@ -80,7 +80,7 @@
|
||||
}
|
||||
.casting-stats div {
|
||||
padding: 10px 12px;
|
||||
border-radius: 4px;
|
||||
border-radius: 0;
|
||||
background: var(--app-subtle);
|
||||
}
|
||||
.casting-stats dt {
|
||||
@@ -143,7 +143,7 @@
|
||||
padding: 9px 14px;
|
||||
min-height: 36px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 5px;
|
||||
border-radius: 0;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
@@ -154,19 +154,16 @@
|
||||
.button-primary {
|
||||
background: var(--app-accent);
|
||||
color: var(--app-on-accent);
|
||||
border-color: var(--app-accent);
|
||||
}
|
||||
.button-primary:hover:not(:disabled) {
|
||||
background: var(--app-accent-hover);
|
||||
}
|
||||
.button-secondary {
|
||||
background: var(--app-surface);
|
||||
border-color: var(--app-border);
|
||||
background: var(--app-control);
|
||||
color: var(--app-ink);
|
||||
}
|
||||
.button-secondary:hover:not(:disabled) {
|
||||
background: var(--app-subtle);
|
||||
border-color: var(--app-border);
|
||||
background: var(--app-control-hover);
|
||||
}
|
||||
|
||||
.text-button {
|
||||
@@ -182,8 +179,18 @@
|
||||
}
|
||||
.panel {
|
||||
background: var(--app-surface);
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 7px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* 次级信息用低对比底色成组,避免用边框把工作台切成碎片。 */
|
||||
.surface-inset {
|
||||
background: var(--app-subtle);
|
||||
}
|
||||
.record-list > article {
|
||||
padding: 20px;
|
||||
}
|
||||
.record-list > article:nth-child(even) {
|
||||
background: var(--app-subtle);
|
||||
}
|
||||
|
||||
.search-field {
|
||||
@@ -191,8 +198,7 @@
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: var(--app-surface);
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 5px;
|
||||
border-radius: 0;
|
||||
padding: 7px 10px;
|
||||
color: var(--color-muted);
|
||||
}
|
||||
@@ -204,8 +210,9 @@
|
||||
width: 145px;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
.search-field:focus-within {
|
||||
border-color: var(--color-accent);
|
||||
.search-field:has(> input):focus-within {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.table-scroll {
|
||||
overflow: hidden;
|
||||
@@ -218,22 +225,23 @@
|
||||
}
|
||||
.project-table th {
|
||||
padding: 13px 20px;
|
||||
background: var(--app-surface);
|
||||
background: var(--app-control);
|
||||
color: var(--color-muted);
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
border-bottom: 1px solid var(--color-line);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.project-table td {
|
||||
padding: 21px 20px;
|
||||
border-bottom: 1px solid var(--app-subtle);
|
||||
}
|
||||
.project-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
.project-table tbody tr:hover {
|
||||
background: var(--app-surface);
|
||||
.project-table tbody tr:nth-child(even) td {
|
||||
background: var(--app-subtle);
|
||||
}
|
||||
.project-table tbody tr:hover td {
|
||||
background: var(--app-selected);
|
||||
}
|
||||
|
||||
.production-controls {
|
||||
@@ -256,8 +264,7 @@
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
flex-shrink: 0;
|
||||
border: 1px solid var(--app-border);
|
||||
border-radius: 5px;
|
||||
border-radius: 0;
|
||||
background: var(--app-subtle);
|
||||
color: var(--app-ink);
|
||||
}
|
||||
@@ -269,7 +276,7 @@
|
||||
.production-stats div,
|
||||
.production-status-grid div {
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
border-radius: 0;
|
||||
background: var(--app-subtle);
|
||||
}
|
||||
.production-stats dt,
|
||||
@@ -297,13 +304,11 @@
|
||||
.production-shot-list {
|
||||
max-height: 760px;
|
||||
overflow: hidden;
|
||||
border-right: 1px solid var(--color-line);
|
||||
background: var(--app-surface);
|
||||
padding-block: 8px;
|
||||
}
|
||||
.production-stage {
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid var(--color-line);
|
||||
}
|
||||
.production-stage:first-of-type {
|
||||
border-top: none;
|
||||
@@ -321,9 +326,8 @@
|
||||
}
|
||||
.keyframe-card {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 5px;
|
||||
background: var(--app-surface);
|
||||
border-radius: 0;
|
||||
background: var(--app-subtle);
|
||||
}
|
||||
.keyframe-card .asset-image {
|
||||
aspect-ratio: 16 / 9;
|
||||
@@ -332,9 +336,8 @@
|
||||
.video-record {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 5px;
|
||||
background: var(--app-surface);
|
||||
border-radius: 0;
|
||||
background: var(--app-subtle);
|
||||
}
|
||||
.production-video {
|
||||
width: min(44%, 390px);
|
||||
@@ -361,7 +364,6 @@
|
||||
max-height: none;
|
||||
overflow: hidden;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--color-line);
|
||||
}
|
||||
.production-status-grid,
|
||||
.keyframe-grid {
|
||||
@@ -389,7 +391,6 @@
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--color-line);
|
||||
}
|
||||
.field-label {
|
||||
display: block;
|
||||
@@ -400,10 +401,9 @@
|
||||
}
|
||||
.input {
|
||||
width: 100%;
|
||||
background: var(--app-surface);
|
||||
background: var(--app-control);
|
||||
color: var(--color-ink);
|
||||
border: 1px solid var(--app-border);
|
||||
border-radius: 5px;
|
||||
border-radius: 0;
|
||||
padding: 9px 11px;
|
||||
font-size: 12px;
|
||||
line-height: 1.7;
|
||||
@@ -417,8 +417,7 @@
|
||||
}
|
||||
.alert {
|
||||
padding: 12px 15px;
|
||||
border: 1px solid var(--app-border);
|
||||
border-radius: 5px;
|
||||
border-radius: 0;
|
||||
background: var(--app-subtle);
|
||||
color: var(--app-ink);
|
||||
font-size: 12px;
|
||||
@@ -426,9 +425,8 @@
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.alert-error {
|
||||
border-color: var(--app-border);
|
||||
background: var(--app-subtle);
|
||||
color: var(--app-ink);
|
||||
color: var(--app-danger);
|
||||
}
|
||||
|
||||
.stage-strip {
|
||||
@@ -460,8 +458,7 @@
|
||||
}
|
||||
.history-panel {
|
||||
padding: 23px 21px;
|
||||
background: var(--app-surface);
|
||||
border-left: 1px solid var(--color-line);
|
||||
background: var(--app-subtle);
|
||||
}
|
||||
|
||||
.form-image-grid {
|
||||
@@ -521,7 +518,6 @@
|
||||
gap: 20px;
|
||||
padding-top: 20px;
|
||||
margin-top: 20px;
|
||||
border-top: 1px solid var(--color-line);
|
||||
}
|
||||
.generation-row > div:last-child {
|
||||
flex-shrink: 0;
|
||||
@@ -534,7 +530,6 @@
|
||||
.shot-list {
|
||||
max-height: 1000px;
|
||||
overflow: hidden;
|
||||
border-right: 1px solid var(--color-line);
|
||||
background: var(--app-surface);
|
||||
padding: 0 8px 20px;
|
||||
}
|
||||
@@ -544,8 +539,8 @@
|
||||
gap: 14px;
|
||||
}
|
||||
.reference-card {
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 4px;
|
||||
background: var(--app-subtle);
|
||||
border-radius: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.reference-placeholder {
|
||||
@@ -558,7 +553,7 @@
|
||||
}
|
||||
.storyboard-json {
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
border-radius: 0;
|
||||
background: var(--app-subtle);
|
||||
font:
|
||||
11px/1.9 ui-monospace,
|
||||
@@ -570,7 +565,6 @@
|
||||
line-height: 1.9;
|
||||
padding: 17px 0 22px;
|
||||
margin-bottom: 23px;
|
||||
border-bottom: 1px solid var(--color-line);
|
||||
}
|
||||
.script-body {
|
||||
white-space: pre-wrap;
|
||||
@@ -582,7 +576,6 @@
|
||||
.script-notes {
|
||||
margin-top: 36px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--color-line);
|
||||
font-size: 12px;
|
||||
}
|
||||
.script-notes dt {
|
||||
@@ -614,7 +607,7 @@
|
||||
overflow-wrap: anywhere;
|
||||
padding: 16px;
|
||||
background: var(--app-subtle);
|
||||
border-radius: 4px;
|
||||
border-radius: 0;
|
||||
font:
|
||||
11px/1.9 ui-monospace,
|
||||
monospace;
|
||||
@@ -629,34 +622,32 @@
|
||||
|
||||
.group-preview {
|
||||
margin-top: 24px;
|
||||
border-top: 1px solid var(--color-line);
|
||||
padding-top: 20px;
|
||||
}
|
||||
.group-chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
border: 1px solid var(--app-subtle);
|
||||
background: var(--app-surface);
|
||||
border-radius: 4px;
|
||||
background: var(--app-subtle);
|
||||
border-radius: 0;
|
||||
padding: 8px 11px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.recovery-strip {
|
||||
background: var(--app-subtle);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
padding: 17px;
|
||||
border: 1px dashed var(--app-border);
|
||||
border-radius: 5px;
|
||||
border-radius: 0;
|
||||
}
|
||||
.subject-ref {
|
||||
display: inline-flex;
|
||||
background: var(--app-subtle);
|
||||
padding: 3px 6px;
|
||||
border-radius: 3px;
|
||||
border-radius: 0;
|
||||
color: var(--app-ink);
|
||||
font-size: 10px;
|
||||
}
|
||||
@@ -677,7 +668,6 @@
|
||||
.beat-section {
|
||||
margin-top: 25px;
|
||||
padding-top: 23px;
|
||||
border-top: 1px solid var(--color-line);
|
||||
}
|
||||
.beat-heading {
|
||||
display: flex;
|
||||
@@ -694,9 +684,8 @@
|
||||
display: flex;
|
||||
gap: 17px;
|
||||
padding: 17px;
|
||||
border: 1px solid var(--app-subtle);
|
||||
border-radius: 5px;
|
||||
background: var(--app-surface);
|
||||
border-radius: 0;
|
||||
background: var(--app-subtle);
|
||||
}
|
||||
.shot-label {
|
||||
flex-shrink: 0;
|
||||
@@ -737,7 +726,6 @@
|
||||
}
|
||||
.history-panel {
|
||||
border-left: 0;
|
||||
border-top: 1px solid var(--color-line);
|
||||
}
|
||||
|
||||
.breakdown-config {
|
||||
@@ -760,7 +748,6 @@
|
||||
.shot-list {
|
||||
max-height: 240px;
|
||||
border-right: 0;
|
||||
border-bottom: 1px solid var(--color-line);
|
||||
}
|
||||
|
||||
.page-container {
|
||||
|
||||
Reference in New Issue
Block a user