@ash-qw/dsh-theme-prts 2.6.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,166 @@
1
+ import { randomUUID, createHash } from 'node:crypto'
2
+ import { createThemeTransfer, validateThemePackage, ignoredThemePackageFields, describeThemePackage, mergeThemePreferences, THEME_PACKAGE_LIMIT } from '../client/theme-transfer.js'
3
+ import { normalizePreferences } from '../client/preferences.js'
4
+ import { draftView, fault } from './emblems.js'
5
+
6
+ function sessionFiles(agent){
7
+ if(!agent?.session)throw fault(400,'请在 DSH 会话中调用此工具')
8
+ const refs=new Map()
9
+ for(const event of agent.session.snapshotEvents()){
10
+ const message=event.type==='user/message'?event.data:event.type==='tool/result'?event.data.message:null
11
+ if(!message || message.isError)continue
12
+ for(const part of message.content ?? []){
13
+ if(part.type==='file' && typeof part.attachment?.attachmentId==='string')refs.set(part.attachment.attachmentId,part.attachment)
14
+ }
15
+ }
16
+ return [...refs.values()]
17
+ }
18
+ export function transferTools({backgrounds,avatars,emblems,attachments,host,themeVersion}){
19
+ const services={backgrounds,avatars,emblems},previews=new Map()
20
+ let busy=false
21
+ const settings=()=>{
22
+ const service=host.get?.('settings'),ns=host.fiber?.entry?.options?.id
23
+ const descriptor=service?.describe({redactSecrets:true}).find(value=>value.ns===ns)
24
+ if(!descriptor)throw fault(400,'当前宿主无法读取主题配置')
25
+ return {service,ns,...descriptor}
26
+ }
27
+ const revisions=assets=>({settings:settings().revision,...(assets?Object.fromEntries(Object.entries(services).map(([key,value])=>[key,value.snapshot().revision])):{})})
28
+ const verify=expected=>{
29
+ const current=revisions(Object.hasOwn(expected,'backgrounds'))
30
+ if(Object.keys(expected).some(key=>current[key]!==expected[key]))throw fault(409,'主题或素材已变化,请重新预检导入文件')
31
+ }
32
+ const saveFile=async(value,label,signal)=>{
33
+ signal?.throwIfAborted()
34
+ const data=Buffer.from(JSON.stringify(value))
35
+ if(data.length>THEME_PACKAGE_LIMIT)throw fault(400,'主题文件超过 160 MB')
36
+ const name=`prts-${label}-${new Date().toISOString().replace(/[:.]/g,'-')}.prts-theme.json`
37
+ const file=await attachments.saveFile({data,name})
38
+ return file
39
+ }
40
+ const read=async(ref,signal)=>{
41
+ if(ref.bytes>THEME_PACKAGE_LIMIT)throw fault(400,'主题文件超过 160 MB')
42
+ let size=0;const chunks=[]
43
+ for await(const chunk of attachments.readFileStream(ref,signal)){
44
+ signal?.throwIfAborted();size+=chunk.length
45
+ if(size>THEME_PACKAGE_LIMIT)throw fault(400,'主题文件超过 160 MB')
46
+ chunks.push(chunk)
47
+ }
48
+ signal?.throwIfAborted()
49
+ const data=Buffer.concat(chunks)
50
+ let value
51
+ try{value=JSON.parse(data.toString('utf8').replace(/^\uFEFF/,''))}catch{throw fault(400,'无法解析主题 JSON 文件')}
52
+ validateThemePackage(value)
53
+ return {value,hash:createHash('sha256').update(data).digest('hex')}
54
+ }
55
+ const bridge=(expected,onDownload)=>{
56
+ const request=async(path,options={})=>{
57
+ options.signal?.throwIfAborted()
58
+ const route=path.replace('/prts-theme-api/','')
59
+ const [kind,...parts]=route.split('/'),service=services[kind],tail=parts.join('/')
60
+ if(!service)throw Error('未知素材服务')
61
+ const data=options.body===undefined?undefined:JSON.parse(options.body)
62
+ let value
63
+ if(tail==='configuration'){
64
+ if(data===undefined){
65
+ value=service.snapshot()
66
+ if(expected && expected[kind]!==undefined && expected[kind]!==value.revision)throw fault(409,'素材已变化,请重新预检')
67
+ }else value=kind==='emblems'?await service.save(data.configuration,data.revision):await service.save(data)
68
+ }else if(kind==='backgrounds' && tail.startsWith('image/')){
69
+ return new Response(await backgrounds.image(parts[1]),{headers:{'content-type':'image/webp'}})
70
+ }else if(kind==='avatars' && tail.startsWith('edit/'))value=avatars.edit(parts[1])
71
+ else if(kind==='emblems' && tail==='import')value=draftView(await emblems.importAsset(data,options.signal))
72
+ else if(kind==='emblems' && parts[0]==='draft' && parts[2]==='export')value=await emblems.exportAsset(decodeURIComponent(parts[1]))
73
+ else throw Error('未知素材操作')
74
+ return {ok:true,json:async()=>value}
75
+ }
76
+ return createThemeTransfer({
77
+ window:{fetch:request,setTimeout,Blob},themeVersion,onDownload,
78
+ readImage:async(blob,signal)=>{signal?.throwIfAborted();return Buffer.from(await blob.arrayBuffer()).toString('base64')},
79
+ getPreferences:()=>normalizePreferences(settings().value),
80
+ checkExport:()=>settings().revision,
81
+ checkWritable:()=>{
82
+ const state=settings()
83
+ if(!state.service.writable)throw fault(400,'当前宿主无法持久化主题配置')
84
+ if(expected)verify(expected)
85
+ return state.revision
86
+ },
87
+ savePreferences:async(value,revision)=>{
88
+ const state=settings()
89
+ await state.service.update(state.ns,{...value,preferencesInitialized:true},revision)
90
+ },
91
+ backupNotice:'导入前备份已保存为附件,可重新导入恢复。',
92
+ })
93
+ }
94
+ const exclusive=async fn=>{
95
+ if(busy)throw fault(409,'正在处理主题文件,请稍候')
96
+ busy=true
97
+ try{return await fn()}finally{busy=false}
98
+ }
99
+ const prune=()=>{for(const [id,preview] of previews)if(preview.expires<Date.now())previews.delete(id)}
100
+ const output={schema:{type:'object'},render:(_args,value)=>[
101
+ {type:'text',text:JSON.stringify(value)},
102
+ ...[value.file,value.backup].filter(ref=>ref && typeof ref==='object' && typeof ref.attachmentId==='string' && typeof ref.name==='string' && Number.isFinite(ref.bytes)).map(attachment=>({type:'file',attachment})),
103
+ ]}
104
+ return [{
105
+ name:'prts_theme_export',
106
+ description:'按用户要求导出当前 PRTS 主题为可下载的 .prts-theme.json 文件附件,和设置界面导出格式一致。includeAssets 默认为 true,含背景、保护区域、用户/AI 头像原图与裁剪、徽记原图及排列;false 仅导出设置。不会修改主题。不包含会话或凭据。最大 160 MB,素材或配置在导出期间变化会拒绝不一致结果。',
107
+ parameters:{type:'object',additionalProperties:false,properties:{includeAssets:{type:'boolean'}}},output,isConcurrencySafe:()=>false,
108
+ execute:(args,exec)=>exclusive(async()=>{
109
+ sessionFiles(exec.agent)
110
+ if(args.includeAssets!==undefined && typeof args.includeAssets!=='boolean')throw fault(400,'includeAssets 必须是布尔值')
111
+ const value=await bridge().export(args.includeAssets!==false,undefined,exec.signal)
112
+ const file=await saveFile(value,'theme',exec.signal)
113
+ return {exported:true,summary:describeThemePackage(value),file}
114
+ }),
115
+ },{
116
+ name:'prts_theme_import_preview',
117
+ description:'只预检 PRTS 主题文件,不修改主题。未传 attachmentId 时列出当前会话上传或工具返回的 JSON 文件;指定后验证文件并返回 previewId、摘要、将忽略的字段和设置变化。支持本工具导出的文件,也支持上传或由会话文件工具返回的文件附件;不接受任意路径/URL。请向用户说明覆盖范围及 ignoredFields;得到导入授权后才调用 import。预检有效期 15 分钟,仅限当前会话,配置或素材变化后需重新预检。',
118
+ parameters:{type:'object',additionalProperties:false,properties:{attachmentId:{type:'string'}}},output,isConcurrencySafe:()=>false,
119
+ execute:(args,exec)=>exclusive(async()=>{
120
+ const files=sessionFiles(exec.agent);exec.signal?.throwIfAborted();prune()
121
+ if(!args.attachmentId)return {files:files.filter(ref=>/\.json$/i.test(ref.name || '') || /json/.test(ref.mediaType || ''))}
122
+ const ref=files.find(file=>file.attachmentId===args.attachmentId)
123
+ if(!ref)throw fault(404,'当前会话没有此文件,请先上传或通过会话工具返回文件附件')
124
+ const {value,hash}=await read(ref,exec.signal),expected=revisions(Boolean(value.assets))
125
+ const current=normalizePreferences(settings().value),next=mergeThemePreferences(current,value.preferences)
126
+ const previewId=randomUUID(),expires=Date.now()+15*60*1000
127
+ while(previews.size>=20)previews.delete(previews.keys().next().value)
128
+ previews.set(previewId,{ref,hash,expected,session:exec.agent.session,expires})
129
+ return {previewId,expiresAt:new Date(expires).toISOString(),summary:describeThemePackage(value),
130
+ ignoredFields:ignoredThemePackageFields(value),
131
+ changes:Object.entries(next).filter(([key,val])=>val!==current[key]).map(([key,after])=>({key,before:current[key],after})),
132
+ scope:'设置影响当前 profile;完整文件中的素材替换影响同一 DSH 实例。仅配置文件保留现有素材。',
133
+ backupNotice:'导入前会先保存备份附件;失败时尝试回退已确认写入的素材。',
134
+ effects:value.assets?[
135
+ '替换或移除背景会删除旧 WebP 文件;备份附件保留恢复所需数据。',
136
+ '替换或移除头像不保留旧原图;备份附件包含原图和裁剪参数。',
137
+ '每个自定义徽记会生成新的草稿和预览;相同原图按内容哈希复用。',
138
+ '草稿达到约 200 份时,按创建时间清理较旧且未受保护的草稿;正在应用的徽记受保护。反复完整导入会增加草稿。',
139
+ ]:['仅修改设置,保留当前素材。'],
140
+ }
141
+ }),
142
+ },{
143
+ name:'prts_theme_import',
144
+ description:'用户授权后,使用当前会话的 previewId 导入已预检的主题文件。完整文件替换背景、头像、徽记及排列,空素材恢复默认;仅设置文件保留现有素材。替换/移除背景会删除旧 WebP,头像旧原图也不会保留。每个自定义徽记会新增草稿和预览,相同原图按内容哈希复用;草稿达到约 200 份时清理较旧且未受保护的草稿,正在应用的徽记受保护。导入前先保存备份附件,失败或取消时尝试回退素材。配置/素材版本变化、预检过期、文件内容变化会拒绝执行,需要重新预检。返回 applied=false 时必须报告错误和备份,不能宣称成功;部分回退失败时应核对实际状态。导入可能关闭 AI 工具或主题总开关,预检时应告知相应变化。',
145
+ parameters:{type:'object',additionalProperties:false,properties:{previewId:{type:'string'}},required:['previewId']},output,isConcurrencySafe:()=>false,
146
+ execute:(args,exec)=>exclusive(async()=>{
147
+ sessionFiles(exec.agent);exec.signal?.throwIfAborted();prune()
148
+ const preview=previews.get(args.previewId)
149
+ if(!preview || preview.session!==exec.agent.session)throw fault(404,'预检不存在或已过期,请在当前会话重新预检')
150
+ verify(preview.expected)
151
+ const {value,hash}=await read(preview.ref,exec.signal)
152
+ if(hash!==preview.hash)throw fault(409,'文件内容已变化,请重新预检')
153
+ verify(preview.expected)
154
+ previews.delete(args.previewId)
155
+ let backup
156
+ const transfer=bridge(preview.expected,async data=>{backup=await saveFile(data,'before-import',exec.signal)})
157
+ try{
158
+ await transfer.import(value,undefined,exec.signal)
159
+ return {applied:true,summary:describeThemePackage(value),backup}
160
+ }catch(error){
161
+ if(!backup)throw error
162
+ return {applied:false,error:error.message,backup}
163
+ }
164
+ }),
165
+ }]
166
+ }
@@ -1,10 +1,12 @@
1
1
  /* Native cursor; gesture frames override the resting image without a DOM overlay. */
