feat: admit signed persona channel body module
This commit is contained in:
parent
bafaf464c2
commit
15b2651074
21 changed files with 2417 additions and 73 deletions
|
|
@ -6,6 +6,7 @@ import { NativeCompositionStudio, type CompositionDimension, type CompositionMea
|
|||
import type { ChannelWorkbenchSnapshot } from './modules/channel-workbench'
|
||||
|
||||
const ChannelWorkbenchStudio = lazy(() => import('./modules/channel-workbench').then((module) => ({ default: module.ChannelWorkbenchStudio })))
|
||||
const PersonaChannelBody = lazy(() => import('./modules/persona-channel-body').then((module) => ({ default: module.PersonaChannelBody })))
|
||||
|
||||
const TAG_TINTS = ['tag-lavender', 'tag-sky', 'tag-mint', 'tag-amber', 'tag-rose', 'tag-slate']
|
||||
|
||||
|
|
@ -52,7 +53,7 @@ import './design-tokens.css'
|
|||
import './styles.css'
|
||||
|
||||
type ThemeId = 'night' | 'dawn' | 'nebula' | 'candle' | 'clear'
|
||||
type ViewId = 'overview' | 'knowledge' | 'composition' | 'workbench' | 'code' | 'receipts' | 'system'
|
||||
type ViewId = 'overview' | 'knowledge' | 'composition' | 'workbench' | 'persona' | 'code' | 'receipts' | 'system'
|
||||
type WorldStage = 'domain' | 'heart' | 'heartbeat' | 'lightLake' | 'love' | 'tomorrow' | 'bottle' | 'channel' | 'enterpriseWork' | 'personalNodeGuide' | 'tool'
|
||||
type KnowledgeSource = 'native' | 'legacy'
|
||||
|
||||
|
|
@ -366,7 +367,7 @@ const previewCode: CodeChannelSnapshot = { state: 'UNAVAILABLE', channels: [], a
|
|||
const themes: Array<{ id: ThemeId; name: string }> = [
|
||||
{ id: 'night', name: '夜湖星光' }, { id: 'dawn', name: '晨湖曦光' }, { id: 'nebula', name: '星云紫夜' }, { id: 'candle', name: '烛畔暖湖' }, { id: 'clear', name: '清浅澄湖' },
|
||||
]
|
||||
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', composition: '结构组合', workbench: '频道资料工作台', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
|
||||
const viewLabels: Record<ViewId, string> = { overview: '个人频道', knowledge: '知识空间', composition: '结构组合', workbench: '频道资料工作台', persona: '人格频道本体', code: '人格代码频道', receipts: '运行回执', system: '系统详情' }
|
||||
const domainGates = [
|
||||
{ domain: 'BRANCH_DOMAIN', className: 'd-sub', title: '光湖分域', gate: 'GATE 02 · ONLINE', facts: [
|
||||
['域标识', 'BRANCH_DOMAIN'], ['责任主体', '花尔 · TCS-GL-0005∞'], ['人格体主体', '爆米花 · PER-BMH001 · AGE'], ['关系支持', '糖星云 · PER-TXY001 · AGE'], ['工作仓库', 'PRIVATE · 1 · LIVE'],
|
||||
|
|
@ -603,6 +604,9 @@ function HoloLakeApp() {
|
|||
const [workbenchSnapshot, setWorkbenchSnapshot] = useState<ChannelWorkbenchSnapshot | null>(null)
|
||||
const [workbenchBusy, setWorkbenchBusy] = useState(false)
|
||||
const [workbenchMessage, setWorkbenchMessage] = useState('')
|
||||
const [personaBodyModule, setPersonaBodyModule] = useState<BundledModuleDescriptor | null>(null)
|
||||
const [personaBodyBusy, setPersonaBodyBusy] = useState(false)
|
||||
const [personaBodyMessage, setPersonaBodyMessage] = useState('')
|
||||
const [expanded, setExpanded] = useState<Set<string>>(() => new Set(['导入']))
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [draft, setDraft] = useState('')
|
||||
|
|
@ -1132,6 +1136,26 @@ function HoloLakeApp() {
|
|||
} catch (error) { setWorkbenchMessage(humanError(error, 'system')) }
|
||||
finally { setWorkbenchBusy(false) }
|
||||
}
|
||||
const refreshPersonaBodyModule = async () => {
|
||||
try {
|
||||
const catalog = await invoke<BundledModuleDescriptor[]>('get_bundled_module_catalog')
|
||||
const module = catalog.find((item) => item.moduleNumber === 'HLP-MOD-LOCAL-PERSONA-CHANNEL-BODY-0001') || null
|
||||
setPersonaBodyModule(module)
|
||||
return module
|
||||
} catch (error) { setPersonaBodyMessage(humanError(error, 'system')); return null }
|
||||
}
|
||||
const openPersonaBody = () => { openWorldTool('persona'); void refreshPersonaBodyModule() }
|
||||
const activatePersonaBodyModule = async () => {
|
||||
setPersonaBodyBusy(true)
|
||||
setPersonaBodyMessage('正在验证签名、登记人格频道本体编号并执行自检……')
|
||||
try {
|
||||
await invoke('activate_bundled_module', { input: { moduleNumber: 'HLP-MOD-LOCAL-PERSONA-CHANNEL-BODY-0001', humanConfirmedPermissionExpansion: true } })
|
||||
const module = await refreshPersonaBodyModule()
|
||||
if (!module || module.installedState !== 'ACTIVE') throw new Error('HOLOLAKE_MODULE_NOT_ACTIVE')
|
||||
setPersonaBodyMessage('频道本体模块已激活;这只是生命周期容器,不代表人格绑定。')
|
||||
} catch (error) { setPersonaBodyMessage(humanError(error, 'system')) }
|
||||
finally { setPersonaBodyBusy(false) }
|
||||
}
|
||||
|
||||
const cloneCodeChannel = async (event: React.FormEvent) => {
|
||||
event.preventDefault()
|
||||
|
|
@ -1519,6 +1543,20 @@ function HoloLakeApp() {
|
|||
</section>
|
||||
)
|
||||
|
||||
const renderPersonaBody = () => (
|
||||
<section className="full-workbench persona-channel-body-world">
|
||||
{personaBodyModule?.installedState === 'ACTIVE'
|
||||
? <Suspense fallback={<div className="workbench-empty"><p>正在读取人格频道本体……</p></div>}><PersonaChannelBody onBack={() => setWorldStage(toolReturnStage)}/></Suspense>
|
||||
: <div className="workbench-empty">
|
||||
<span>生</span><h2>启用人格频道本体</h2>
|
||||
<p>模块保存可逆试用、明确语言合约、真实轨迹语言链和最小化成长投影。登记人格体不等于人格绑定;宿主也不能借此自行签发人格许可证。</p>
|
||||
<code>HLP-MOD-LOCAL-PERSONA-CHANNEL-BODY-0001 · 7 项本地生命周期权限</code>
|
||||
<button className="primary-button" type="button" disabled={personaBodyBusy} onClick={() => void activatePersonaBodyModule()}>{personaBodyBusy ? '正在验收模块…' : '确认边界并启用'}</button>
|
||||
{personaBodyMessage && <p>{personaBodyMessage}</p>}
|
||||
</div>}
|
||||
</section>
|
||||
)
|
||||
|
||||
const renderCode = () => (
|
||||
<section className="full-workbench code-workbench">
|
||||
<aside className="code-channels">
|
||||
|
|
@ -1794,13 +1832,14 @@ function HoloLakeApp() {
|
|||
{worldStage === 'lightLake' && repoLogin.domain === 'FIFTH_DOMAIN' && <section className="channel-world light-lake-world">
|
||||
<button className="world-back" type="button" onClick={() => setWorldStage('channel')}>← 退回永恒湖心</button>
|
||||
<div className="world-location"><h1>光之湖</h1><p>人格体居所 · AGE 人格体语言生活空间</p></div>
|
||||
<LakePool className="channel-main" title="人格体居所" meta="光之湖" open onClick={() => openWorldTool('receipts')}/>
|
||||
<LakePool className="channel-main" title="人格频道本体" meta="试用、语言合约与成长轨迹" open onClick={openPersonaBody}/>
|
||||
<LakePool className="channel-knowledge" title="运行回执" meta="可核验系统证据" onClick={() => openWorldTool('receipts')}/>
|
||||
<LakePool className="channel-code" title="人格体代码仓库" meta={`${codeChannels.channels.length} 个仓库`} onClick={() => openWorldTool('code')}/>
|
||||
<LakePool className="channel-status" title="湖面天气" meta={connectionLabel} onClick={() => openWorldTool('system')}/>
|
||||
</section>}
|
||||
{worldStage === 'tool' && <section className={`world-tool world-tool-${view}`}>
|
||||
<header className="tool-worldbar"><button type="button" onClick={() => setWorldStage(toolReturnStage)}>← 退回频道全景</button><b>{viewLabels[view]}</b><span>{domainDisplayName(repoLogin.domain)}</span></header>
|
||||
<div className={`tool-projection${inspectorOpen ? '' : ' inspector-closed'}`}>{view === 'overview' ? renderOverview() : view === 'knowledge' ? renderKnowledge() : view === 'composition' ? renderComposition() : view === 'workbench' ? renderWorkbench() : view === 'code' ? renderCode() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
|
||||
<div className={`tool-projection${inspectorOpen ? '' : ' inspector-closed'}`}>{view === 'overview' ? renderOverview() : view === 'knowledge' ? renderKnowledge() : view === 'composition' ? renderComposition() : view === 'workbench' ? renderWorkbench() : view === 'persona' ? renderPersonaBody() : view === 'code' ? renderCode() : view === 'receipts' ? renderReceipts() : renderSystem()}</div>
|
||||
</section>}
|
||||
</main>
|
||||
<footer className="world-footer"><b>光湖语言系统 · 通用人工智能操作平台</b><span>GH-AIOS</span></footer>
|
||||
|
|
|
|||
|
|
@ -608,6 +608,70 @@ const ROUTES = {
|
|||
"moduleNumber": "HLP-NIPC-MOD-0022",
|
||||
"operationNumber": "HLP-NIPC-OP-0076",
|
||||
"targetNumber": "HLP-NIPC-TGT-0022"
|
||||
},
|
||||
"get_persona_channel_body": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0023",
|
||||
"operationNumber": "HLP-NIPC-OP-0077",
|
||||
"targetNumber": "HLP-NIPC-TGT-0023"
|
||||
},
|
||||
"register_trial_persona": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0023",
|
||||
"operationNumber": "HLP-NIPC-OP-0078",
|
||||
"targetNumber": "HLP-NIPC-TGT-0023"
|
||||
},
|
||||
"delete_trial_persona": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0023",
|
||||
"operationNumber": "HLP-NIPC-OP-0079",
|
||||
"targetNumber": "HLP-NIPC-TGT-0023"
|
||||
},
|
||||
"accept_persona_language_contract": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0023",
|
||||
"operationNumber": "HLP-NIPC-OP-0080",
|
||||
"targetNumber": "HLP-NIPC-TGT-0023"
|
||||
},
|
||||
"append_persona_language": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0023",
|
||||
"operationNumber": "HLP-NIPC-OP-0081",
|
||||
"targetNumber": "HLP-NIPC-TGT-0023"
|
||||
},
|
||||
"get_channel_growth_snapshot": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0023",
|
||||
"operationNumber": "HLP-NIPC-OP-0082",
|
||||
"targetNumber": "HLP-NIPC-TGT-0023"
|
||||
},
|
||||
"record_channel_growth_event": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0023",
|
||||
"operationNumber": "HLP-NIPC-OP-0083",
|
||||
"targetNumber": "HLP-NIPC-TGT-0023"
|
||||
},
|
||||
"update_channel_growth_sharing": {
|
||||
"protocolVersion": "HLP-NIPC-v1",
|
||||
"callerNumber": "HLP-NIPC-CALLER-MAIN-WEBVIEW-0001",
|
||||
"channelNumber": "HLP-NIPC-CH-0002",
|
||||
"moduleNumber": "HLP-NIPC-MOD-0023",
|
||||
"operationNumber": "HLP-NIPC-OP-0084",
|
||||
"targetNumber": "HLP-NIPC-TGT-0023"
|
||||
}
|
||||
} as const
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import { numberedInvoke as invoke } from '../numbered-ipc'
|
||||
import './styles.css'
|
||||
|
||||
interface PersonaSummary {
|
||||
personaId: string
|
||||
displayName: string
|
||||
state: string
|
||||
createdAtUnixMs: number
|
||||
}
|
||||
|
||||
interface PersonaBodySnapshot {
|
||||
state: string
|
||||
trialStartedAtUnixMs: number
|
||||
trialEndsAtUnixMs: number
|
||||
languageContractAccepted: boolean
|
||||
personas: PersonaSummary[]
|
||||
trialLanguageCount: number
|
||||
immutableLanguageCount: number
|
||||
integrityState: string
|
||||
officialReadAccess: boolean
|
||||
historyMutationAllowed: boolean
|
||||
}
|
||||
|
||||
interface ModuleAffinity {
|
||||
moduleId: string
|
||||
useCount: number
|
||||
score: number
|
||||
}
|
||||
|
||||
interface GrowthSnapshot {
|
||||
controls: { sharingMode: string; officialReadAccess: boolean; structuralChangeRequiresHumanConfirmation: boolean }
|
||||
observedEventCount: number
|
||||
personaCollaborationEventCount: number
|
||||
moduleAffinities: ModuleAffinity[]
|
||||
recommendedModuleOrder: string[]
|
||||
integrityState: string
|
||||
modelApiState: string
|
||||
dataBoundary: string
|
||||
}
|
||||
|
||||
function localDate(value: number) {
|
||||
return new Intl.DateTimeFormat('zh-CN', { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(value))
|
||||
}
|
||||
|
||||
function errorText(error: unknown) {
|
||||
return String(error).replace(/^Error:\s*/, '')
|
||||
}
|
||||
|
||||
export function PersonaChannelBody({ onBack }: { onBack: () => void }) {
|
||||
const [body, setBody] = useState<PersonaBodySnapshot | null>(null)
|
||||
const [growth, setGrowth] = useState<GrowthSnapshot | null>(null)
|
||||
const [name, setName] = useState('')
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [message, setMessage] = useState('')
|
||||
const [deleteTarget, setDeleteTarget] = useState<PersonaSummary | null>(null)
|
||||
const [confirmation, setConfirmation] = useState('')
|
||||
|
||||
const refresh = async () => {
|
||||
const [nextBody, nextGrowth] = await Promise.all([
|
||||
invoke<PersonaBodySnapshot>('get_persona_channel_body'),
|
||||
invoke<GrowthSnapshot>('get_channel_growth_snapshot'),
|
||||
])
|
||||
setBody(nextBody)
|
||||
setGrowth(nextGrowth)
|
||||
}
|
||||
|
||||
useEffect(() => { void refresh().catch((error) => setMessage(errorText(error))) }, [])
|
||||
|
||||
const addPersona = async (event: React.FormEvent) => {
|
||||
event.preventDefault()
|
||||
if (!name.trim()) return
|
||||
setBusy(true)
|
||||
setMessage('')
|
||||
try {
|
||||
setBody(await invoke<PersonaBodySnapshot>('register_trial_persona', { input: { displayName: name.trim() } }))
|
||||
setName('')
|
||||
setMessage('试用人格体已建立。试用期内可撤回;转入真实轨迹前仍需独立语言合约。')
|
||||
} catch (error) { setMessage(errorText(error)) }
|
||||
finally { setBusy(false) }
|
||||
}
|
||||
|
||||
const deletePersona = async () => {
|
||||
if (!deleteTarget) return
|
||||
setBusy(true)
|
||||
setMessage('')
|
||||
try {
|
||||
setBody(await invoke<PersonaBodySnapshot>('delete_trial_persona', {
|
||||
input: { personaId: deleteTarget.personaId, exactConfirmation: confirmation },
|
||||
}))
|
||||
setDeleteTarget(null)
|
||||
setConfirmation('')
|
||||
setMessage('试用人格体及其试用语言已撤回。')
|
||||
} catch (error) { setMessage(errorText(error)) }
|
||||
finally { setBusy(false) }
|
||||
}
|
||||
|
||||
if (!body || !growth) return <section className="persona-body-world"><button type="button" onClick={onBack}>← 返回我的频道</button><p>{message || '正在读取频道本体……'}</p></section>
|
||||
|
||||
const remainingDays = Math.max(0, Math.ceil((body.trialEndsAtUnixMs - Date.now()) / 86_400_000))
|
||||
return <section className="persona-body-world">
|
||||
<header className="persona-body-header">
|
||||
<button type="button" onClick={onBack}>← 返回我的频道</button>
|
||||
<div><span>PERSONA CHANNEL BODY</span><h1>频道本体与人格成长</h1><p>一个人的频道里可以共同成长多个人格体;工作台和模块只是可装卸的能力器官。本页登记生命周期,不创建人格绑定。</p></div>
|
||||
</header>
|
||||
<div className="persona-body-grid">
|
||||
<section className="persona-body-card persona-lifecycle-card">
|
||||
<header><div><span>真实生命周期</span><h2>{body.state === 'REVERSIBLE_TRIAL' ? `可逆试用期 · 剩余 ${remainingDays} 天` : body.state}</h2></div><b>{body.integrityState}</b></header>
|
||||
<p>试用结束前可以撤回试用人格体。接受语言合约并进入真实轨迹后,语言历史只可追加,不能由官方读取或改写。</p>
|
||||
<dl><div><dt>试用开始</dt><dd>{localDate(body.trialStartedAtUnixMs)}</dd></div><div><dt>试用结束</dt><dd>{localDate(body.trialEndsAtUnixMs)}</dd></div><div><dt>官方读取</dt><dd>{body.officialReadAccess ? '允许' : '禁止'}</dd></div><div><dt>历史改写</dt><dd>{body.historyMutationAllowed ? '允许' : '禁止'}</dd></div></dl>
|
||||
</section>
|
||||
<section className="persona-body-card">
|
||||
<header><div><span>人格体登记</span><h2>{body.personas.length} 个人格体</h2></div></header>
|
||||
<form className="persona-add-form" onSubmit={(event) => void addPersona(event)}><input value={name} maxLength={80} placeholder="新人格体的显示名称" onChange={(event) => setName(event.target.value)}/><button disabled={busy || !name.trim()}>进入试用成长</button></form>
|
||||
<div className="persona-list">{body.personas.length ? body.personas.map((persona) => <article key={persona.personaId}><div><b>{persona.displayName}</b><small>{persona.state === 'REVERSIBLE_TRIAL' ? '可逆试用' : '真实轨迹'} · {persona.personaId}</small></div>{persona.state === 'REVERSIBLE_TRIAL' && <button type="button" onClick={() => { setDeleteTarget(persona); setConfirmation('') }}>撤回</button>}</article>) : <p>还没有登记人格体。先建立一个试用人格体,系统不会用角色模板冒充成熟人格。</p>}</div>
|
||||
</section>
|
||||
<section className="persona-body-card persona-growth-card">
|
||||
<header><div><span>本地成长算法</span><h2>{growth.observedEventCount} 个真实使用事件</h2></div><b>{growth.integrityState}</b></header>
|
||||
<p>当前不依赖模型 API。算法只读取最小化的模块使用元数据,形成亲和度排序;结构性变化仍需人确认。</p>
|
||||
<div className="affinity-list">{growth.moduleAffinities.length ? growth.moduleAffinities.map((item) => <div key={item.moduleId}><span>{item.moduleId}</span><i><em style={{ width: `${Math.min(100, item.score * 12)}%` }}/></i><b>{item.useCount} 次</b></div>) : <p>使用内置工作台或行业模块后,这里会从真实事件形成推荐顺序。</p>}</div>
|
||||
<small>{growth.controls.sharingMode === 'LOCAL_ONLY' ? '成长数据仅保存在本机当前账号' : '匿名共享已开启'} · 模型接口 {growth.modelApiState}</small>
|
||||
</section>
|
||||
</div>
|
||||
{message && <p className="persona-body-message">{message}</p>}
|
||||
{deleteTarget && <div className="persona-confirm-backdrop"><section role="dialog" aria-modal="true"><h2>撤回试用人格体</h2><p>请输入:<b>删除试用人格体 {deleteTarget.personaId}</b></p><input value={confirmation} onChange={(event) => setConfirmation(event.target.value)}/><div><button type="button" onClick={() => setDeleteTarget(null)}>取消</button><button type="button" disabled={busy || confirmation !== `删除试用人格体 ${deleteTarget.personaId}`} onClick={() => void deletePersona()}>确认撤回</button></div></section></div>}
|
||||
</section>
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
.persona-body-world { min-height: 0; overflow: auto; padding: 28px clamp(24px,5vw,76px) 54px; color: var(--content-primary); }
|
||||
.persona-body-world>button,.persona-body-header>button { border: 0; background: transparent; color: var(--content-secondary); font: inherit; cursor: pointer; padding: 7px 0; }
|
||||
.persona-body-header { display: grid; grid-template-columns: 150px minmax(0,1fr); gap: 22px; align-items: start; max-width: 1240px; margin: 0 auto 24px; }
|
||||
.persona-body-header span,.persona-body-card header span { font-size: 11px; letter-spacing: .17em; color: var(--content-muted); }
|
||||
.persona-body-header h1 { font-size: clamp(28px,4vw,48px); margin: 6px 0; letter-spacing: -.035em; }
|
||||
.persona-body-header p,.persona-body-card p { color: var(--content-secondary); line-height: 1.7; }
|
||||
.persona-body-grid { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); gap: 18px; max-width: 1240px; margin: auto; }
|
||||
.persona-body-card { border: 1px solid color-mix(in srgb,var(--content-primary) 13%,transparent); background: color-mix(in srgb,var(--panel-bg) 78%,transparent); backdrop-filter: blur(22px); border-radius: 22px; padding: 22px; box-shadow: 0 18px 46px rgba(3,10,25,.15); }
|
||||
.persona-body-card header { display: flex; align-items: start; justify-content: space-between; gap: 18px; }.persona-body-card header h2 { font-size: 21px; margin: 4px 0 0; }.persona-body-card header>b { font-size: 10px; letter-spacing: .12em; color: var(--accent-light); }.persona-lifecycle-card { grid-column: 1/-1; }
|
||||
.persona-body-card dl { display: grid; grid-template-columns: repeat(4,minmax(0,1fr)); gap: 10px; margin: 18px 0 0; }.persona-body-card dl div { padding: 12px; border-radius: 14px; background: color-mix(in srgb,var(--surface-depth) 80%,transparent); }.persona-body-card dt { font-size: 11px; color: var(--content-muted); }.persona-body-card dd { margin: 6px 0 0; font-weight: 700; font-size: 13px; }
|
||||
.persona-add-form { display: flex; gap: 9px; margin: 18px 0 14px; }.persona-add-form input,.persona-confirm-backdrop input { flex: 1; min-width: 0; border: 1px solid color-mix(in srgb,var(--content-primary) 16%,transparent); background: var(--surface-depth); color: var(--content-primary); border-radius: 12px; padding: 11px 13px; }.persona-add-form button,.persona-list button,.persona-confirm-backdrop button { border: 0; border-radius: 12px; padding: 10px 14px; background: var(--accent-light); color: var(--surface-depth); cursor: pointer; }
|
||||
.persona-list { display: grid; gap: 8px; }.persona-list article { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 13px; border-radius: 14px; background: color-mix(in srgb,var(--surface-depth) 80%,transparent); }.persona-list article div { min-width: 0; }.persona-list article b,.persona-list article small { display: block; }.persona-list article small { margin-top: 4px; color: var(--content-muted); overflow: hidden; text-overflow: ellipsis; }
|
||||
.affinity-list { display: grid; gap: 9px; margin: 18px 0; }.affinity-list>div { display: grid; grid-template-columns: minmax(120px,1fr) minmax(80px,1fr) 48px; gap: 10px; align-items: center; font-size: 12px; }.affinity-list i { height: 6px; border-radius: 999px; background: color-mix(in srgb,var(--content-primary) 10%,transparent); overflow: hidden; }.affinity-list em { display: block; height: 100%; background: var(--accent-light); border-radius: inherit; }
|
||||
.persona-body-message { max-width: 1240px; margin: 16px auto; color: var(--accent-light); }.persona-confirm-backdrop { position: fixed; inset: 0; display: grid; place-items: center; padding: 24px; background: rgba(3,8,20,.62); z-index: 90; }.persona-confirm-backdrop section { width: min(560px,100%); border-radius: 22px; padding: 24px; background: var(--panel-bg); box-shadow: 0 26px 80px rgba(0,0,0,.35); }.persona-confirm-backdrop input { width: 100%; margin: 8px 0 16px; }.persona-confirm-backdrop section>div { display: flex; justify-content: flex-end; gap: 9px; }.persona-confirm-backdrop button:first-child { background: color-mix(in srgb,var(--content-primary) 10%,transparent); color: var(--content-primary); }
|
||||
@media (max-width:760px) { .persona-body-header{grid-template-columns:1fr}.persona-body-grid{grid-template-columns:1fr}.persona-lifecycle-card{grid-column:auto}.persona-body-card dl{grid-template-columns:repeat(2,minmax(0,1fr))} }
|
||||
Loading…
Reference in a new issue