@ash-qw/dsh-theme-prts 2.2.0 → 2.3.0

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,167 @@
1
+ import { DEFAULT_PREFERENCES, normalizePreferences } from './preferences.js'
2
+
3
+ export const THEME_PACKAGE_LIMIT = 160 * 1024 * 1024
4
+ const transferSlots = ['full', 'sidebar', 'conversation']
5
+ const transferRoles = ['user', 'assistant']
6
+ const transferBuiltins = ['rhodes-island', 'lungmen', 'penguin-logistics', 'rhine-lab', 'reunion']
7
+ const record = value => value !== null && typeof value === 'object' && !Array.isArray(value)
8
+ const encodedImage = (value, limit) => typeof value === 'string' && value.length > 0 && value.length <= Math.ceil(limit / 3) * 4 && value.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(value)
9
+
10
+ export function validateThemePackage(value) {
11
+ if (!record(value) || value.format !== 'prts-theme' || value.version !== 1) throw Error('不支持的主题文件格式或版本')
12
+ if (!record(value.preferences) || !Object.keys(value.preferences).some(key => key !== 'version' && Object.hasOwn(DEFAULT_PREFERENCES, key))) throw Error('主题文件缺少设置')
13
+ if (!Number.isInteger(value.preferences.version) || value.preferences.version < 1 || value.preferences.version > DEFAULT_PREFERENCES.version) throw Error('不支持此设置版本,请更新主题后重试')
14
+ const normalized = normalizePreferences(value.preferences)
15
+ for (const [key, input] of Object.entries(value.preferences)) {
16
+ if (!Object.hasOwn(DEFAULT_PREFERENCES, key) || key === 'version') continue
17
+ if (typeof input !== typeof DEFAULT_PREFERENCES[key] || (typeof input === 'number' && !Number.isFinite(input))) throw Error(`设置 ${key} 的类型无效`)
18
+ // Current-format files must not silently turn malformed settings into defaults.
19
+ if (value.preferences.version === DEFAULT_PREFERENCES.version && normalized[key] !== input) throw Error(`设置 ${key} 的值无效`)
20
+ }
21
+ if (value.assets === undefined) return value
22
+ const a = value.assets
23
+ if (!record(a) || !record(a.backgrounds) || !record(a.avatars) || !record(a.emblems)) throw Error('主题素材数据不完整')
24
+ for (const slot of transferSlots) if (a.backgrounds[slot] !== null && !encodedImage(a.backgrounds[slot], 10 * 1024 * 1024)) throw Error('背景图片数据无效')
25
+ for (const role of transferRoles) {
26
+ const avatar = a.avatars[role]
27
+ if (avatar === null) continue
28
+ const crop = avatar?.crop
29
+ if (!record(avatar) || !encodedImage(avatar.image, 5 * 1024 * 1024) || !record(crop)
30
+ || !Number.isFinite(crop.zoom) || crop.zoom < 1 || crop.zoom > 4
31
+ || !Number.isFinite(crop.x) || crop.x < 0 || crop.x > 1 || !Number.isFinite(crop.y) || crop.y < 0 || crop.y > 1) throw Error('头像数据或裁剪参数无效')
32
+ }
33
+ const { items, hero, conversation } = a.emblems
34
+ if (!Array.isArray(items) || items.length > 13 || !Array.isArray(conversation) || conversation.length < 1 || conversation.length > 12) throw Error('徽记数量无效')
35
+ for (const item of items) {
36
+ if (!record(item) || item.format !== 'prts-emblem' || item.version !== 1 || typeof item.label !== 'string' || !item.label.trim() || item.label.length > 100 || !record(item.params) || !encodedImage(item.image, 8 * 1024 * 1024)) throw Error('徽记素材无效')
37
+ }
38
+ const validEntry = entry => record(entry) && (entry.kind === 'builtin' ? transferBuiltins.includes(entry.key) : entry.kind === 'custom' && Number.isInteger(entry.asset) && entry.asset >= 0 && entry.asset < items.length && typeof entry.label === 'string' && entry.label.length <= 100)
39
+ if ((hero !== null && (!validEntry(hero) || hero.kind !== 'custom')) || !conversation.every(validEntry)) throw Error('徽记引用无效')
40
+ return value
41
+ }
42
+
43
+ export function mergeThemePreferences(current, imported) {
44
+ const normalized = normalizePreferences(imported)
45
+ const patch = Object.fromEntries(Object.keys(DEFAULT_PREFERENCES).filter(key => key !== 'version' && Object.hasOwn(imported, key)).map(key => [key, normalized[key]]))
46
+ return normalizePreferences({ ...current, ...patch, version: DEFAULT_PREFERENCES.version })
47
+ }
48
+
49
+ export function describeThemePackage(value) {
50
+ const count = Object.keys(value.preferences).filter(key => key !== 'version' && Object.hasOwn(DEFAULT_PREFERENCES, key)).length
51
+ if (!value.assets) return `${count} 项主题设置;不包含素材,将保留当前背景、头像和徽记。`
52
+ const a = value.assets
53
+ return `${count} 项主题设置、${transferSlots.filter(k => a.backgrounds[k]).length} 张背景、${transferRoles.filter(k => a.avatars[k]).length} 个头像、${a.emblems.items.length} 个自定义徽记。将替换当前素材及徽记排列,空项恢复为默认。`
54
+ }
55
+
56
+ export function createThemeTransfer({ window, getPreferences, savePreferences, checkWritable, checkExport = checkWritable, themeVersion }) {
57
+ let busy = false, backup
58
+ const request = async (path, data) => {
59
+ const response = await window.fetch('/prts-theme-api/' + path, { credentials: 'same-origin',
60
+ ...(data === undefined ? {} : { method: 'POST', headers: { 'content-type': 'application/json', 'x-prts-request': '1' }, body: JSON.stringify(data) }) })
61
+ const result = await response.json()
62
+ if (!response.ok) throw Error(result.error || '无法连接主题素材服务')
63
+ return result
64
+ }
65
+ const readImage = blob => new Promise((resolve, reject) => {
66
+ const reader = new window.FileReader()
67
+ reader.onload = () => resolve(String(reader.result).split(',')[1])
68
+ reader.onerror = () => reject(Error('无法读取背景图片'))
69
+ reader.readAsDataURL(blob)
70
+ })
71
+ async function capture(includeAssets) {
72
+ const value = { format: 'prts-theme', version: 1, themeVersion, exportedAt: new Date().toISOString(), preferences: { ...getPreferences() } }
73
+ const states = {}
74
+ if (includeAssets) {
75
+ const backgrounds = {}, avatars = {}, items = [], ids = new Map()
76
+ states.backgrounds = await request('backgrounds/configuration')
77
+ for (const slot of transferSlots) {
78
+ const entry = states.backgrounds[slot]
79
+ backgrounds[slot] = null
80
+ if (entry) {
81
+ if (!/^[a-f0-9-]{36}$/.test(entry.id)) throw Error('背景引用无效')
82
+ const response = await window.fetch('/prts-theme-api/backgrounds/image/' + entry.id, { credentials: 'same-origin' })
83
+ if (!response.ok) throw Error('无法导出背景图片')
84
+ backgrounds[slot] = await readImage(await response.blob())
85
+ }
86
+ }
87
+ states.avatars = await request('avatars/configuration')
88
+ for (const role of transferRoles) {
89
+ const source = await request('avatars/edit/' + role)
90
+ if (source.revision !== states.avatars.revision) throw Error('头像已变化,请重新导出')
91
+ avatars[role] = source.image ? { image: source.image, crop: source.crop } : null
92
+ }
93
+ states.emblems = await request('emblems/configuration')
94
+ async function reference(entry) {
95
+ if (!entry) return null
96
+ if (entry.kind === 'builtin') return { kind: 'builtin', key: entry.key, label: entry.label }
97
+ if (!ids.has(entry.draftId)) {
98
+ ids.set(entry.draftId, items.length)
99
+ items.push(await request('emblems/draft/' + encodeURIComponent(entry.draftId) + '/export'))
100
+ }
101
+ return { kind: 'custom', asset: ids.get(entry.draftId), label: entry.label }
102
+ }
103
+ const hero = await reference(states.emblems.configuration.hero), conversation = []
104
+ for (const entry of states.emblems.configuration.conversation) conversation.push(await reference(entry))
105
+ value.assets = { backgrounds, avatars, emblems: { items, hero, conversation } }
106
+ for (const kind of ['backgrounds', 'avatars', 'emblems']) {
107
+ if ((await request(kind + '/configuration')).revision !== states[kind].revision) throw Error('素材在导出期间发生变化,请重试')
108
+ }
109
+ }
110
+ if (JSON.stringify(value.preferences) !== JSON.stringify(getPreferences())) throw Error('设置在导出期间发生变化,请重试')
111
+ validateThemePackage(value)
112
+ return { value, states }
113
+ }
114
+ async function exclusive(fn) {
115
+ if (busy) throw Error('正在处理主题文件,请稍候')
116
+ busy = true
117
+ try { return await fn() } finally { busy = false }
118
+ }
119
+ function download(value, name = 'theme') {
120
+ const blob = new window.Blob([JSON.stringify(value)], { type: 'application/json' })
121
+ if (blob.size > THEME_PACKAGE_LIMIT) throw Error('主题文件超过 160 MB,请减少素材后重试')
122
+ const url = window.URL.createObjectURL(blob), link = window.document.createElement('a')
123
+ link.href = url; link.download = `prts-${name}-${new Date().toISOString().replace(/[:.]/g, '-')}.prts-theme.json`
124
+ window.document.body.append(link); link.click(); link.remove()
125
+ window.setTimeout(() => window.URL.revokeObjectURL(url), 1000)
126
+ }
127
+ async function apply(value) {
128
+ validateThemePackage(value)
129
+ const preferenceRevision = checkWritable()
130
+ const before = await capture(Boolean(value.assets))
131
+ backup = before.value
132
+ download(backup, 'before-import')
133
+ const undo = [], revisions = Object.fromEntries(Object.entries(before.states).map(([key, state]) => [key, state.revision]))
134
+ const write = async (kind, data, previous) => {
135
+ const result = await request(kind + '/configuration', { ...data, revision: revisions[kind] })
136
+ revisions[kind] = result.revision
137
+ undo.push(async () => {
138
+ const restored = await request(kind + '/configuration', { ...previous, revision: revisions[kind] })
139
+ revisions[kind] = restored.revision
140
+ })
141
+ }
142
+ try {
143
+ if (value.assets) {
144
+ const a = value.assets, old = before.value.assets, drafts = []
145
+ // Generate every custom draft before changing the active configuration.
146
+ for (const item of a.emblems.items) drafts.push(await request('emblems/import', item))
147
+ const entry = (ref, index) => ref === null ? null : ref.kind === 'builtin' ? ref : { kind: 'custom', key: 'import-' + index + '-' + drafts[ref.asset].draftId, label: ref.label, draftId: drafts[ref.asset].draftId }
148
+ const configuration = { version: 2, hero: entry(a.emblems.hero, 'hero'), conversation: a.emblems.conversation.map(entry) }
149
+ for (const slot of transferSlots) await write('backgrounds', { slot, image: a.backgrounds[slot] }, { slot, image: old.backgrounds[slot] })
150
+ for (const role of transferRoles) await write('avatars', { role, image: a.avatars[role]?.image ?? null, crop: a.avatars[role]?.crop }, { role, image: old.avatars[role]?.image ?? null, crop: old.avatars[role]?.crop })
151
+ await write('emblems', { configuration }, { configuration: before.states.emblems.configuration })
152
+ }
153
+ await savePreferences(mergeThemePreferences(before.value.preferences, value.preferences), preferenceRevision)
154
+ } catch (error) {
155
+ let failures = 0
156
+ for (const restore of undo.reverse()) { try { await restore() } catch { failures++ } }
157
+ throw Error(`${error.message}。${failures ? '部分素材未能自动恢复。' : '已回退已确认写入的素材。'}若连接中断,请检查实际状态;导入前备份已发起下载,可重新导入恢复。`)
158
+ }
159
+ }
160
+ return {
161
+ export: includeAssets => exclusive(async () => { checkExport(); return (await capture(includeAssets)).value }),
162
+ import: value => exclusive(() => apply(value)),
163
+ restore: () => exclusive(() => { if (!backup) throw Error('没有可恢复的备份'); return apply(backup) }),
164
+ get hasBackup() { return Boolean(backup) },
165
+ download,
166
+ }
167
+ }
@@ -329,3 +329,17 @@ animation: prts-baker-typing 1.2s linear infinite;
329
329
  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