2
2
  @media (any-pointer: fine) {
3
3
  html[data-dsh-prts][data-prts-cursor="prts"] {
4
- --prts-hand-cursor: var(--prts-hand-frame, url("__PRTS_HAND__") 7 6);
4
+ --prts-rest-hand-cursor: url("__PRTS_HAND__") 7 6;
5
+ --prts-hand-cursor: var(--prts-hand-frame, var(--prts-rest-hand-cursor));
5
6
  --prts-text-cursor: var(--prts-text-frame, url("__PRTS_TEXT__") 24 24);
6
7
  --prts-sort-cursor: url("__PRTS_GRABBING__") 22 19, grabbing;
7
- --prts-link-cursor: url("__PRTS_LINK__") 7 7;
8
+ --prts-rest-link-cursor: url("__PRTS_LINK__") 7 7;
9
+ --prts-link-cursor: var(--prts-rest-link-cursor);
8
10
  cursor: var(--prts-hand-cursor), default;
9
11
  }
10
12
  html[data-dsh-prts][data-prts-cursor="prts"] :is(button, a[href], summary, [role="button"], [role="tab"], [role="treeitem"], [role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"], [role="option"], [data-prts-workspace-row], [data-prts-session-row], .dsh-partner-sidebar-row, select, input[type="button"], input[type="submit"], input[type="checkbox"], input[type="radio"], input[type="color"], input[type="range"]) {
@@ -41,3 +43,25 @@ html[data-dsh-prts] [data-prts-region-stage] [data-prts-region-handle] { cursor:
41
43
  html[data-dsh-prts] [data-prts-region-stage] :is([data-prts-region-handle="e"],[data-prts-region-handle="w"]) { cursor:ew-resize!important; }
42
44
  html[data-dsh-prts] [data-prts-region-stage] :is([data-prts-region-handle="nw"],[data-prts-region-handle="se"]) { cursor:nwse-resize!important; }
43
45
  html[data-dsh-prts] [data-prts-region-stage] :is([data-prts-region-handle="ne"],[data-prts-region-handle="sw"]) { cursor:nesw-resize!important; }
46
+
47
+ /* A single renderer owns every in-page cursor state. State classification
48
+ reads mirrored cursor metadata, so hiding never needs to be toggled to read. */
49
+ html[data-dsh-prts][data-prts-follower-visible],
50
+ html[data-dsh-prts][data-prts-follower-visible] :is(*,#prts-cursor-layer-specificity) {
51
+ cursor:none!important;
52
+ }
53
+ .prts-cursor-follower {
54
+ position:fixed;top:0;left:0;width:1px;height:1px;z-index:2147483647;
55
+ pointer-events:none!important;user-select:none;contain:layout style;
56
+ margin:0;padding:0;border:0;background:transparent;overflow:visible;right:auto;bottom:auto;
57
+ }
58
+ .prts-cursor-follower[hidden] { display:none!important; }
59
+ .prts-cursor-follower * { pointer-events:none!important; }
60
+ .prts-cursor-follower img { position:absolute;max-width:none!important;max-height:none!important; }
61
+
62
+ /* Known DSH horizontal resize boundaries must have their native image before
63
+ pointer events fire, including the chat width handle and its hit children. */
64
+ html[data-dsh-prts][data-prts-cursor="prts"] :is([class$="_widthHandle"],[class$="_detailsResizeHandle"],.dsh-ssh-workbench-resizer),
65
+ html[data-dsh-prts][data-prts-cursor="prts"] :is([class$="_widthHandle"],[class$="_detailsResizeHandle"],.dsh-ssh-workbench-resizer) * {
66
+ cursor: url("__PRTS_RESIZE_EW__") 16 16, col-resize;
67
+ }
@@ -520,7 +520,7 @@ html[data-dsh-prts] :is([data-chat-flow-kind],[data-message-role],[data-submissi
520
520
  }
521
521
  &:active{transform:none}
522
522
  }
523
- html[data-dsh-prts] [data-prts-body-target-frame] {
523
+ html[data-dsh-prts] :is([data-prts-body-target-frame],[data-prts-body-target-ghost]) {
524
524
  position:fixed;inset:auto;left:0;top:0;width:0;height:0;margin:0;padding:0;border:0;background:transparent;overflow:visible;z-index:100;pointer-events:none;opacity:0;--prts-corner-width:1px;
525
525
  & > i {
526
526
  position:absolute;left:0;top:0;width:7px;height:7px;will-change:transform;
@@ -321,9 +321,13 @@ padding-block: 9px;
321
321
  }
322
322
  html[data-dsh-prts] [data-prts-settings-common] [data-prts-session-flow-color-workbench] {
323
323
  display: grid;
324
- grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
324
+ grid-template-columns: minmax(0, 1fr);
325
325
  align-items: center;
326
- gap: 10px;
326
+ gap: 12px;
327
+ padding: 12px;
328
+ border: 1px solid var(--prts-line);
329
+ border-radius: 6px;
330
+ background: color-mix(in srgb, var(--prts-panel) 45%, var(--prts-canvas));
327
331
  }
328
332
  html[data-dsh-prts] [data-prts-session-flow-preview] {
329
333
  --prts-lifeline-pink: #f7ddeb;
@@ -357,7 +361,7 @@ display: grid;
357
361
  grid-template-columns: repeat(3, minmax(0, 1fr));
358
362
  gap: 5px;
359
363
  }
360
- html[data-dsh-prts] [data-prts-session-flow-color-inputs] label { min-width: 0; display: grid; grid-template-columns: 34px minmax(34px, 1fr); gap: 4px; align-items: center; color: var(--prts-muted); font: 700 8px/1 var(--prts-mono); }
364
+ html[data-dsh-prts] [data-prts-session-flow-color-inputs] :is(label,[data-prts-page-control-row]) { min-width: 0; display: grid; grid-template-columns: 34px minmax(34px, 1fr); gap: 4px; align-items: center; color: var(--prts-muted); font: 700 8px/1 var(--prts-mono); }
361
365
  html[data-dsh-prts] [data-prts-session-flow-color-inputs] input[type="color"] { width: 100%; height: 28px; padding: 2px; border: 1px solid var(--prts-line); border-radius: 0; background: var(--prts-panel-raised); cursor: pointer; }
362
366
  html[data-dsh-prts] [data-prts-session-flow-color-inputs] output { grid-column: 1 / -1; color: var(--prts-ink); font: 700 8px/1 var(--prts-mono); text-align: center; text-transform: uppercase; }
363
367
  html[data-dsh-prts] [data-prts-theme-settings] button[data-prts-setting-key],
@@ -820,7 +824,7 @@ margin-bottom: 20px;
820
824
  }
821
825
  & [data-prts-custom-emblem] header { display: grid; gap: 6px; }
822
826
  & [data-prts-custom-emblem] :is(small, p) { font: 400 12px/1.6 var(--prts-body); color: var(--prts-muted); margin: 0; }
823
- & [data-prts-custom-emblem] label { display: grid; grid-template-columns: 110px minmax(0, 1fr); align-items: center; gap: 12px; font-size: 13px; }
827
+ & [data-prts-custom-emblem] :is(label,[data-prts-page-control-row]) { display: grid; grid-template-columns: 110px minmax(0, 1fr); align-items: center; gap: 12px; font-size: 13px; }
824
828
  & [data-prts-emblem-editor]{display:grid;gap:14px;}& [data-prts-custom-emblem] :is(input,select){min-width:0;max-width:100%;}& [data-prts-custom-emblem] select{padding:8px;background:var(--prts-panel-raised);color:var(--prts-ink);border:1px solid var(--prts-line);}& [data-prts-emblem-preview]{touch-action:none;cursor:grab;width:min(100%,280px);height:auto;justify-self:center;border:1px solid var(--prts-line);border-radius:4px;}& [data-prts-emblem-actions]{display:flex;flex-wrap:wrap;gap:10px;}& [data-prts-emblem-actions] button{padding:10px 14px;border:1px solid var(--prts-line);border-radius:4px;color:var(--prts-ink);background:var(--prts-panel-raised);}& [data-prts-emblem-actions] button:disabled{opacity:.45;}& [data-prts-custom-emblem] h4{margin:8px 0 12px;font:600 14px/1.5 var(--prts-body);}& [data-prts-emblem-list]{display:grid;gap:10px;list-style:none;padding:0;margin:14px 0;}& :is([data-prts-emblem-list] > li,[data-prts-emblem-hero-row]){display:flex;align-items:center;flex-wrap:wrap;gap:10px;padding:12px;border:1px solid var(--prts-line);border-radius:4px;}& :is([data-prts-emblem-list],[data-prts-emblem-hero-row]) canvas{width:48px;height:48px;flex:none;border-radius:3px;}& :is([data-prts-emblem-list] > li,[data-prts-emblem-hero-row]) > span{flex:1;min-width:100px;overflow-wrap:anywhere;font-size:13px;}& [data-emblem-action]{padding:7px 10px;border:1px solid var(--prts-line);border-radius:4px;background:var(--prts-panel-raised);color:var(--prts-ink);}& [data-emblem-action]:disabled{opacity:.4;}& [data-prts-emblem-reorder]{padding:0;border:0;background:transparent;cursor:grab;}& [data-emblem-key]{touch-action:none;cursor:grab;user-select:none;-webkit-touch-callout:none}& [data-prts-emblem-reorder]:focus-visible{outline:2px solid var(--prts-ink);outline-offset:3px;}& [data-prts-emblem-sorting]{user-select:none;}& [data-prts-emblem-dragging]{opacity:.35;border-style:dashed !important;}& [data-prts-emblem-drag-ghost]{background:var(--prts-panel-raised);box-shadow:0 8px 28px #0005;opacity:.95;}& :is([data-prts-emblem-reset-confirm],[data-prts-emblem-workspace],[data-prts-emblem-discard]){position:fixed;inset:0;margin:auto;width:min(460px,calc(100vw - 32px));max-height:80dvh;box-sizing:border-box;overflow:auto;padding:24px;border:1px solid var(--prts-line);border-radius:8px;background:var(--prts-panel-raised);color:var(--prts-ink);box-shadow:0 16px 64px #0008;font:13px/1.7 var(--prts-body);}& :is([data-prts-emblem-reset-confirm],[data-prts-emblem-workspace],[data-prts-emblem-discard])::backdrop{background:#0008;}& [data-prts-emblem-reset-confirm]{& > strong{display:block;font-size:16px;}& > p{margin:12px 0 18px;}& > [data-prts-emblem-actions]{justify-content:flex-end;padding-top:16px;border-top:1px solid var(--prts-line);}& [data-prts-emblem-reset-cancel]{border:0;background:transparent;}& [data-emblem-action]{display:flex;gap:6px;padding:5px 10px;font-size:12px;background:transparent;}& [data-prts-emblem-reset-status]:empty{display:none;}}& [data-prts-emblem-reset-exports]{display:grid;gap:8px;margin:18px 0;}& [data-prts-emblem-reset-asset]{display:grid;grid-template-columns:36px minmax(0,1fr) auto;gap:12px;align-items:center;padding:12px;border:1px solid var(--prts-line);border-radius:4px;}& [data-prts-emblem-reset-asset] > span{overflow-wrap:anywhere;line-height:1.5;}& [data-prts-emblem-reset-asset] canvas{width:36px;height:36px;border-radius:3px;}& [data-prts-emblem-workspace]{width:min(820px,calc(100vw - 24px));padding:0;max-height:90dvh;}
825
829
  & [data-prts-emblem-edit-header],& [data-prts-emblem-edit-bar]{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:18px 22px;}
826
830
  & [data-prts-emblem-edit-header]{border-bottom:1px solid var(--prts-line);}
@@ -899,8 +903,8 @@ html[data-dsh-prts] [data-prts-preview-settings] {
899
903
  & [data-prts-setting-row] strong { font:600 12px/1.5 var(--prts-body); }
900
904
  & [data-prts-setting-row="previewReplyLimit"] { padding:0 0 14px;gap:12px; }
901
905
  & [data-prts-setting-row="previewReplyLimit"] strong { color:var(--prts-muted);font-size:11px; }
902
- & [data-prts-preview-reply-limit] { min-width:112px;padding:7px 10px;border:1px solid var(--prts-line);border-radius:5px;background:var(--prts-panel-raised);color:var(--prts-ink);font:400 11px/1.5 var(--prts-body); }
903
- & [data-prts-preview-reply-limit]:focus-visible { outline:2px solid var(--prts-ink);outline-offset:2px; }
906
+ & :is([data-prts-preview-reply-limit],[data-prts-cursor-mode]) { min-width:112px;padding:7px 10px;border:1px solid var(--prts-line);border-radius:5px;background:var(--prts-panel-raised);color:var(--prts-ink);font:400 11px/1.5 var(--prts-body); }
907
+ & :is([data-prts-preview-reply-limit],[data-prts-cursor-mode]):focus-visible { outline:2px solid var(--prts-ink);outline-offset:2px; }
904
908
  & [data-prts-preview-details] { padding:0 12px;margin:0 -12px 8px 0;border-radius:5px;background:color-mix(in srgb,var(--prts-ink) 3%,transparent); }
905
909
  & [data-prts-preview-details] [data-prts-setting-row] { padding:10px 0; }
906
910
  & [data-prts-preview-details] [data-prts-setting-row] strong { font-size:11px; }
@@ -1095,3 +1099,131 @@ html[data-dsh-prts] [data-prts-corner-presets] [data-prts-corner-custom][aria-pr
1095
1099
 
1096
1100
  html[data-dsh-prts] [data-prts-corner-custom] [data-prts-corner-preview] { position: relative; }
1097
1101
  html[data-dsh-prts] [data-prts-corner-custom][aria-pressed="true"] [data-prts-corner-preview]::after { content: ""; position: absolute; left: 4px; top: 5px; width: 8px; height: 4px; border-left: 2px solid #172025; border-bottom: 2px solid #172025; transform: rotate(-45deg); }
1102
+
1103
+
1104
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-common] [data-prts-setting-row="cursor"] {
1105
+ grid-template-columns:minmax(0,1fr) auto;gap:6px 16px;
1106
+ }
1107
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-setting-row="cursor"] > span { display:contents; }
1108
+ html[data-dsh-prts] [data-prts-setting-row="cursor"] strong { grid-column:1;grid-row:1; }
1109
+ html[data-dsh-prts] [data-prts-cursor-description] { grid-column:1 / -1;grid-row:2;max-width:58ch; }
1110
+ html[data-dsh-prts] [data-prts-cursor-mode] {
1111
+ grid-column:2;grid-row:1;appearance:none;box-sizing:border-box;
1112
+ width:166px;max-width:100%;height:30px;margin:0;padding:0 28px 0 10px;
1113
+ border:1px solid color-mix(in srgb,var(--prts-line) 65%,transparent);border-radius:4px;
1114
+ background-color:color-mix(in srgb,var(--prts-ink) 3%,var(--prts-panel));
1115
+ background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='m1 1 4 4 4-4' fill='none' stroke='%23869298' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
1116
+ background-repeat:no-repeat;background-position:right 10px center;
1117
+ color:var(--prts-ink);font-family:var(--prts-body)!important;font-size:12px!important;font-weight:400;line-height:1.4;
1118
+ transition:border-color .15s,background-color .15s;
1119
+ }
1120
+ html[data-dsh-prts] [data-prts-cursor-mode]:hover {
1121
+ border-color:color-mix(in srgb,var(--prts-ink) 35%,var(--prts-line));
1122
+ background-color:color-mix(in srgb,var(--prts-ink) 6%,var(--prts-panel));
1123
+ }
1124
+ html[data-dsh-prts] [data-prts-cursor-mode]:focus-visible { outline:1px solid var(--prts-ink);outline-offset:2px; }
1125
+ html[data-dsh-prts] [data-prts-cursor-mode] option { background:var(--prts-panel);color:var(--prts-ink); }
1126
+
1127
+ /* Page-rendered form popups keep the experimental cursor inside the document. */
1128
+ html[data-dsh-prts] [data-prts-native-model] { display: none !important; }
1129
+ html[data-dsh-prts] .prts-page-control {
1130
+ display: inline-flex; align-items: center; justify-content: space-between; gap: 14px;
1131
+ box-sizing: border-box; min-width: 0; max-width: 100%; height: 30px; padding: 0 10px;
1132
+ border: 1px solid var(--prts-line); border-radius: 5px;
1133
+ background: var(--prts-panel-raised); color: var(--prts-ink);
1134
+ font: 400 12px/1.4 var(--prts-body) !important; cursor: pointer;
1135
+ }
1136
+ html[data-dsh-prts] .prts-page-control[role="combobox"]::after { content: ''; width: 5px; height: 5px; flex: 0 0 5px; border-right: 1px solid currentColor; border-bottom: 1px solid currentColor; transform: rotate(45deg) translateY(-2px); opacity: .65; }
1137
+ html[data-dsh-prts] .prts-page-control:hover,
1138
+ html[data-dsh-prts] .prts-page-control[aria-expanded="true"] { border-color: var(--prts-muted); background: var(--prts-panel); }
1139
+ html[data-dsh-prts] .prts-page-control:focus-visible,
1140
+ html[data-dsh-prts] .prts-page-popup button:focus-visible { outline: 1px solid var(--prts-yellow); outline-offset: 2px; }
1141
+ html[data-dsh-prts] .prts-page-control:disabled { opacity: .45; cursor: not-allowed; }
1142
+ html[data-dsh-prts] .prts-page-color { min-width: 0; width: 100%; justify-content: flex-start; gap: 8px; }
1143
+ html[data-dsh-prts] .prts-page-color::before { content: ''; width: 100%; height: 16px; border-radius: 3px; background: var(--sample); border: 1px solid #ffffff40; }
1144
+ html[data-dsh-prts] .prts-page-popup {
1145
+ position: fixed; inset: auto; margin: 0; box-sizing: border-box;
1146
+ max-width: calc(100vw - 16px); max-height: min(360px, calc(100vh - 16px)); overflow: auto;
1147
+ padding: 5px; border: 1px solid var(--prts-line); border-radius: 7px;
1148
+ color: var(--prts-ink); background: var(--prts-panel-raised);
1149
+ box-shadow: 0 10px 32px #0005; font: 12px/1.5 var(--prts-font, sans-serif);
1150
+ }
1151
+ html[data-dsh-prts] .prts-page-popup [role="option"] {
1152
+ display: flex; justify-content: space-between; align-items: center; gap: 18px;
1153
+ width: 100%; min-height: 30px; padding: 6px 9px; border: 0; border-radius: 4px;
1154
+ background: transparent; color: inherit; font: 400 12px/1.5 var(--prts-body) !important; box-sizing: border-box; text-align: left; cursor: pointer;
1155
+ }
1156
+ html[data-dsh-prts] .prts-page-popup [role="option"]:is(:hover,:focus-visible) { background: color-mix(in srgb, var(--prts-ink) 9%, transparent); }
1157
+ html[data-dsh-prts] .prts-page-popup [aria-selected="true"]::after { content: '✓'; color: var(--prts-yellow); }
1158
+ html[data-dsh-prts] .prts-page-color-editor { width: 250px; padding: 14px; }
1159
+ html[data-dsh-prts] .prts-page-color-editor label { display: flex; align-items: center; gap: 9px; margin-top: 12px; }
1160
+ html[data-dsh-prts] .prts-page-color-editor input { min-width: 0; flex: 1; width: 100%; }
1161
+ html[data-dsh-prts] .prts-page-color-editor input:not([type]) { padding: 5px 8px; border: 1px solid var(--prts-line); background: var(--prts-panel); color: inherit; }
1162
+ html[data-dsh-prts] .prts-page-color-editor output { min-width: 24px; text-align: right; }
1163
+ html[data-dsh-prts] .prts-page-sample { display: block; height: 32px; margin-top: 10px; border-radius: 4px; }
1164
+ html[data-dsh-prts] .prts-page-confirm { box-sizing: border-box; max-height: calc(100dvh - 32px); overflow: auto; width: min(340px, calc(100vw - 40px)); padding: 22px; border: 1px solid var(--prts-line); border-radius: 8px; color: var(--prts-ink); background: var(--prts-panel-raised); }
1165
+ html[data-dsh-prts] .prts-page-confirm::backdrop { background: #0006; }
1166
+ html[data-dsh-prts] .prts-page-confirm p { margin: 12px 0 20px; font-size: 13px; color: var(--prts-muted); }
1167
+ html[data-dsh-prts] :is(.prts-page-confirm,.prts-page-color-editor) footer { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
1168
+ html[data-dsh-prts] :is(.prts-page-confirm,.prts-page-color-editor) footer button { padding: 6px 12px; border: 1px solid var(--prts-line); border-radius: 4px; background: var(--prts-panel); color: var(--prts-ink); font: inherit; cursor: pointer; }
1169
+ html[data-dsh-prts] :is(.prts-page-confirm,.prts-page-color-editor) footer button:hover { border-color: var(--prts-muted); }
1170
+ html[data-dsh-prts] .prts-page-confirm [data-confirm] { color: #ee9292; }
1171
+
1172
+
1173
+ html[data-dsh-prts][data-prts-cursor="prts"] [data-prts-page-control-row] {
1174
+ cursor: var(--prts-hand-cursor), default;
1175
+ }
1176
+ html[data-dsh-prts] [data-prts-setting-row="cursor"] > .prts-page-control {
1177
+ grid-column: 2; grid-row: 1; width: 208px; justify-self: end;
1178
+ }
1179
+
1180
+ html[data-dsh-prts] .prts-page-caption { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1181
+ html[data-dsh-prts] .prts-page-color .prts-page-caption { display: none; }
1182
+ html[data-dsh-prts] .prts-page-color-editor :is(input,button,output) { font: 400 12px/1.5 var(--prts-body) !important; }
1183
+ html[data-dsh-prts] .prts-page-color-editor input[aria-invalid="true"] { border-color: var(--prts-red); }
1184
+ html[data-dsh-prts] .prts-page-color-editor button:disabled { opacity: .45; cursor: not-allowed; }
1185
+
1186
+
1187
+ /* Three equal color cards: one hit area for the swatch, layer and value. */
1188
+ html[data-dsh-prts] [data-prts-session-flow-color-inputs] { gap: 8px; }
1189
+ html[data-dsh-prts] [data-prts-session-flow-color-inputs] [data-prts-page-control-row] { display: block; min-width: 0; }
1190
+ html[data-dsh-prts] [data-prts-session-flow-color-inputs] .prts-page-color {
1191
+ display: flex; align-items: center; gap: 9px; height: 56px; padding: 8px;
1192
+ background: color-mix(in srgb, var(--prts-panel-raised) 55%, var(--prts-canvas));
1193
+ border: 1px solid var(--prts-line); border-radius: 5px;
1194
+ transition: background-color .15s, border-color .15s;
1195
+ }
1196
+ html[data-dsh-prts] [data-prts-session-flow-color-inputs] .prts-page-color::before {
1197
+ flex: 0 0 18px; width: 18px; height: 32px; border: 0; border-radius: 3px;
1198
+ box-shadow: inset 0 0 0 1px #ffffff20;
1199
+ }
1200
+ html[data-dsh-prts] [data-prts-session-flow-color-inputs] .prts-page-color-meta {
1201
+ display: flex; min-width: 0; flex-direction: column; align-items: flex-start; gap: 5px;
1202
+ }
1203
+ html[data-dsh-prts] [data-prts-session-flow-color-inputs] .prts-page-color-meta > span {
1204
+ color: var(--prts-muted); font: 400 11px/1 var(--prts-body);
1205
+ }
1206
+ html[data-dsh-prts] [data-prts-session-flow-color-inputs] .prts-page-color-meta > output {
1207
+ color: var(--prts-ink); font: 500 9px/1 var(--prts-mono); text-align: left; white-space: nowrap;
1208
+ }
1209
+ html[data-dsh-prts] [data-prts-session-flow-color-inputs] .prts-page-color:is(:hover,[aria-expanded="true"]) {
1210
+ border-color: color-mix(in srgb, var(--sample) 55%, var(--prts-line));
1211
+ background: color-mix(in srgb, var(--sample) 7%, var(--prts-panel));
1212
+ }
1213
+
1214
+ html[data-dsh-prts] [data-prts-session-flow-color-workbench] > [data-prts-session-flow-preview] {
1215
+ box-sizing: border-box; height: 42px; width: 100%; border: 0; background: transparent;
1216
+ }
1217
+
1218
+ html[data-dsh-prts] [data-prts-settings-common] [data-prts-session-flow-color-workbench] { gap: 8px; }
1219
+
1220
+ html[data-dsh-prts] [data-prts-settings-common] [data-prts-session-flow-color-workbench] { box-sizing: border-box; width: 100%; max-width: 520px; justify-self: start; }
1221
+
1222
+ html[data-dsh-prts] .prts-page-tooltip {
1223
+ position: fixed; inset: auto; margin: 0; box-sizing: border-box;
1224
+ width: 280px; max-width: calc(100vw - 16px); padding: 11px 13px;
1225
+ border: 1px solid var(--prts-line-strong); border-radius: 5px;
1226
+ color: var(--prts-ink); background: var(--prts-panel-raised);
1227
+ box-shadow: 0 8px 24px #0005; font: 400 12px/1.7 var(--prts-body);
1228
+ pointer-events: none;
1229
+ }
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) Cuberto, Ltd.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.