finch-skin-studio 0.1.0 → 0.1.2
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.
- package/README.md +4 -2
- package/dist/panel.html +111 -6
- package/dist/panel.js +70 -14
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -9,6 +9,7 @@ Design and manage how Finch looks — Home background and color skins — from o
|
|
|
9
9
|
- **Extract your current skin** — already have a look you like? Save whatever is currently applied as a named custom skin, no need to remember the exact colors.
|
|
10
10
|
- **6 built-in presets** — 3 light (Snow White, Mint Morning, Peach Cream) and 3 dark (Graphite, Midnight Berry, Deep Teal), ready to use immediately.
|
|
11
11
|
- **Switch back to Finch's own theme** — not just custom skins: a segmented control lets you drop back to Finch's built-in Auto/Light/Dark system theme at any time.
|
|
12
|
+
- **Ask the AI to design one** — a small button next to that control drops a ready-made "design me a skin" request into the Composer, so you only have to add the mood or colors you want before sending.
|
|
12
13
|
- **Copy & paste skins** — copy any custom skin to the clipboard as a small JSON snippet to back it up or share it, then paste it into another Finch to add it to that library.
|
|
13
14
|
- **Card gallery panel** — open the palette icon in the Composer toolbar (or the right Panel launcher) to browse every preset and custom skin as a rounded thumbnail card, switch with one click, and manage the background image — all without going through the AI.
|
|
14
15
|
|
|
@@ -16,7 +17,7 @@ Design and manage how Finch looks — Home background and color skins — from o
|
|
|
16
17
|
|
|
17
18
|
**From chat:** just describe what you want — "把首页背景换成这张图,铺满显示" or "apply the Deep Teal skin" or "save this as 'My Sunset'" or "switch back to the system dark theme". The assistant calls the right tool and it takes effect immediately, exactly like editing Appearance Settings yourself.
|
|
18
19
|
|
|
19
|
-
**From the panel:** click the palette icon in the Composer toolbar to open the Skin Studio panel. The background card lets you pick an image, toggle Fill/Tile, and adjust brightness. Above the skin gallery, a small Auto/Light/Dark control switches back to Finch's own built-in theme. The gallery itself shows built-in presets plus anything you've saved; click a card to apply it, hover a custom card to copy or delete it, click "Save current skin" to name and keep whatever's currently applied
|
|
20
|
+
**From the panel:** click the palette icon in the Composer toolbar to open the Skin Studio panel. The background card lets you pick an image, toggle Fill/Tile, and adjust brightness. Above the skin gallery, a small Auto/Light/Dark control switches back to Finch's own built-in theme, followed by two round buttons: the paintbrush drops a design request into the Composer for you to top up and send, and the clipboard next to it opens a dialog where you can paste a skin someone shared with you. The gallery itself shows built-in presets plus anything you've saved; click a card to apply it, hover a custom card to copy or delete it, or click "Save current skin" to name and keep whatever's currently applied.
|
|
20
21
|
|
|
21
22
|
**Picking a background image:** click "Choose image" to open your operating system's own native file dialog — no more browsing through a Finch-themed file tree. You can also just **drag an image file and drop it onto the preview box**; that works for any image on disk regardless of where it lives. Dropped/picked images are copied into this mini tool's private storage (max 15MB), and the previous copy is cleaned up automatically once you set a new one.
|
|
22
23
|
|
|
@@ -40,6 +41,7 @@ Design and manage how Finch looks — Home background and color skins — from o
|
|
|
40
41
|
- **提取当前皮肤** — 已经调出了满意的效果?把当前正在使用的配色保存为一个自定义皮肤,不用自己记颜色值。
|
|
41
42
|
- **6 套内置预设** — 明亮 3 套(简白、薄荷晨光、蜜桃奶油)、深色 3 套(石墨深邃、深夜莓果、深海青碧),开箱即用。
|
|
42
43
|
- **随时切回 Finch 自带主题** — 不只是自定义皮肤:一个分段控件可以随时切回 Finch 内置的跟随系统/浅色/深色主题。
|
|
44
|
+
- **让 AI 帮你设计** — 分段控件右侧有一个小按钮,点一下就会把一句现成的"帮我设计皮肤"需求插进输入框,你只要补上想要的氛围或颜色再发送即可。
|
|
43
45
|
- **复制与粘贴皮肤** — 把任意自定义皮肤复制为一小段 JSON 文本用于备份或分享,也可以把别人发来的皮肤数据粘贴进来添加到自己的皮肤库。
|
|
44
46
|
- **卡片画廊面板** — 点击 Composer 工具栏的调色板图标(或右侧面板启动器)打开换肤工坊,用圆角缩略卡片浏览所有预设与自定义皮肤,一键切换,也能可视化管理背景图——全程无需经过 AI。
|
|
45
47
|
|
|
@@ -47,7 +49,7 @@ Design and manage how Finch looks — Home background and color skins — from o
|
|
|
47
49
|
|
|
48
50
|
**在对话中:** 直接说出你想要的效果——"把首页背景换成这张图,铺满显示"、"应用深海青碧皮肤"、"把这个保存为『我的落日』"、"切回系统深色主题",AI 会调用对应工具立即生效,效果等同于自己在外观设置里操作。
|
|
49
51
|
|
|
50
|
-
**在面板中:** 点击 Composer 工具栏的调色板图标打开换肤工坊面板。背景卡片可以选图、切换铺满/平铺、调整明暗;皮肤画廊上方有一个跟随系统/浅色/深色的小控件,可以切回 Finch
|
|
52
|
+
**在面板中:** 点击 Composer 工具栏的调色板图标打开换肤工坊面板。背景卡片可以选图、切换铺满/平铺、调整明暗;皮肤画廊上方有一个跟随系统/浅色/深色的小控件,可以切回 Finch 自带主题,右侧两个圆形按钮:画笔会把一句设计需求插进输入框,你补充一下再发送;旁边的剪贴板会弹出一个小对话框,粘贴别人分享的皮肤数据即可导入。画廊本身展示内置预设和已保存的自定义皮肤,点击卡片即可应用,鼠标悬停自定义卡片可复制或删除它,点击"保存当前皮肤"即可为当前效果命名保存。
|
|
51
53
|
|
|
52
54
|
**选择背景图片:** 点击"选择图片"会直接打开操作系统自带的原生文件选择框,不再是 Finch 自己的文件树浏览器。你也可以直接**把一张图片拖拽到预览框里**,不管它存在磁盘上哪个位置都能用。拖放/选中的图片会被复制一份保存到本小程序的私有存储里(最大 15MB),换新图后旧的那份会自动清理掉。
|
|
53
55
|
|
package/dist/panel.html
CHANGED
|
@@ -71,6 +71,12 @@
|
|
|
71
71
|
button.primary:hover { filter: brightness(1.05); }
|
|
72
72
|
button.ghost { background: transparent; border-color: transparent; color: var(--ss-text-secondary); }
|
|
73
73
|
button:disabled { opacity: 0.5; cursor: default; }
|
|
74
|
+
/* Inline Lucide icons (see the <symbol> sprite at the top of <body>).
|
|
75
|
+
Presentation properties inherit into <use> shadow content, so size and
|
|
76
|
+
stroke are set here once instead of on every path. */
|
|
77
|
+
.ico { width: 15px; height: 15px; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
|
|
78
|
+
.ico-sm { width: 11px; height: 11px; }
|
|
79
|
+
.ico-lg { width: 20px; height: 20px; stroke-width: 1.75; }
|
|
74
80
|
|
|
75
81
|
/* ── Background section ── */
|
|
76
82
|
.bg-preview {
|
|
@@ -97,6 +103,37 @@
|
|
|
97
103
|
.seg button + button { border-left: 1px solid var(--ss-border); }
|
|
98
104
|
.seg button.active { background: var(--ss-accent-dim); color: var(--ss-accent); font-weight: 600; }
|
|
99
105
|
.label { color: var(--ss-text-tertiary); font-size: 12px; margin-right: 2px; }
|
|
106
|
+
.row-actions { margin-left: auto; display: flex; align-items: center; gap: 2px; flex: none; }
|
|
107
|
+
.icon-btn {
|
|
108
|
+
position: relative;
|
|
109
|
+
width: 28px; height: 28px; padding: 0; border-radius: 50%;
|
|
110
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
111
|
+
background: transparent; border-color: transparent; color: var(--ss-accent);
|
|
112
|
+
}
|
|
113
|
+
.icon-btn:hover { background: var(--ss-accent-dim); }
|
|
114
|
+
/* Custom tooltip. These buttons sit against the panel's right edge, so the
|
|
115
|
+
bubble is right-aligned and grows leftwards instead of overflowing. */
|
|
116
|
+
.icon-btn::after {
|
|
117
|
+
content: attr(data-tip);
|
|
118
|
+
position: absolute;
|
|
119
|
+
right: 0;
|
|
120
|
+
bottom: calc(100% + 8px);
|
|
121
|
+
padding: 5px 9px;
|
|
122
|
+
border-radius: var(--ss-radius-sm);
|
|
123
|
+
border: 1px solid var(--ss-border);
|
|
124
|
+
background: var(--ss-bg-elevated);
|
|
125
|
+
color: var(--ss-text-primary);
|
|
126
|
+
font-size: 12px; font-weight: 400; line-height: 1.3; letter-spacing: normal;
|
|
127
|
+
white-space: nowrap;
|
|
128
|
+
box-shadow: 0 4px 14px rgba(0,0,0,0.16);
|
|
129
|
+
opacity: 0; pointer-events: none; visibility: hidden;
|
|
130
|
+
transform: translateY(2px);
|
|
131
|
+
transition: opacity 0.12s ease, transform 0.12s ease, visibility 0.12s;
|
|
132
|
+
z-index: 20;
|
|
133
|
+
}
|
|
134
|
+
.icon-btn:hover::after, .icon-btn:focus-visible::after {
|
|
135
|
+
opacity: 1; visibility: visible; transform: translateY(0);
|
|
136
|
+
}
|
|
100
137
|
|
|
101
138
|
/* ── Skin grid ── */
|
|
102
139
|
.grid { display: grid; gap: 12px; }
|
|
@@ -120,6 +157,9 @@
|
|
|
120
157
|
.thumb .pill { width: 26px; height: 8px; border-radius: 4px; margin-top: 4px; }
|
|
121
158
|
.skin-name {
|
|
122
159
|
padding: 8px 10px;
|
|
160
|
+
/* Reserves room for the 20px hover-revealed action buttons so showing
|
|
161
|
+
them cannot grow the card, the row, or the page. */
|
|
162
|
+
min-height: 36px;
|
|
123
163
|
font-size: 12px;
|
|
124
164
|
display: flex;
|
|
125
165
|
align-items: center;
|
|
@@ -132,24 +172,26 @@
|
|
|
132
172
|
width: 18px; height: 18px; border-radius: 50%;
|
|
133
173
|
background: var(--ss-accent); color: #fff;
|
|
134
174
|
display: flex; align-items: center; justify-content: center;
|
|
135
|
-
font-size: 11px; line-height: 1;
|
|
136
175
|
}
|
|
176
|
+
.check-badge .ico { width: 12px; height: 12px; stroke-width: 3; }
|
|
137
177
|
.del-btn {
|
|
138
178
|
border: none; background: rgba(0,0,0,0.55); color: #fff;
|
|
139
179
|
width: 20px; height: 20px; border-radius: 50%;
|
|
140
180
|
display: none; align-items: center; justify-content: center;
|
|
141
|
-
|
|
181
|
+
padding: 0; flex: none;
|
|
142
182
|
}
|
|
143
183
|
.skin-card:hover .del-btn { display: flex; }
|
|
144
184
|
.add-card {
|
|
145
185
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
146
|
-
|
|
186
|
+
/* Matches a skin card exactly (2px border + 84px thumb + 36px name row)
|
|
187
|
+
so the dashed box is the same size even when it sits alone in a row. */
|
|
188
|
+
gap: 6px; min-height: 122px;
|
|
147
189
|
color: var(--ss-text-tertiary);
|
|
148
190
|
border: 1px dashed var(--ss-border); border-radius: var(--ss-radius-lg);
|
|
149
191
|
background: transparent; cursor: pointer; padding: 0;
|
|
150
192
|
}
|
|
151
|
-
.add-card .add-icon { font-size: 20px; line-height: 1; }
|
|
152
193
|
.add-card .add-label { font-size: 12px; text-align: center; }
|
|
194
|
+
#btn-pick, #btn-clear { display: inline-flex; align-items: center; gap: 6px; }
|
|
153
195
|
.add-card:hover { color: var(--ss-accent); border-color: var(--ss-accent); background: var(--ss-bg-hover); }
|
|
154
196
|
.skin-actions { display: flex; gap: 4px; flex: none; }
|
|
155
197
|
|
|
@@ -164,6 +206,53 @@
|
|
|
164
206
|
</style>
|
|
165
207
|
</head>
|
|
166
208
|
<body>
|
|
209
|
+
<!-- Inline Lucide sprite (lucide-static v1.44.0, ISC). The panel is a
|
|
210
|
+
packaged offline page, so the paths are embedded rather than fetched
|
|
211
|
+
from a CDN. Sizing/stroke come from the .ico class. -->
|
|
212
|
+
<svg width="0" height="0" style="position:absolute" aria-hidden="true" focusable="false">
|
|
213
|
+
<symbol id="i-copy" viewBox="0 0 24 24">
|
|
214
|
+
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
|
|
215
|
+
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
|
|
216
|
+
</symbol>
|
|
217
|
+
<symbol id="i-trash" viewBox="0 0 24 24">
|
|
218
|
+
<path d="M10 11v6" />
|
|
219
|
+
<path d="M14 11v6" />
|
|
220
|
+
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6" />
|
|
221
|
+
<path d="M3 6h18" />
|
|
222
|
+
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2" />
|
|
223
|
+
</symbol>
|
|
224
|
+
<symbol id="i-plus" viewBox="0 0 24 24">
|
|
225
|
+
<path d="M5 12h14" />
|
|
226
|
+
<path d="M12 5v14" />
|
|
227
|
+
</symbol>
|
|
228
|
+
<symbol id="i-paste" viewBox="0 0 24 24">
|
|
229
|
+
<path d="M11 14h10" />
|
|
230
|
+
<path d="M16 4h2a2 2 0 0 1 2 2v1.344" />
|
|
231
|
+
<path d="m17 18 4-4-4-4" />
|
|
232
|
+
<path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 1.793-1.113" />
|
|
233
|
+
<rect x="8" y="2" width="8" height="4" rx="1" />
|
|
234
|
+
</symbol>
|
|
235
|
+
<symbol id="i-paintbrush" viewBox="0 0 24 24">
|
|
236
|
+
<path d="m14.622 17.897-10.68-2.913" />
|
|
237
|
+
<path d="M18.376 2.622a1 1 0 1 1 3.002 3.002L17.36 9.643a.5.5 0 0 0 0 .707l.944.944a2.41 2.41 0 0 1 0 3.408l-.944.944a.5.5 0 0 1-.707 0L8.354 7.348a.5.5 0 0 1 0-.707l.944-.944a2.41 2.41 0 0 1 3.408 0l.944.944a.5.5 0 0 0 .707 0z" />
|
|
238
|
+
<path d="M9 8c-1.804 2.71-3.97 3.46-6.583 3.948a.507.507 0 0 0-.302.819l7.32 8.883a1 1 0 0 0 1.185.204C12.735 20.405 16 16.792 16 15" />
|
|
239
|
+
</symbol>
|
|
240
|
+
<symbol id="i-check" viewBox="0 0 24 24">
|
|
241
|
+
<path d="M20 6 9 17l-5-5" />
|
|
242
|
+
</symbol>
|
|
243
|
+
<symbol id="i-x" viewBox="0 0 24 24">
|
|
244
|
+
<path d="M18 6 6 18" />
|
|
245
|
+
<path d="m6 6 12 12" />
|
|
246
|
+
</symbol>
|
|
247
|
+
<symbol id="i-image-plus" viewBox="0 0 24 24">
|
|
248
|
+
<path d="M16 5h6" />
|
|
249
|
+
<path d="M19 2v6" />
|
|
250
|
+
<path d="M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5" />
|
|
251
|
+
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
|
|
252
|
+
<circle cx="9" cy="9" r="2" />
|
|
253
|
+
</symbol>
|
|
254
|
+
</svg>
|
|
255
|
+
|
|
167
256
|
<h1 id="t-title">Skin Studio</h1>
|
|
168
257
|
|
|
169
258
|
<section class="card" id="bg-card">
|
|
@@ -177,8 +266,14 @@
|
|
|
177
266
|
</div>
|
|
178
267
|
<input type="file" id="bg-file-input" accept="image/png,image/jpeg,image/webp,image/gif,image/avif" style="display:none" />
|
|
179
268
|
<div class="row">
|
|
180
|
-
<button class="primary" id="btn-pick" type="button"
|
|
181
|
-
|
|
269
|
+
<button class="primary" id="btn-pick" type="button">
|
|
270
|
+
<svg class="ico" aria-hidden="true"><use href="#i-image-plus"></use></svg>
|
|
271
|
+
<span id="t-choose">Choose image</span>
|
|
272
|
+
</button>
|
|
273
|
+
<button class="ghost" id="btn-clear" type="button" disabled>
|
|
274
|
+
<svg class="ico" aria-hidden="true"><use href="#i-x"></use></svg>
|
|
275
|
+
<span id="t-clear">Clear</span>
|
|
276
|
+
</button>
|
|
182
277
|
</div>
|
|
183
278
|
<div class="row">
|
|
184
279
|
<span class="label" id="t-placement">Placement</span>
|
|
@@ -210,6 +305,16 @@
|
|
|
210
305
|
<button data-value="light" id="t-sys-light">Light</button>
|
|
211
306
|
<button data-value="dark" id="t-sys-dark">Dark</button>
|
|
212
307
|
</div>
|
|
308
|
+
<div class="row-actions">
|
|
309
|
+
<button class="icon-btn" id="btn-ai-theme" type="button"
|
|
310
|
+
aria-label="Design a skin with AI" data-tip="Design a skin with AI">
|
|
311
|
+
<svg class="ico" aria-hidden="true"><use href="#i-paintbrush"></use></svg>
|
|
312
|
+
</button>
|
|
313
|
+
<button class="icon-btn" id="btn-import-skin" type="button"
|
|
314
|
+
aria-label="Import a shared skin" data-tip="Import a shared skin">
|
|
315
|
+
<svg class="ico" aria-hidden="true"><use href="#i-paste"></use></svg>
|
|
316
|
+
</button>
|
|
317
|
+
</div>
|
|
213
318
|
</div>
|
|
214
319
|
<div class="grid" id="skins-grid"></div>
|
|
215
320
|
</section>
|
package/dist/panel.js
CHANGED
|
@@ -17,9 +17,15 @@
|
|
|
17
17
|
dropInvalidType: 'Please choose a supported image file (PNG / JPEG / WebP / GIF / AVIF).',
|
|
18
18
|
dropTooLarge: 'Image is too large (max 15MB).',
|
|
19
19
|
systemTheme: 'System Appearance', sysAuto: 'Auto', sysLight: 'Light', sysDark: 'Dark',
|
|
20
|
-
exportSkin: 'Copy',
|
|
20
|
+
exportSkin: 'Copy', importSkin: 'Import a shared skin',
|
|
21
21
|
importInvalid: 'This file is not a valid skin export.',
|
|
22
22
|
skinCopied: 'Skin copied to clipboard',
|
|
23
|
+
aiTheme: 'Design a skin with AI',
|
|
24
|
+
aiThemeChip: 'Design a new color skin for me',
|
|
25
|
+
aiThemePrompt:
|
|
26
|
+
'Help me design a new Finch color skin. Propose a palette that fits the mood I describe below, '
|
|
27
|
+
+ 'then apply it with skin_studio_theme and save it to my custom skins. Mood / keywords: ',
|
|
28
|
+
aiThemeUnavailable: 'No chat input here — open a chat or a Space first.',
|
|
23
29
|
},
|
|
24
30
|
'zh-CN': {
|
|
25
31
|
title: '换肤工坊', bgHeading: '首页背景', bgEmpty: '尚未设置背景图',
|
|
@@ -34,9 +40,15 @@
|
|
|
34
40
|
dropInvalidType: '请选择支持的图片格式(PNG / JPEG / WebP / GIF / AVIF)。',
|
|
35
41
|
dropTooLarge: '图片过大(最多 15MB)。',
|
|
36
42
|
systemTheme: '系统外观', sysAuto: '跟随系统', sysLight: '浅色', sysDark: '深色',
|
|
37
|
-
exportSkin: '复制',
|
|
43
|
+
exportSkin: '复制', importSkin: '导入别人分享的皮肤',
|
|
38
44
|
importInvalid: '这不是有效的皮肤导出文件。',
|
|
39
45
|
skinCopied: '皮肤已复制到剪贴板',
|
|
46
|
+
aiTheme: '让 AI 设计皮肤',
|
|
47
|
+
aiThemeChip: '帮我设计一套新的配色皮肤',
|
|
48
|
+
aiThemePrompt:
|
|
49
|
+
'帮我设计一套新的 Finch 配色皮肤。先根据我下面描述的氛围给出配色方案,'
|
|
50
|
+
+ '再用 skin_studio_theme 应用并保存到我的自定义皮肤库。风格关键词:',
|
|
51
|
+
aiThemeUnavailable: '当前场景没有可用的输入框,请先打开一个对话或空间。',
|
|
40
52
|
},
|
|
41
53
|
};
|
|
42
54
|
DICT['zh-HK'] = DICT['zh-CN'];
|
|
@@ -60,6 +72,15 @@
|
|
|
60
72
|
document.getElementById('t-sys-auto').textContent = t('sysAuto');
|
|
61
73
|
document.getElementById('t-sys-light').textContent = t('sysLight');
|
|
62
74
|
document.getElementById('t-sys-dark').textContent = t('sysDark');
|
|
75
|
+
// The row's icon buttons carry no visible text. The label feeds both the
|
|
76
|
+
// accessible name and the CSS tooltip (data-tip -> .icon-btn::after);
|
|
77
|
+
// no native `title`, so the two never show at once.
|
|
78
|
+
[['btn-ai-theme', 'aiTheme'], ['btn-import-skin', 'importSkin']].forEach(function (pair) {
|
|
79
|
+
var el = document.getElementById(pair[0]);
|
|
80
|
+
var label = t(pair[1]);
|
|
81
|
+
el.setAttribute('aria-label', label);
|
|
82
|
+
el.setAttribute('data-tip', label);
|
|
83
|
+
});
|
|
63
84
|
}
|
|
64
85
|
|
|
65
86
|
// ── Color helpers ─────────────────────────────────────────────────────
|
|
@@ -101,6 +122,26 @@
|
|
|
101
122
|
return thumb;
|
|
102
123
|
}
|
|
103
124
|
|
|
125
|
+
// ── Icons ─────────────────────────────────────────────────────────────
|
|
126
|
+
// References one of the inline Lucide <symbol>s in panel.html.
|
|
127
|
+
var SVG_NS = 'http://www.w3.org/2000/svg';
|
|
128
|
+
function makeIcon(symbolId, extraClass) {
|
|
129
|
+
var svg = document.createElementNS(SVG_NS, 'svg');
|
|
130
|
+
svg.setAttribute('class', 'ico' + (extraClass ? ' ' + extraClass : ''));
|
|
131
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
132
|
+
var use = document.createElementNS(SVG_NS, 'use');
|
|
133
|
+
use.setAttribute('href', '#' + symbolId);
|
|
134
|
+
svg.appendChild(use);
|
|
135
|
+
return svg;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function makeLabel(text) {
|
|
139
|
+
var span = document.createElement('span');
|
|
140
|
+
span.className = 'add-label';
|
|
141
|
+
span.textContent = text;
|
|
142
|
+
return span;
|
|
143
|
+
}
|
|
144
|
+
|
|
104
145
|
// ── State ─────────────────────────────────────────────────────────────
|
|
105
146
|
var state = {
|
|
106
147
|
builtin: [], custom: [], background: { placement: 'fill', tone: 'balanced' },
|
|
@@ -181,7 +222,7 @@
|
|
|
181
222
|
var exp = document.createElement('button');
|
|
182
223
|
exp.className = 'del-btn';
|
|
183
224
|
exp.type = 'button';
|
|
184
|
-
exp.
|
|
225
|
+
exp.appendChild(makeIcon('i-copy', 'ico-sm'));
|
|
185
226
|
exp.title = t('exportSkin');
|
|
186
227
|
exp.addEventListener('click', function (ev) {
|
|
187
228
|
ev.stopPropagation();
|
|
@@ -190,7 +231,7 @@
|
|
|
190
231
|
var del = document.createElement('button');
|
|
191
232
|
del.className = 'del-btn';
|
|
192
233
|
del.type = 'button';
|
|
193
|
-
del.
|
|
234
|
+
del.appendChild(makeIcon('i-trash', 'ico-sm'));
|
|
194
235
|
del.title = t('deleteConfirm');
|
|
195
236
|
del.addEventListener('click', function (ev) {
|
|
196
237
|
ev.stopPropagation();
|
|
@@ -204,7 +245,7 @@
|
|
|
204
245
|
if (active) {
|
|
205
246
|
var badge = document.createElement('div');
|
|
206
247
|
badge.className = 'check-badge';
|
|
207
|
-
badge.
|
|
248
|
+
badge.appendChild(makeIcon('i-check'));
|
|
208
249
|
card.appendChild(badge);
|
|
209
250
|
}
|
|
210
251
|
card.addEventListener('click', function () {
|
|
@@ -274,19 +315,12 @@
|
|
|
274
315
|
var addCard = document.createElement('button');
|
|
275
316
|
addCard.type = 'button';
|
|
276
317
|
addCard.className = 'add-card';
|
|
277
|
-
addCard.
|
|
318
|
+
addCard.appendChild(makeIcon('i-plus', 'ico-lg'));
|
|
319
|
+
addCard.appendChild(makeLabel(t('addCard')));
|
|
278
320
|
addCard.addEventListener('click', function () {
|
|
279
321
|
api.postMessage({ type: 'requestSaveCurrent' });
|
|
280
322
|
});
|
|
281
323
|
grid.appendChild(addCard);
|
|
282
|
-
var importCard = document.createElement('button');
|
|
283
|
-
importCard.type = 'button';
|
|
284
|
-
importCard.className = 'add-card';
|
|
285
|
-
importCard.innerHTML = '<span class="add-icon">⇪</span><span class="add-label">' + t('importCard') + '</span>';
|
|
286
|
-
importCard.addEventListener('click', function () {
|
|
287
|
-
api.postMessage({ type: 'requestImportSkin' });
|
|
288
|
-
});
|
|
289
|
-
grid.appendChild(importCard);
|
|
290
324
|
computeColumns();
|
|
291
325
|
}
|
|
292
326
|
|
|
@@ -311,6 +345,9 @@
|
|
|
311
345
|
document.getElementById('btn-clear').addEventListener('click', function () {
|
|
312
346
|
api.postMessage({ type: 'clearBackground' });
|
|
313
347
|
});
|
|
348
|
+
document.getElementById('btn-import-skin').addEventListener('click', function () {
|
|
349
|
+
api.postMessage({ type: 'requestImportSkin' });
|
|
350
|
+
});
|
|
314
351
|
document.getElementById('seg-placement').addEventListener('click', function (ev) {
|
|
315
352
|
var btn = ev.target.closest('button[data-value]');
|
|
316
353
|
if (!btn) return;
|
|
@@ -327,6 +364,25 @@
|
|
|
327
364
|
api.postMessage({ type: 'setSystemTheme', theme: btn.getAttribute('data-value') });
|
|
328
365
|
});
|
|
329
366
|
|
|
367
|
+
// Drops a guiding prompt into the Composer draft as a removable annotation
|
|
368
|
+
// chip, so the user can add their own mood keywords and hit send. This is
|
|
369
|
+
// the only Composer-writing API a Panel page gets (`ctx.composerActions`'
|
|
370
|
+
// `composer.fill()` only exists inside a toolbar-button handler), it needs a
|
|
371
|
+
// real user gesture like this click, and it never sends the message itself.
|
|
372
|
+
document.getElementById('btn-ai-theme').addEventListener('click', function () {
|
|
373
|
+
var composer = api.composer;
|
|
374
|
+
if (!composer || typeof composer.addContexts !== 'function') {
|
|
375
|
+
showToast(t('aiThemeUnavailable'));
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
composer
|
|
379
|
+
.addContexts([{ type: 'annotation', label: t('aiThemeChip'), promptText: t('aiThemePrompt') }])
|
|
380
|
+
.catch(function () {
|
|
381
|
+
// Rejects on container / no-draft scopes.
|
|
382
|
+
showToast(t('aiThemeUnavailable'));
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
330
386
|
// ── Background image upload (native file dialog + drag & drop) ────────
|
|
331
387
|
var MAX_DROPPED_IMAGE_BYTES = 15 * 1024 * 1024; // 15 MB, mirrors the backend limit
|
|
332
388
|
var IMAGE_TYPE_RE = /^image\/(png|jpeg|jpg|webp|gif|avif)$/;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "finch-skin-studio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Design Finch skins and Home backgrounds: pick a background image and tiling, save AI-designed or current color schemes as custom skins, and switch between built-in and custom skins from a card gallery.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "PuterJam",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"finch": {
|
|
37
37
|
"manifestVersion": 1,
|
|
38
|
+
"minVersion": "1.6.3",
|
|
38
39
|
"name": "Skin Studio",
|
|
39
40
|
"description": "Set the Home background image and tiling, save AI-designed or current color schemes as custom skins, and switch skins from a card gallery.",
|
|
40
41
|
"systemPrompt": "Skin Studio manages Finch's visual appearance.\nUse skin_studio_theme to design/apply/save/remove color skins:\n- action=list: list built-in presets and the user's saved custom skins.\n- action=apply: apply a skin. Pass id to apply a built-in preset or a saved custom skin. To design and apply a brand-new AI-generated skin in one step, omit id and pass name+base+colors instead (colors is a partial AppearanceCustomTheme color map: bgRoot,bgMain,bgSidebar,bgElevated,bgHover,bgActive,textPrimary,textSecondary,textTertiary,accent,accentDim,border); add save:true to also persist it into the user's custom skin library in the same call. To switch back to Finch's own built-in theme instead of a custom color skin, pass systemTheme ('system'|'light'|'dark') and omit id/name/colors.\n- action=save: persist a skin into the custom library. Pass name+base+colors to save a specific AI-designed palette, or omit colors to save the most recently applied skin (\"extract current skin\") under a new name.\n- action=remove: delete a custom skin by id (built-in presets cannot be removed).\nUse skin_studio_background to change the Home background:\n- action=set: requires an absolute local image path (imagePath), optional placement ('fill'|'tile', default 'fill') and tone ('brightest'|'bright'|'balanced'|'dark'|'darkest').\n- action=clear: remove the Home background and revert to the plain theme surface.\nBoth tools take effect immediately, exactly like the user editing Appearance Settings themselves. The user can also open the Skin Studio panel (palette icon in the Composer toolbar, or the right Panel launcher) to browse skins as cards, switch back to Finch's own system light/dark/auto theme, manage the background (including choosing a file via the OS's native picker or dragging one in), and copy/paste custom skins between libraries — all without going through the AI.",
|