@ash-qw/dsh-theme-prts 2.2.0 → 2.3.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.
@@ -2,6 +2,7 @@ import { mkdir, readFile, writeFile, rename, rm } from 'node:fs/promises'
2
2
  import { join } from 'node:path'
3
3
  import { randomUUID } from 'node:crypto'
4
4
  import sharp from 'sharp'
5
+ import { validBackgroundRegion } from '../shared/background-region.js'
5
6
  import { body, json, isBase64 } from './api.js'
6
7
  import { fault } from './emblems.js'
7
8
 
@@ -26,8 +27,11 @@ export async function createBackgroundService(directory) {
26
27
  const task = queue.then(async () => {
27
28
  if (!BACKGROUND_SLOTS.includes(data?.slot)) throw fault(400, '背景区域无效')
28
29
  if (data.revision !== state.revision) throw fault(409, '背景已在其他页面更新,请重新操作')
29
- let entry = null, output
30
- if (data.image !== null) {
30
+ const regionOnly = Object.hasOwn(data, 'region') && !Object.hasOwn(data, 'image')
31
+ if (Object.hasOwn(data, 'region') && !validBackgroundRegion(data.region)) throw fault(400, '背景保护区域无效')
32
+ if (regionOnly && !state[data.slot]) throw fault(400, '请先上传背景图片')
33
+ let entry = regionOnly ? { ...state[data.slot], region: data.region } : null, output
34
+ if (!regionOnly && data.image !== null) {
31
35
  if (!isBase64(data.image) || data.image.length > 14 * 1024 * 1024) throw fault(400, '请上传不超过 10 MB 的图片')
32
36
  const bytes = Buffer.from(data.image, 'base64')
33
37
  if (!bytes.length || bytes.length > 10 * 1024 * 1024) throw fault(400, '请上传不超过 10 MB 的图片')
@@ -37,22 +41,22 @@ export async function createBackgroundService(directory) {
37
41
  if (!['png', 'jpeg', 'webp', 'avif'].includes(metadata.format)) throw Error('format')
38
42
  const result = await input.rotate().resize({ width: 3840, height: 2160, fit: 'inside', withoutEnlargement: true }).webp({ quality: 86 }).toBuffer({ resolveWithObject: true })
39
43
  output = result.data
40
- entry = { id: randomUUID(), width: result.info.width, height: result.info.height }
44
+ entry = { id: randomUUID(), width: result.info.width, height: result.info.height, region: data.region ?? null }
41
45
  } catch { throw fault(400, '无法读取图片,请使用 PNG、JPEG、WebP 或 AVIF(最多 4000 万像素)') }
42
46
  }
43
47
  const next = { ...state, revision: state.revision + 1, [data.slot]: entry }
44
48
  const temporary = configurationPath + '.' + randomUUID() + '.tmp'
45
49
  try {
46
- if (entry) await writeFile(join(directory, entry.id + '.webp'), output, { mode: 0o600 })
50
+ if (output) await writeFile(join(directory, entry.id + '.webp'), output, { mode: 0o600 })
47
51
  await writeFile(temporary, JSON.stringify(next), { mode: 0o600 })
48
52
  await rename(temporary, configurationPath)
49
53
  } catch (error) {
50
- if (entry) await rm(join(directory, entry.id + '.webp'), { force: true }).catch(() => {})
54
+ if (output) await rm(join(directory, entry.id + '.webp'), { force: true }).catch(() => {})
51
55
  throw error
52
56
  } finally { await rm(temporary, { force: true }).catch(() => {}) }
53
57
  const previous = state[data.slot]
54
58
  state = next
55
- if (previous) await rm(join(directory, previous.id + '.webp'), { force: true }).catch(() => {})
59
+ if (previous && previous.id !== entry?.id) await rm(join(directory, previous.id + '.webp'), { force: true }).catch(() => {})
56
60
  for (const listener of listeners) { try { listener(state.revision) } catch {} }
57
61
  return snapshot()
58
62
  })
@@ -0,0 +1,21 @@
1
+ // Coordinates are fractions of the oriented, stored image.
2
+ export function validBackgroundRegion(region) {
3
+ return region === null || (region && typeof region === 'object' && !Array.isArray(region)
4
+ && ['x','y','width','height'].every(key => Number.isFinite(region[key]))
5
+ && region.x >= 0 && region.y >= 0 && region.width > 0 && region.height > 0
6
+ && region.x + region.width <= 1.000001 && region.y + region.height <= 1.000001)
7
+ }
8
+ export function backgroundPlacement(imageWidth, imageHeight, width, height, region = null) {
9
+ if (![imageWidth,imageHeight,width,height].every(v => Number.isFinite(v) && v > 0)) return {x:0,y:0,width:0,height:0,clipped:false}
10
+ const scale = Math.max(width / imageWidth, height / imageHeight)
11
+ const w = imageWidth * scale, h = imageHeight * scale
12
+ const clamp = (v,a,b) => Math.max(a,Math.min(b,v))
13
+ const axis = (size, viewport, start, length, vertical = false) => {
14
+ const centered = (viewport-size)/2
15
+ if (!region) return centered
16
+ if (length*size > viewport) return clamp(vertical ? -start*size : viewport/2-(start+length/2)*size,viewport-size,0)
17
+ return clamp(centered, Math.max(viewport-size,-start*size), Math.min(0,viewport-(start+length)*size))
18
+ }
19
+ return {x:axis(w,width,region?.x,region?.width),y:axis(h,height,region?.y,region?.height,true),width:w,height:h,
20
+ clipped:Boolean(region && (region.width*w > width+.01 || region.height*h > height+.01))}
21
+ }
@@ -33,3 +33,11 @@
33
33
  html[data-dsh-prts] :is([data-prts-emblem-sort-active],[data-prts-emblem-sort-active] *) {
34
34
  cursor: var(--prts-sort-cursor, grabbing) !important;
35
35
  }
36
+
37
+ /* The crop surface uses one native cursor family, including during pointer capture. */
38
+ html[data-dsh-prts] [data-prts-region-stage] { cursor:default; }
39
+ html[data-dsh-prts] [data-prts-region-stage] [data-prts-region-box] { cursor:move; }
40
+ html[data-dsh-prts] [data-prts-region-stage] [data-prts-region-handle] { cursor:ns-resize!important; }
41
+ html[data-dsh-prts] [data-prts-region-stage] :is([data-prts-region-handle="e"],[data-prts-region-handle="w"]) { cursor:ew-resize!important; }
42
+ html[data-dsh-prts] [data-prts-region-stage] :is([data-prts-region-handle="nw"],[data-prts-region-handle="se"]) { cursor:nwse-resize!important; }
43
+ html[data-dsh-prts] [data-prts-region-stage] :is([data-prts-region-handle="ne"],[data-prts-region-handle="sw"]) { cursor:nesw-resize!important; }
@@ -1,18 +1,18 @@
1
1
  /* Viewport-aligned wallpaper. */
2
2
  html[data-dsh-prts] { --prts-background-wash: color-mix(in srgb, var(--prts-canvas) var(--prts-background-shade, 45%), transparent); }
3
3
  html[data-dsh-prts][data-prts-background-mode="full"][data-prts-background-full] :is([data-prts-region="frame"], [data-prts-nav-rail]) {
4
- background: linear-gradient(var(--prts-background-wash), var(--prts-background-wash)), var(--prts-background-full) center / cover fixed;
4
+ background: linear-gradient(var(--prts-background-wash), var(--prts-background-wash)), var(--prts-background-full) var(--prts-protected-full-x, 50%) var(--prts-protected-full-y, 50%) / cover fixed;
5
5
  }
6
6
  html[data-dsh-prts][data-prts-background-mode="full"][data-prts-background-full] :is([data-prts-region="sessions"], [data-prts-region="operation"]) { background: transparent; }
7
7
  html[data-dsh-prts][data-prts-background-mode="split"][data-prts-background-sidebar] {
8
- --prts-wall-width:max(calc(var(--prts-sidebar-width,280px) + var(--prts-nav-width)),calc((100dvh - var(--prts-host-top-inset)) * var(--prts-sidebar-image-ratio,1)));
9
- --prts-wall-x:calc((var(--prts-sidebar-width,280px) + var(--prts-nav-width) - var(--prts-wall-width)) / 2);
10
- & :is([data-prts-region="sessions"],[data-prts-nav-rail]){background:linear-gradient(var(--prts-background-wash),var(--prts-background-wash)),var(--prts-background-sidebar) var(--prts-wall-x) center / var(--prts-wall-width) auto no-repeat}
11
- & [data-prts-region="sessions"]{background-position:center,calc(var(--prts-wall-x) - var(--prts-nav-width)) center}
8
+ --prts-wall-width:var(--prts-protected-sidebar-width,max(calc(var(--prts-sidebar-width,280px) + var(--prts-nav-width)),calc((100dvh - var(--prts-host-top-inset)) * var(--prts-sidebar-image-ratio,1))));
9
+ --prts-wall-x:var(--prts-protected-sidebar-x,calc((var(--prts-sidebar-width,280px) + var(--prts-nav-width) - var(--prts-wall-width)) / 2));
10
+ & :is([data-prts-region="sessions"],[data-prts-nav-rail]){background:linear-gradient(var(--prts-background-wash),var(--prts-background-wash)),var(--prts-background-sidebar) var(--prts-wall-x) var(--prts-protected-sidebar-y, 50%) / var(--prts-wall-width) auto no-repeat}
11
+ & [data-prts-region="sessions"]{background-position:center,calc(var(--prts-wall-x) - var(--prts-nav-width)) var(--prts-protected-sidebar-y, 50%)}
12
12
  }
13
13
  html[data-dsh-prts][data-prts-rail-mode="overlay"]:is([data-prts-background-mode="split"][data-prts-background-sidebar],[data-prts-background-mode="full"][data-prts-background-full]) [data-prts-nav-rail] { background: transparent; }
14
14
  html[data-dsh-prts][data-prts-background-mode="split"][data-prts-background-conversation] [data-prts-region="operation"] {
15
- background: linear-gradient(var(--prts-background-wash), var(--prts-background-wash)), var(--prts-background-conversation) center / cover no-repeat;
15
+ background: linear-gradient(var(--prts-background-wash), var(--prts-background-wash)), var(--prts-background-conversation) var(--prts-protected-conversation-x, 50%) var(--prts-protected-conversation-y, 50%) / cover no-repeat;
16
16
  }
17
17
  html[data-dsh-prts][data-prts-background-mode="full"][data-prts-background-full] [data-prts-ambient-layer],
18
18
  html[data-dsh-prts][data-prts-background-mode="split"][data-prts-background-conversation] [data-prts-ambient-layer] { display: none !important; }
@@ -34,13 +34,23 @@ html[data-dsh-prts][data-prts-background-mode="split"][data-prts-background-conv
34
34
  [data-prts-background-images] { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
35
35
  [data-prts-background-card="full"] { grid-column: 1 / -1; }
36
36
  [data-prts-background-card-heading] { display: flex; justify-content: space-between; align-items: center; min-height: 30px; margin-bottom: 6px; font-size: 13px; }
37
- [data-prts-background-upload] { position: relative; display: grid; place-items: center; width: 100%; height: 120px; padding: 0; border: 1px dashed var(--prts-line); border-radius: 8px; color: var(--prts-muted); background: var(--prts-panel); overflow: hidden; cursor: pointer; }
38
- [data-prts-background-card="full"] [data-prts-background-upload] { height: 155px; }
39
- [data-prts-background-upload][data-has-image] { border-style: solid; }
40
- [data-prts-background-upload]:hover { border-color: var(--prts-cyan); color: var(--prts-ink); }
41
- [data-prts-background-thumbnail] { position: absolute; inset: 0; background: center / cover no-repeat; }
42
- [data-prts-background-caption] { position: relative; padding: 6px 12px; border-radius: 5px; font-size: 12px; }
43
- [data-has-image] [data-prts-background-caption] { align-self: end; justify-self: end; margin: 8px; background: var(--prts-panel); color: var(--prts-ink); box-shadow: 0 1px 5px #0003; }
37
+ [data-prts-background-image-controls] { position:relative;display:grid;place-items:center;min-height:140px;border:1px dashed var(--prts-line);border-radius:8px;background:var(--prts-panel);overflow:hidden; }
38
+ [data-prts-background-card="full"] [data-prts-background-image-controls] { min-height:180px; }
39
+ [data-prts-background-image-controls][data-has-image] { border-style:solid; }
40
+ [data-prts-background-thumbnail] { position:absolute;inset:0;background:center / cover no-repeat;pointer-events:none; }
41
+ [data-prts-background-empty] {font-size:12px;color:var(--prts-muted);padding-bottom:40px}
42
+ [data-has-image] [data-prts-background-empty] {visibility:hidden}
43
+ /* One compact toolbar stays legible over either bright or dark photography. */
44
+ [data-prts-background-image-actions] {position:absolute;bottom:10px;right:10px;display:flex;align-items:center;gap:2px;padding:2px;border:1px solid #ffffff26;border-radius:7px;background:rgb(20 26 30 / 82%);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);box-shadow:0 2px 8px #0002;color:#edf2f3}
45
+ [data-prts-background-image-actions] button {display:inline-flex;align-items:center;justify-content:center;gap:5px;min-height:26px;padding:4px 7px;border:0;border-radius:5px;background:transparent;color:inherit;box-shadow:none;font:500 11px/1.2 var(--prts-body);white-space:nowrap;cursor:pointer;transition:background-color 120ms ease,color 120ms ease}
46
+ [data-prts-background-image-actions] button + button {position:relative}
47
+ [data-prts-background-image-actions] button + button::before {content:"";position:absolute;left:-1px;top:7px;bottom:7px;width:1px;background:#ffffff24}
48
+ [data-prts-background-image-actions] button[hidden] + button::before {display:none}
49
+ [data-prts-background-image-actions] svg {width:13px;height:13px;flex:none;fill:none;stroke:currentColor;stroke-width:1.4;stroke-linecap:round;stroke-linejoin:round;opacity:.8}
50
+ [data-prts-background-image-actions] button:hover {background:#ffffff18;color:#fff}
51
+ [data-prts-background-image-actions] button:active {background:#ffffff28}
52
+ [data-prts-background-image-actions] button:disabled {opacity:.45;cursor:wait}
53
+ [data-prts-background-settings] [data-prts-background-image-actions] button:focus-visible {outline:2px solid #b9e6ee;outline-offset:-2px}
44
54
  [data-prts-background-remove] { padding: 4px 8px; background: transparent; color: var(--prts-muted); border: 0; border-radius: 4px; font-size: 12px; cursor: pointer; }
45
55
  [data-prts-background-remove]:hover { color: var(--prts-red); background: color-mix(in srgb, var(--prts-red) 8%, transparent); }
46
56
  [data-prts-background-shade] { display: grid; gap: 8px; font-size: 13px; padding-top: 14px; border-top: 1px solid var(--prts-line); }
@@ -329,3 +339,63 @@ animation: prts-baker-typing 1.2s linear infinite;
329
339
  33% { background: currentColor; box-shadow: -10px 0 0 color-mix(in srgb,currentColor 30%,transparent), 10px 0 0 color-mix(in srgb,currentColor 30%,transparent); }
330
340
  66% { background: color-mix(in srgb,currentColor 30%,transparent); box-shadow: -10px 0 0 color-mix(in srgb,currentColor 30%,transparent), 10px 0 0 currentColor; }
331
341
  }
342
+
343
+ /* Keep the hero treatment beside the background readability controls. */
344
+ html[data-dsh-prts] [data-prts-background-hero-setting] {
345
+ padding:16px 0 2px; border-top:1px solid var(--prts-line);
346
+ & [data-prts-setting-row] { display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:center; gap:20px; margin:0; padding:0; border:0; background:none; }
347
+ & [data-prts-setting-row] > span { display:grid; gap:5px; min-width:0; }
348
+ & strong { font:500 13px/1.5 var(--prts-body); color:var(--prts-ink); }
349
+ & small { max-width:36em; font:400 12px/1.6 var(--prts-body); color:var(--prts-muted); }
350
+ & [data-prts-setting-switch] { justify-self:end; align-self:center; margin:0; }
351
+ }
352
+ html[data-dsh-prts] [data-prts-background-hero-setting][hidden] { display:none; }
353
+
354
+ /* Windows chrome must not clip a different canvas color into the conversation. */
355
+ html[data-dsh-prts][data-windows-titlebar] [data-prts-region="operation"] { border-radius: 0; }
356
+
357
+ [data-prts-region-editor] { width:min(1000px,calc(100vw - 32px));max-width:none;max-height:calc(100dvh - 32px);box-sizing:border-box;margin:auto;padding:24px;border:1px solid var(--prts-line);border-radius:14px;background:var(--prts-panel);color:var(--prts-ink);overflow:auto; }
358
+ [data-prts-region-editor]:not([open]) {display:none}
359
+ [data-prts-region-editor]::backdrop {background:#0009;backdrop-filter:blur(5px)}
360
+ [data-prts-region-editor] > header {display:flex;justify-content:space-between;align-items:start;gap:20px;margin-bottom:24px}
361
+ [data-prts-region-editor] > header strong {font-size:18px}
362
+ [data-prts-region-workspace] {display:grid;grid-template-columns:minmax(0,1.5fr) minmax(290px,1fr);gap:28px}
363
+ [data-prts-region-original],[data-prts-region-samples] {display:grid;align-content:start;gap:12px;min-width:0}
364
+ [data-prts-region-original] {padding:0 8px 0 24px}
365
+ [data-prts-region-stage] {margin:24px 0 12px}
366
+ [data-prts-region-preview-grid] {display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);gap:14px;align-items:start;margin-top:12px}
367
+ [data-prts-region-preview-grid] figure {display:grid;justify-items:center;align-content:start;gap:12px;margin:0;min-width:0}
368
+ [data-prts-region-preview-grid] figcaption {font-size:11px;color:var(--prts-muted)}
369
+ [data-prts-region-editor] > footer {display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:24px;padding-top:16px;border-top:1px solid var(--prts-line)}
370
+ [data-prts-region-editor] > footer > span {flex:1}
371
+ [data-prts-region-ruler] {position:absolute;pointer-events:none;color:var(--prts-muted);font:10px/1 var(--prts-mono)}
372
+ [data-prts-region-ruler="x"] {left:0;right:0;top:-20px;height:10px;border-bottom:1px solid var(--prts-line)}
373
+ [data-prts-region-ruler="y"] {top:0;bottom:0;left:-24px;width:16px;border-right:1px solid var(--prts-line)}
374
+ [data-prts-region-ruler] span,[data-prts-region-ruler] b {position:absolute;font-weight:400}
375
+ [data-prts-region-ruler="x"] span,[data-prts-region-ruler="x"] b {transform:translateX(-50%)}
376
+ [data-prts-region-ruler="y"] span,[data-prts-region-ruler="y"] b {transform:translateY(-50%)}
377
+ [data-prts-region-ruler] b {background:var(--prts-panel);color:var(--prts-cyan);z-index:1}
378
+ @media(max-width:760px){[data-prts-region-editor]{padding:18px;width:calc(100vw - 16px);max-height:calc(100dvh - 16px)}[data-prts-region-workspace]{grid-template-columns:1fr}[data-prts-region-preview-grid]{grid-template-columns:1fr 1fr}[data-prts-region-editor] > footer > span{display:none}}
379
+ [data-prts-region-editor] p { margin:0;font-size:12px;color:var(--prts-muted); }
380
+ [data-prts-region-stage] { position:relative;max-width:100%;justify-self:center;touch-action:none;user-select:none;cursor:default; }
381
+ [data-prts-region-stage] img { display:block;width:100%;height:auto;pointer-events:none; }
382
+ [data-prts-region-box] { position:absolute;box-sizing:border-box;border:2px solid #fff;outline:1px solid #111;box-shadow:0 0 0 1px #111;background:#ffffff08;cursor:move;touch-action:none; }
383
+ [data-prts-region-box] > span { position:absolute;top:4px;left:4px;background:#111b;color:#fff;font-size:10px;white-space:nowrap;pointer-events:none; }
384
+ [data-prts-region-handle] { position:absolute;width:16px!important;height:16px!important;min-width:0!important;padding:0!important;border:1px solid #111!important;background:#fff!important;border-radius:3px!important;touch-action:none; }
385
+ [data-prts-region-handle="nw"] {left:-8px;top:-8px;cursor:nwse-resize}
386
+ [data-prts-region-handle="ne"] {right:-8px;top:-8px;cursor:nesw-resize}
387
+ [data-prts-region-handle="sw"] {left:-8px;bottom:-8px;cursor:nesw-resize}
388
+ [data-prts-region-handle="se"] {right:-8px;bottom:-8px;cursor:nwse-resize}
389
+ [data-prts-region-preview] { position:relative;justify-self:center;max-width:100%;overflow:hidden;background-repeat:no-repeat;border:1px solid var(--prts-line);box-sizing:content-box; }
390
+ [data-prts-region-preview] i { position:absolute;border:1px solid #fff;box-shadow:0 0 0 1px #111;box-sizing:border-box;pointer-events:none; }
391
+ [data-prts-region-actions] { display:flex;gap:8px;flex-wrap:wrap; }
392
+ [data-prts-region-editor] button { padding:6px 10px;border:1px solid var(--prts-line);border-radius:5px;background:var(--prts-panel);color:var(--prts-ink);cursor:pointer;font-size:12px; }
393
+ [data-prts-region-save] { border-color:var(--prts-cyan)!important; }
394
+ [data-prts-region-status]:empty {display:none}
395
+
396
+ [data-prts-region-editor="sidebar"] [data-prts-region-preview-grid] {grid-template-columns:minmax(0,1fr) minmax(0,1.45fr)}
397
+
398
+ [data-prts-region-handle="n"] {left:50%;top:-8px;transform:translateX(-50%);cursor:ns-resize}
399
+ [data-prts-region-handle="s"] {left:50%;bottom:-8px;transform:translateX(-50%);cursor:ns-resize}
400
+ [data-prts-region-handle="w"] {left:-8px;top:50%;transform:translateY(-50%);cursor:ew-resize}
401
+ [data-prts-region-handle="e"] {right:-8px;top:50%;transform:translateY(-50%);cursor:ew-resize}
@@ -1907,12 +1907,12 @@ html[data-dsh-prts][data-prts-conversation-style="deck-chat"] {
1907
1907
  & [data-message-role="assistant"]::after,
1908
1908
  & :is([data-chat-flow-kind="user"], [data-submission-echo]:not([data-pending-steering])) [class$="_bubble"]::before,
1909
1909
  & [data-message-role="user"]::before {
1910
- content: ""; position: absolute; top: 0; left: -9px; width: 10px; height: 18px; pointer-events: none;
1910
+ content: ""; position: absolute; top: 0; left: -10px; width: 10px; height: 18px; pointer-events: none;
1911
1911
  background: var(--prts-bubble-surface, var(--prts-chat-user-surface));
1912
1912
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 18'%3E%3Cpath d='M0 0H10V18Q10 6 0 0Z'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
1913
1913
  }
1914
1914
  & :is([data-chat-flow-kind="user"], [data-submission-echo]:not([data-pending-steering])) [class$="_bubble"]::before,
1915
- & [data-message-role="user"]::before { left: auto; right: -9px; transform: scaleX(-1); }
1915
+ & [data-message-role="user"]::before { left: auto; right: -10px; transform: scaleX(-1); }
1916
1916
  }
1917
1917
 
1918
1918
  /* The upstream component replaces only the composer's two background plates. */
@@ -393,6 +393,12 @@ html[data-dsh-prts] [data-prts-conversation-control="danger"] {
393
393
  }
394
394
 
395
395
  /* Empty session: emblem owns the upper field, composer anchors in the lower third. */
396
+ /* Reserve the native blank header before session restoration adds its controls.
397
+ Otherwise its height grows from 0 to 40px after the emblem has been positioned. */
398
+ html[data-dsh-prts] [data-phase="hero"] > [data-slot="conversation.header"] > header {
399
+ min-height: 40px;
400
+ }
401
+
396
402
  html[data-dsh-prts] [data-prts-hero-active] {
397
403
  --prts-hero-narrow-spacer: clamp(24px, 8dvh, 72px);
398
404
  --prts-hero-wide-spacer: clamp(96px, 22dvh, 340px);
@@ -482,3 +488,25 @@ html[data-dsh-prts][data-prts-conversation-style="deck-chat"] :is([class$="_bubb
482
488
  padding:2px 7px!important;border-radius:6px!important;
483
489
  &:is(:hover,:focus-visible){border-color:var(--prts-cyan)!important}
484
490
  }
491
+
492
+ /* Notification cards own one surface across the disclosure and its content. */
493
+ html[data-dsh-prts] [data-turn-trigger] {
494
+ background:var(--prts-glass-soft-surface);
495
+ border:1px solid var(--prts-line);
496
+ border-radius:var(--dsw-radius-xl,16px);
497
+ -webkit-backdrop-filter:blur(12px);
498
+ backdrop-filter:blur(12px);
499
+ color:var(--prts-ink);
500
+ & > button {
501
+ background:transparent;
502
+ border:0;
503
+ border-radius:inherit;
504
+ color:inherit;
505
+ &:hover { background:color-mix(in srgb,var(--prts-ink) 5%,transparent); }
506
+ &:focus-visible { outline:2px solid var(--prts-cyan); outline-offset:-3px; }
507
+ & time { color:var(--prts-muted); }
508
+ }
509
+ & > div { border-top:1px solid var(--prts-line); }
510
+ & > div > p { color:var(--prts-muted); }
511
+ & [data-context-text] { color:var(--prts-ink); }
512
+ }
@@ -757,9 +757,9 @@ html[data-dsh-prts] [data-prts-theme-settings] {
757
757
  & [data-prts-reset-confirm] > span { flex-basis:100%; }
758
758
  @media(max-width:720px) {
759
759
  & [data-prts-settings-sidebar] { flex-direction:row; border:0; border-bottom:1px solid var(--prts-line); }
760
- & [data-prts-settings-nav] { flex:1; padding:8px 4px; border:0; }
760
+ & [data-prts-settings-nav] { flex:1; min-width:0; overflow-x:auto; padding:8px 4px; border:0; }
761
761
  & [data-prts-settings-jump] { padding:10px 8px; }
762
- & [data-prts-settings-actions] { margin:0; padding:8px 4px; align-self:center; }
762
+ & [data-prts-settings-actions] { flex:0 0 auto; white-space:nowrap; margin:0; padding:8px 4px; align-self:center; }
763
763
  & [data-prts-reset-confirm], & [data-prts-persistence-error] { left:auto; right:8px; top:100%; bottom:auto; max-width:calc(100vw - 32px); }
764
764
  }
765
765
  & [data-prts-reset-zone] { padding: 0; }
@@ -929,3 +929,88 @@ html[data-dsh-prts] [data-prts-ai-tools] blockquote { margin:12px 0;padding:8px
929
929
  html[data-dsh-prts] [data-prts-ai-tools] summary { cursor:pointer; }
930
930
  html[data-dsh-prts] [data-prts-ai-tools] dt { margin-top:12px; }
931
931
  html[data-dsh-prts] [data-prts-ai-tools] dd { margin:3px 0 0; }
932
+
933
+ html[data-dsh-prts] [data-prts-transfer] {
934
+ display: grid;
935
+ gap: 16px;
936
+ padding-block: 16px;
937
+ & header { display: grid; gap: 8px; }
938
+ & p, & small { margin: 0; color: var(--prts-muted); line-height: 1.7; overflow-wrap: anywhere; }
939
+ & label { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
940
+ & input[type="file"] { max-width: 100%; min-width: 0; }
941
+ & input[type="checkbox"] { accent-color: var(--prts-yellow); }
942
+ & button { justify-self: start; padding: 8px 14px; border: 1px solid var(--prts-line-strong); border-radius: 0; background: var(--prts-panel-raised); color: var(--prts-ink); font: inherit; cursor: pointer; }
943
+ & button:hover:not(:disabled) { border-color: var(--prts-yellow); }
944
+ & button:focus-visible { outline: 2px solid var(--prts-yellow); outline-offset: 3px; }
945
+ & button:disabled { opacity: .55; cursor: wait; }
946
+ & [data-prts-transfer-export], & [data-prts-transfer-apply] { border-color: var(--prts-yellow); }
947
+ & hr { width: 100%; border: 0; border-top: 1px solid var(--prts-line); }
948
+ & [data-prts-transfer-preview], & [data-prts-transfer-recovery] { display: grid; gap: 12px; padding: 16px; border: 1px solid var(--prts-line); }
949
+ & [hidden] { display: none !important; }
950
+ }
951
+
952
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-actions] { display:flex; flex-direction:column; align-items:stretch; gap:8px; }
953
+ html[data-dsh-prts] [data-prts-transfer-open] { display:flex; align-items:center; gap:8px; width:100%; padding:10px 8px; background:none; border:1px solid var(--prts-line); border-radius:4px; color:var(--prts-ink); font:500 12px/1.4 var(--prts-body); cursor:pointer; }
954
+ html[data-dsh-prts] [data-prts-transfer-open]:hover { border-color:var(--prts-yellow); }
955
+ html[data-dsh-prts] [data-prts-transfer-dialog] { box-sizing:border-box; width:min(660px,calc(100vw - 32px)); max-height:calc(100dvh - 48px); padding:0; border:1px solid var(--prts-line-strong); border-radius:8px; background:var(--prts-canvas); color:var(--prts-ink); box-shadow:0 24px 80px #0006; font:13px/1.6 var(--prts-body); overflow:auto; }
956
+ html[data-dsh-prts] [data-prts-transfer-dialog]::backdrop { background:#0007; }
957
+ html[data-dsh-prts] [data-prts-transfer] {
958
+ padding:24px; gap:20px;
959
+ & [data-prts-transfer-heading] { display:flex; align-items:center; justify-content:space-between; gap:16px; }
960
+ & h2 { margin:4px 0 0; font-size:20px; line-height:1.4; }
961
+ & h3 { margin:0; font-size:14px; }
962
+ & [data-prts-transfer-close] { padding:2px 10px; font-size:24px; border:0; background:none; }
963
+ & [data-prts-transfer-columns] { display:grid; grid-template-columns:1fr 1fr; gap:24px; }
964
+ & [data-prts-transfer-card] { display:flex; flex-direction:column; gap:12px; min-width:0; }
965
+ & [data-prts-transfer-card] + section { padding-left:24px; border-left:1px solid var(--prts-line); }
966
+ & [data-prts-transfer-card] > button { margin-top:auto; display:flex; align-items:center; justify-content:space-between; }
967
+ & [data-prts-transfer-option] { align-items:flex-start; flex-wrap:nowrap; padding:12px 0 0; }
968
+ & [data-prts-transfer-option] input { margin:5px 0 0; }
969
+ & [data-prts-transfer-option] small { display:block; margin-top:4px; }
970
+ & [data-prts-transfer-file-hint] { display:grid; place-items:center; flex:1; gap:6px; padding:12px; border:1px dashed var(--prts-line-strong); }
971
+ & [data-prts-transfer-file-hint] > span { font-size:24px; color:var(--prts-muted); }
972
+ & [data-prts-transfer-buttons] { display:flex; gap:8px; }
973
+ & [data-prts-transfer-recovery] { border:0; border-top:1px solid var(--prts-line); padding:16px 0 0; }
974
+ & [data-prts-transfer-status]:empty { display:none; }
975
+ }
976
+ @media(max-width:720px) {
977
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-body] { grid-template-rows:auto minmax(0,1fr) auto; }
978
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-sidebar] { display:contents; }
979
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-nav] { grid-row:1; }
980
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-theme-settings-scroll] { grid-row:2; }
981
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-actions] { grid-row:3; position:relative; flex-direction:row; align-items:center; justify-content:space-between; padding:12px 16px; border-top:1px solid var(--prts-line); background:var(--prts-canvas); }
982
+ html[data-dsh-prts] [data-prts-transfer-open] { width:auto; }
983
+ html[data-dsh-prts] [data-prts-theme-settings] :is([data-prts-reset-confirm],[data-prts-persistence-error]) { top:auto; bottom:100%; }
984
+ }
985
+ @media(max-width:540px) {
986
+ html[data-dsh-prts] [data-prts-transfer] { padding:20px; }
987
+ html[data-dsh-prts] [data-prts-transfer] [data-prts-transfer-columns] { grid-template-columns:1fr; gap:20px; }
988
+ html[data-dsh-prts] [data-prts-transfer] [data-prts-transfer-card] + section { padding:20px 0 0; border-left:0; border-top:1px solid var(--prts-line); }
989
+ }
990
+
991
+ /* Shared quiet actions below the settings categories. */
992
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-actions] {
993
+ padding:12px 10px; gap:2px; border-top:1px solid var(--prts-line);
994
+ }
995
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-footer-action] {
996
+ display:flex; align-items:center; justify-content:flex-start; gap:10px;
997
+ width:100%; min-height:40px; padding:10px; margin:0;
998
+ border:0; border-radius:5px; clip-path:none; box-shadow:none;
999
+ background:transparent; color:var(--prts-muted);
1000
+ font:500 12px/1.4 var(--prts-body); text-align:left; cursor:pointer;
1001
+ }
1002
+ html[data-dsh-prts] [data-prts-footer-action] svg {
1003
+ width:16px; height:16px; flex:none; fill:none; stroke:currentColor;
1004
+ stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round;
1005
+ }
1006
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-footer-action]:hover {
1007
+ background:var(--prts-panel-raised); color:var(--prts-ink);
1008
+ }
1009
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-footer-action]:focus-visible {
1010
+ outline:2px solid var(--prts-yellow); outline-offset:-2px;
1011
+ }
1012
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-footer-action][hidden] { display:none; }
1013
+ @media(max-width:720px) {
1014
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-settings-actions] { display:grid; grid-template-columns:1fr 1fr; gap:8px; padding:8px 12px; }
1015
+ html[data-dsh-prts] [data-prts-theme-settings] [data-prts-reset-zone] { width:100%; }
1016
+ }