330
  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
331
  }
332
+
333
+ /* Keep the hero treatment beside the background readability controls. */
334
+ html[data-dsh-prts] [data-prts-background-hero-setting] {
335
+ padding:16px 0 2px; border-top:1px solid var(--prts-line);
336
+ & [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; }
337
+ & [data-prts-setting-row] > span { display:grid; gap:5px; min-width:0; }
338
+ & strong { font:500 13px/1.5 var(--prts-body); color:var(--prts-ink); }
339
+ & small { max-width:36em; font:400 12px/1.6 var(--prts-body); color:var(--prts-muted); }
340
+ & [data-prts-setting-switch] { justify-self:end; align-self:center; margin:0; }
341
+ }
342
+ html[data-dsh-prts] [data-prts-background-hero-setting][hidden] { display:none; }
343
+
344
+ /* Windows chrome must not clip a different canvas color into the conversation. */
345
+ html[data-dsh-prts][data-windows-titlebar] [data-prts-region="operation"] { border-radius: 0; }
@@ -482,3 +482,25 @@ html[data-dsh-prts][data-prts-conversation-style="deck-chat"] :is([class$="_bubb
482
482
  padding:2px 7px!important;border-radius:6px!important;
483
483
  &:is(:hover,:focus-visible){border-color:var(--prts-cyan)!important}
484
484
  }
485
+
486
+ /* Notification cards own one surface across the disclosure and its content. */
487
+ html[data-dsh-prts] [data-turn-trigger] {
488
+ background:var(--prts-glass-soft-surface);
489
+ border:1px solid var(--prts-line);
490
+ border-radius:var(--dsw-radius-xl,16px);
491
+ -webkit-backdrop-filter:blur(12px);
492
+ backdrop-filter:blur(12px);
493
+ color:var(--prts-ink);
494
+ & > button {
495
+ background:transparent;
496
+ border:0;
497
+ border-radius:inherit;
498
+ color:inherit;
499
+ &:hover { background:color-mix(in srgb,var(--prts-ink) 5%,transparent); }
500
+ &:focus-visible { outline:2px solid var(--prts-cyan); outline-offset:-3px; }
501
+ & time { color:var(--prts-muted); }
502
+ }
503
+ & > div { border-top:1px solid var(--prts-line); }
504
+ & > div > p { color:var(--prts-muted); }
505
+ & [data-context-text] { color:var(--prts-ink); }
506
+ }
@@ -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
+ }