viberoom 0.7.0 → 0.9.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.
- package/NOTICE +5 -3
- package/README.md +90 -19
- package/assets/vendors/grok.svg +3 -0
- package/assets/vendors/hermes.svg +1 -0
- package/dist/agent-health.js +1 -1
- package/dist/hub.js +98 -49
- package/dist/jsonrpc.js +3 -1
- package/dist/look-lint.js +180 -0
- package/dist/looks.js +151 -0
- package/dist/main.js +4 -1
- package/dist/mcp-skills-server.js +87 -0
- package/dist/persona.js +10 -3
- package/dist/recipes.js +110 -8
- package/dist/room-design.js +9 -6
- package/dist/room.js +190 -26
- package/dist/server.js +73 -1
- package/dist/skills.js +23 -2
- package/package.json +1 -1
- package/ui/app.css +23 -16
- package/ui/app.js +198 -66
- package/ui/components.css +96 -115
- package/ui/components.js +8 -3
- package/ui/index.html +3 -2
- package/ui/looks.css +462 -77
- package/ui/theme.css +94 -13
- package/ui/tokens.js +526 -57
package/dist/server.js
CHANGED
|
@@ -34,6 +34,8 @@ const STATIC_FILES = {
|
|
|
34
34
|
"/vendor-icons/cursor.svg": { file: "vendors/cursor.svg", type: "image/svg+xml", dir: "assets" },
|
|
35
35
|
"/vendor-icons/opencode.svg": { file: "vendors/opencode.svg", type: "image/svg+xml", dir: "assets" },
|
|
36
36
|
"/vendor-icons/copilot.svg": { file: "vendors/copilot.svg", type: "image/svg+xml", dir: "assets" },
|
|
37
|
+
"/vendor-icons/grok.svg": { file: "vendors/grok.svg", type: "image/svg+xml", dir: "assets" },
|
|
38
|
+
"/vendor-icons/hermes.svg": { file: "vendors/hermes.svg", type: "image/svg+xml", dir: "assets" },
|
|
37
39
|
"/vendor/mermaid.min.js": { file: "mermaid/dist/mermaid.min.js", type: "text/javascript; charset=utf-8", dir: "node_modules" },
|
|
38
40
|
"/vendor/marked.umd.js": { file: "marked/lib/marked.umd.js", type: "text/javascript; charset=utf-8", dir: "node_modules" },
|
|
39
41
|
"/vendor/prism.js": { file: "prismjs/prism.js", type: "text/javascript; charset=utf-8", dir: "node_modules" },
|
|
@@ -137,10 +139,16 @@ export function startServer(hub, port, log, info, onShutdownRequest, onRestartRe
|
|
|
137
139
|
}
|
|
138
140
|
return;
|
|
139
141
|
}
|
|
142
|
+
if (req.method === "GET" && path === "/looks-custom.css") {
|
|
143
|
+
const css = await hub.looks.css();
|
|
144
|
+
res.writeHead(200, { "Content-Type": "text/css; charset=utf-8", "Cache-Control": "no-cache" });
|
|
145
|
+
res.end(css);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
140
148
|
if (req.method === "GET" && STATIC_FILES[path]) {
|
|
141
149
|
const entry = STATIC_FILES[path];
|
|
142
150
|
const body = await readFile(staticPath(entry));
|
|
143
|
-
res.writeHead(200, { "Content-Type": entry.type, "Cache-Control": entry.dir === "
|
|
151
|
+
res.writeHead(200, { "Content-Type": entry.type, "Cache-Control": entry.dir === "node_modules" ? "public, max-age=3600" : "no-cache" });
|
|
144
152
|
res.end(body);
|
|
145
153
|
return;
|
|
146
154
|
}
|
|
@@ -360,6 +368,15 @@ export function startServer(hub, port, log, info, onShutdownRequest, onRestartRe
|
|
|
360
368
|
sendJson(res, 200, target.room.describeRoomForAgent(target.participantId));
|
|
361
369
|
return;
|
|
362
370
|
}
|
|
371
|
+
if (req.method === "GET" && path === "/api/mcp/looks") {
|
|
372
|
+
const target = hub.resolveMcpToken(url.searchParams.get("token") ?? "");
|
|
373
|
+
if (!target) {
|
|
374
|
+
sendJson(res, 403, { error: "unknown skills token (the session it belonged to is gone)" });
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
sendJson(res, 200, await target.room.describeLooksForAgent(target.participantId));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
363
380
|
if (req.method === "GET" && path === "/api/mcp/message") {
|
|
364
381
|
const target = hub.resolveMcpToken(url.searchParams.get("token") ?? "");
|
|
365
382
|
if (!target) {
|
|
@@ -410,6 +427,14 @@ export function startServer(hub, port, log, info, onShutdownRequest, onRestartRe
|
|
|
410
427
|
sendJson(res, 200, { templates: hub.templates.list() });
|
|
411
428
|
return;
|
|
412
429
|
}
|
|
430
|
+
if (req.method === "GET" && path === "/api/looks") {
|
|
431
|
+
sendJson(res, 200, { looks: hub.looks.list() });
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (req.method === "GET" && path === "/api/looks/describe") {
|
|
435
|
+
sendJson(res, 200, await hub.looks.describe());
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
413
438
|
if (req.method === "GET" && path === "/api/rooms") {
|
|
414
439
|
sendJson(res, 200, [...hub.rooms.values()].map((r) => r.snapshot()));
|
|
415
440
|
return;
|
|
@@ -423,6 +448,29 @@ export function startServer(hub, port, log, info, onShutdownRequest, onRestartRe
|
|
|
423
448
|
sendJson(res, 200, { ok: true, settings: hub.updateSettings(body) });
|
|
424
449
|
return;
|
|
425
450
|
}
|
|
451
|
+
if (path === "/api/looks/check") {
|
|
452
|
+
const { checkLookSpec } = await import("./looks.js");
|
|
453
|
+
try {
|
|
454
|
+
const checked = await checkLookSpec(body.spec ?? body);
|
|
455
|
+
sendJson(res, 200, { ok: checked.lint.ok, id: checked.spec.id, errors: checked.lint.errors, warnings: checked.lint.warnings, report: checked.lint.report });
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
sendJson(res, 200, { ok: false, errors: [{ level: "error", key: "spec", message: error instanceof Error ? error.message : String(error) }], warnings: [], report: [] });
|
|
459
|
+
}
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
if (path === "/api/looks") {
|
|
463
|
+
const saved = await hub.saveLook(body.spec ?? body, { author: "human", replace: body.replace === true || body.replace === "true" });
|
|
464
|
+
sendJson(res, 200, { ok: true, look: saved.spec, warnings: saved.lint.warnings, report: saved.lint.report });
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (path === "/api/looks/remove") {
|
|
468
|
+
const id = String(body.id ?? "");
|
|
469
|
+
if (!hub.removeLook(id))
|
|
470
|
+
throw new Error(`no look "${id}" of your own to remove`);
|
|
471
|
+
sendJson(res, 200, { ok: true });
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
426
474
|
if (path === "/api/fs/mkdir") {
|
|
427
475
|
sendJson(res, 200, { ok: true, path: await createFolder(String(body.parent ?? ""), String(body.name ?? "")) });
|
|
428
476
|
return;
|
|
@@ -498,6 +546,30 @@ export function startServer(hub, port, log, info, onShutdownRequest, onRestartRe
|
|
|
498
546
|
sendJson(res, 200, target.room.createTemplateForAgent(target.participantId, design, body.replace === true || body.replace === "true"));
|
|
499
547
|
return;
|
|
500
548
|
}
|
|
549
|
+
if (path === "/api/mcp/looks/lint" || path === "/api/mcp/looks/create" || path === "/api/mcp/looks/propose") {
|
|
550
|
+
const target = hub.resolveMcpToken(String(body.token ?? ""));
|
|
551
|
+
if (!target) {
|
|
552
|
+
sendJson(res, 403, { error: "unknown skills token (the session it belonged to is gone)" });
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (path === "/api/mcp/looks/lint") {
|
|
556
|
+
sendJson(res, 200, await target.room.lintLookForAgent(target.participantId, body.spec ?? body));
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
if (path === "/api/mcp/looks/create") {
|
|
560
|
+
sendJson(res, 200, await target.room.createLookForAgent(target.participantId, body.spec ?? body, body.replace === true || body.replace === "true"));
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const num = (v) => (v === undefined || v === null || v === "" ? undefined : Number(v));
|
|
564
|
+
sendJson(res, 200, await target.room.proposeLookChanges(target.participantId, String(body.why ?? ""), {
|
|
565
|
+
look: typeof body.look === "string" ? body.look : undefined,
|
|
566
|
+
adjust: body.adjust && typeof body.adjust === "object" && !Array.isArray(body.adjust) ? body.adjust : undefined,
|
|
567
|
+
chatFontSize: num(body.chatFontSize),
|
|
568
|
+
font: typeof body.font === "string" ? body.font : undefined,
|
|
569
|
+
mono: typeof body.mono === "string" ? body.mono : undefined,
|
|
570
|
+
}));
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
501
573
|
if (path === "/api/mcp/propose") {
|
|
502
574
|
const target = hub.resolveMcpToken(String(body.token ?? ""));
|
|
503
575
|
if (!target) {
|
package/dist/skills.js
CHANGED
|
@@ -4,7 +4,7 @@ import { join, resolve } from "node:path";
|
|
|
4
4
|
import { isReservedSkillName, RESERVED_SKILL_NAMES } from "./commands.js";
|
|
5
5
|
export const SKILL_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/;
|
|
6
6
|
export const SKILL_FILE = "SKILL.md";
|
|
7
|
-
import { ROOM_DESIGNER_NAME } from "./persona.js";
|
|
7
|
+
import { LOOK_DESIGNER_NAME, ROOM_DESIGNER_NAME } from "./persona.js";
|
|
8
8
|
export const BUILTIN_AUTHOR = "viberoom";
|
|
9
9
|
export const HUMAN_AUTHOR = "human";
|
|
10
10
|
const DESCRIPTION_MAX = 300;
|
|
@@ -175,7 +175,28 @@ export function renderSkillBody(body, args) {
|
|
|
175
175
|
.replace(/\r\n/g, "\n")
|
|
176
176
|
.trim();
|
|
177
177
|
}
|
|
178
|
-
export const
|
|
178
|
+
export const LOOK_DESIGNER = {
|
|
179
|
+
name: LOOK_DESIGNER_NAME,
|
|
180
|
+
description: "How to design a good viberoom look (how the window is drawn: colours, light, corners, fonts) as data that extends a shipped look. Load it before lint_look, create_look or propose_look_changes.",
|
|
181
|
+
argumentHint: "",
|
|
182
|
+
body: [
|
|
183
|
+
"A look is data, not code: which shipped look it extends, a few hues laid over that look's palette, and what it wants otherwise (corners, fonts, shadows, the chat's paper, a part of an element). Everything the window draws is derived from the palette, so a hue changed there reaches every place that wears it; start from the shipped look closest to what is asked and change as little as says it. The facts (the looks, every hue and element with what it means, the value syntax, the fonts, how the window is set now) come from describe_looks; never guess a name, a key the look does not have is refused.",
|
|
184
|
+
"",
|
|
185
|
+
"Name what a colour looks like, not what it is for: the palette says primary, ink, bg, white, lav; the elements say which part wears which hue (bubble.bg, btn.hoverInk). Prefer changing hues over changing elements: a new accent is palette.primary plus its primaryLight, primaryDeep and primaryDark steps and its tints lav and lav2; a new paper is bg with white (the panels) a shade apart from it, and soft / softer a step off white; a new ink is ink with ink2 for the words in a bubble and ink3, muted and faint growing quieter. Change an element only when a part must differ from what the palette gives it.",
|
|
186
|
+
"",
|
|
187
|
+
"You cannot see the colours; the lint can. Every look must read: the words on a bubble and on the paper at 7:1, the quiet words at 3:1 (4.5:1 on dark paper), every ink on its own paper at 3:1. Run lint_look before you save and read the report: it names every pair with its ratio. Fix the ink before the paper (a darker muted, an ink2 nearer black), keep the accent readable where it is written on (btn.onPrimary on primary at 3:1), and give a bubble that sits on paper of nearly its own shade a hairline (bubble.border) or another shade.",
|
|
188
|
+
"",
|
|
189
|
+
"Light comes from one place, top-left, and the shadows fall from it in the palette's shadowInk; a flat look sets the shadows to none, a look with volume raises a thing with a soft shadow below it and a bevel (a light gradient over its face) and sinks a pressed one with an inset shade. Say scheme dark when the paper is dark: diagrams, marks and the quiet-word floors follow it. One accent; the state colours (green ready, yellow thinking, red error, blue waiting, violet writing) keep their families, only their shades follow the paper. Fonts by id from describe_looks (they ship with viberoom and look the same on every machine); running text at lineHeight 1.45 or more; a terminal look wants the mono font everywhere and no curves (rScale 0), a soft look rounder corners (rScale 1.2-1.6) and pills for every control (rCtlMin 99px).",
|
|
190
|
+
"",
|
|
191
|
+
"When it reads: create_look saves it among the human's own looks (Settings → Appearance, after the shipped ones, with the human's name on it); one of the human's own may be replaced with replace: true, a shipped look never. Then propose_look_changes offers it as a card (look: its id), or fine-tunes any look with the adjustables describe_looks lists (a colour as #rrggbb, a scale 0-2) — the whole window changes, not this room, and only on the human's click. What a look cannot do: no CSS of its own, no layout, no icons, no fonts beyond the shipped ids; when the human asks for such a thing, say so instead of forcing a token to carry it.",
|
|
192
|
+
].join("\n"),
|
|
193
|
+
userInvocable: true,
|
|
194
|
+
agentInvocable: true,
|
|
195
|
+
author: BUILTIN_AUTHOR,
|
|
196
|
+
reviewed: true,
|
|
197
|
+
draft: false,
|
|
198
|
+
};
|
|
199
|
+
export const BUILTIN_SKILLS = [SKILL_WRITER, ROOM_DESIGNER, LOOK_DESIGNER];
|
|
179
200
|
export function isBuiltinSkill(name) {
|
|
180
201
|
const lower = name.trim().toLowerCase();
|
|
181
202
|
return BUILTIN_SKILLS.some((b) => b.name === lower);
|
package/package.json
CHANGED
package/ui/app.css
CHANGED
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
@supports not selector(::-webkit-scrollbar) { .rail-rooms { scrollbar-width: thin; } }
|
|
31
31
|
.shell.rail-open .rail-rooms { align-items: stretch; }
|
|
32
32
|
.rail-room { flex: none; }
|
|
33
|
-
.rail-room [data-ui="room-mark"] { width: 40px; height: 40px; border-radius:
|
|
33
|
+
.rail-room [data-ui="room-mark"] { width: 40px; height: 40px; border-radius: var(--room-mark-radius-rail); font-size: 16px; opacity: 0.72; transition: opacity var(--t-fast), transform var(--t-fast) var(--ease-out), box-shadow var(--t-fast); }
|
|
34
34
|
.rail-room [data-ui="room-mark"][data-kind="emoji"] { font-size: 21px; }
|
|
35
35
|
.rail-room:hover [data-ui="room-mark"] { opacity: 1; transform: translateY(-1px); }
|
|
36
36
|
.rail-room.active, .rail-room.active:hover { background: transparent; box-shadow: none; }
|
|
@@ -231,18 +231,18 @@
|
|
|
231
231
|
.home-room .hr-time { color: var(--faint); font-size: calc(11px * var(--fs-scale)); font-weight: 700; flex: none; }
|
|
232
232
|
.home-room.empty-room { cursor: default; }
|
|
233
233
|
.feature-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; }
|
|
234
|
-
.feature { border-radius: calc(18px * var(--r-scale)); padding: 18px 18px 16px; display: flex; flex-direction: column; gap: 8px; box-shadow: var(--
|
|
234
|
+
.feature { border-radius: calc(18px * var(--r-scale)); padding: 18px 18px 16px; display: flex; flex-direction: column; gap: 8px; box-shadow: var(--feature-shadow); background-image: var(--bevel); transition: transform var(--t-fast) var(--ease-out); }
|
|
235
235
|
.feature:hover { transform: translateY(-2px); }
|
|
236
236
|
.feature .f-emoji { width: 46px; height: 46px; border-radius: calc(14px * var(--r-scale)); background: var(--feature-emoji-bg); display: grid; place-content: center; font-size: 24px; box-shadow: var(--shadow-tile); }
|
|
237
237
|
.feature h3 { font-size: calc(15px * var(--fs-scale)); margin-top: 4px; }
|
|
238
238
|
.feature p { margin: 0; font-size: calc(13px * var(--fs-scale)); font-weight: 600; line-height: 1.5; color: var(--ink-2); }
|
|
239
|
-
.feature.lav { background: var(--lav); }
|
|
240
|
-
.feature.mint { background: var(--mint); }
|
|
241
|
-
.feature.warm { background: var(--warm); }
|
|
242
|
-
.feature.peach { background: var(--feature-peach-bg); }
|
|
243
|
-
.feature.rose { background: var(--rose); }
|
|
244
|
-
.feature.sky { background: var(--sky); }
|
|
245
|
-
.feature.orchid { background: var(--orchid); }
|
|
239
|
+
.feature.lav { background-color: var(--lav); }
|
|
240
|
+
.feature.mint { background-color: var(--mint); }
|
|
241
|
+
.feature.warm { background-color: var(--warm); }
|
|
242
|
+
.feature.peach { background-color: var(--feature-peach-bg); }
|
|
243
|
+
.feature.rose { background-color: var(--rose); }
|
|
244
|
+
.feature.sky { background-color: var(--sky); }
|
|
245
|
+
.feature.orchid { background-color: var(--orchid); }
|
|
246
246
|
.home-vendors { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 4px; }
|
|
247
247
|
.steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
|
|
248
248
|
.step { display: flex; gap: 14px; align-items: flex-start; background: var(--step-bg); border-radius: calc(18px * var(--r-scale)); padding: 16px 18px; box-shadow: var(--edge); }
|
|
@@ -362,6 +362,13 @@
|
|
|
362
362
|
.look-cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
|
|
363
363
|
.look-cards [data-ui="look-card"] { width: auto; }
|
|
364
364
|
#look-dialog .look-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 6px 0 14px; }
|
|
365
|
+
.look-own { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin: 8px 0 2px; }
|
|
366
|
+
.look-own .own-only { display: inline-flex; gap: 6px; }
|
|
367
|
+
.prop-window { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; font-size: calc(12px * var(--fs-scale)); font-weight: 600; }
|
|
368
|
+
.prop-window .note { display: flex; align-items: center; gap: 6px; }
|
|
369
|
+
.prop-window .note > .i { width: 14px; height: 14px; flex: none; }
|
|
370
|
+
.prop-look { display: flex; align-items: center; gap: 10px; }
|
|
371
|
+
.prop-look [data-ui="look-card"] { width: 150px; cursor: default; }
|
|
365
372
|
.look-adjust { margin-top: 4px; }
|
|
366
373
|
.look-adjust .adjust-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
367
374
|
.look-adjust .adjust-head .label { font-size: calc(12px * var(--fs-scale)); font-weight: 800; color: var(--ink-3); }
|
|
@@ -373,14 +380,10 @@
|
|
|
373
380
|
.adjust-group.text .field.row [data-ui="number-field"] { width: 104px; flex: none; }
|
|
374
381
|
.adjust-group.text .field.row select { flex: 0 1 58%; }
|
|
375
382
|
.adjust-group.text #sp-chat-sample { margin-top: 4px; }
|
|
376
|
-
.bubble { max-width: 100%; background: var(--bubble-bg); border-radius: calc(6px * var(--r-scale)) calc(18px * var(--r-scale)) calc(18px * var(--r-scale)) calc(18px * var(--r-scale)); padding: 12px 16px; min-width: 0; font-size: calc(14.5px * var(--fs-scale)); font-weight: 600; line-height: 1.55; color: var(--bubble-ink); box-shadow: inset 0 0 0 1px var(--bubble-border), var(--bubble-shadow); }
|
|
377
|
-
.msg.mine .bubble { background: var(--bubble-mine-bg); color: var(--bubble-mine-ink); border-radius: calc(18px * var(--r-scale)) calc(6px * var(--r-scale)) calc(18px * var(--r-scale)) calc(18px * var(--r-scale)); box-shadow: inset 0 0 0 1px var(--bubble-border), var(--bubble-mine-shadow); padding: 11px 16px; }
|
|
383
|
+
.bubble { max-width: 100%; background-color: var(--bubble-bg); background-image: var(--bevel); border-radius: calc(6px * var(--r-scale)) calc(18px * var(--r-scale)) calc(18px * var(--r-scale)) calc(18px * var(--r-scale)); padding: 12px 16px; min-width: 0; font-size: calc(14.5px * var(--fs-scale)); font-weight: 600; line-height: 1.55; color: var(--bubble-ink); box-shadow: inset 0 0 0 1px var(--bubble-border), var(--bubble-shadow); }
|
|
384
|
+
.msg.mine .bubble { background-color: transparent; background-image: var(--bevel), var(--bubble-mine-bg); color: var(--bubble-mine-ink); border-radius: calc(18px * var(--r-scale)) calc(6px * var(--r-scale)) calc(18px * var(--r-scale)) calc(18px * var(--r-scale)); box-shadow: inset 0 0 0 1px var(--bubble-border), var(--bubble-mine-shadow); padding: 11px 16px; }
|
|
378
385
|
.msg.mine .bubble .text { color: var(--bubble-mine-ink); }
|
|
379
386
|
.msg.mine.waiting .bubble { background: var(--attention-grad); box-shadow: var(--attention-shadow); }
|
|
380
|
-
html[data-look="plush"] .msg .bubble { background-image: var(--bevel); }
|
|
381
|
-
html[data-look="plush"] .msg.mine .bubble { background-image: var(--bevel), var(--bubble-mine-bg); }
|
|
382
|
-
html[data-look="plush"] .composer-box { box-shadow: var(--input-shadow); background: var(--softer); border-radius: calc(14px * var(--r-scale)); padding: 0 12px; }
|
|
383
|
-
html[data-look="plush"] .feature { box-shadow: var(--shadow-card); background-image: var(--bevel); }
|
|
384
387
|
.bubble .waiting { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--bubble-mine-rule); font-size: calc(13px * var(--fs-scale)); font-weight: 600; color: var(--bubble-mine-waiting-ink); }
|
|
385
388
|
.bubble .waiting .i { width: 14px; height: 14px; vertical-align: -2px; }
|
|
386
389
|
.bubble .waiting-text { flex: 1; min-width: 160px; }
|
|
@@ -543,7 +546,7 @@ table.csv tbody tr:nth-child(even) td { background: var(--table-stripe); }
|
|
|
543
546
|
.composer .smile-btn { border: 0; background: transparent; color: var(--placeholder); padding: 0; display: grid; place-content: center; width: 28px; height: 28px; border-radius: calc(8px * var(--r-scale)); flex: none; }
|
|
544
547
|
.composer .smile-btn .i { width: 20px; height: 20px; }
|
|
545
548
|
.composer .smile-btn:hover { color: var(--primary); }
|
|
546
|
-
.composer-box { flex: 1; min-width: 0; display: flex; align-items: center; }
|
|
549
|
+
.composer-box { flex: 1; min-width: 0; display: flex; align-items: center; box-shadow: var(--composer-shadow); background: var(--composer-bg); border-radius: var(--composer-radius); padding: 0 var(--composer-pad-x); }
|
|
547
550
|
.composer-box:has(.shots-tray:not([hidden])) { flex-direction: column; align-items: stretch; }
|
|
548
551
|
.composer-box:has(.shots-tray:not([hidden])) textarea { flex: none; }
|
|
549
552
|
.composer.drop-target { box-shadow: 0 0 0 2px var(--primary); }
|
|
@@ -636,6 +639,7 @@ table.csv tbody tr:nth-child(even) td { background: var(--table-stripe); }
|
|
|
636
639
|
#fv-tools label.search > input:not([type="checkbox"]) { flex: 1 1 auto; margin-top: 0; width: auto; height: 100%; padding: 0; border: 0; background: transparent; }
|
|
637
640
|
.fv-tools .fv-hits, .fv-tools .fv-count { color: var(--faint); font-size: var(--fs-xs); font-weight: 700; }
|
|
638
641
|
.fv-tools .fv-goto, .fv-tools .fv-wrap { display: flex; align-items: center; gap: 5px; color: var(--muted); font-size: var(--fs-xs); font-weight: 700; }
|
|
642
|
+
#fv-tools .fv-goto, #fv-tools .fv-wrap { margin: 0; }
|
|
639
643
|
#fv-tools .fv-goto > input[type="number"] { flex: 0 0 76px; margin-top: 0; width: 76px; height: 30px; padding: 0 8px; }
|
|
640
644
|
.fv-tools .fv-wrap input { accent-color: var(--primary); }
|
|
641
645
|
.text [data-ui="file-card"] { margin: 8px 0 4px; }
|
|
@@ -708,6 +712,9 @@ table.csv tbody tr:nth-child(even) td { background: var(--table-stripe); }
|
|
|
708
712
|
.section textarea { resize: vertical; min-height: 76px; height: auto; padding: 10px 14px; line-height: 1.5; }
|
|
709
713
|
.section input::placeholder, .section textarea::placeholder { color: var(--placeholder); font-weight: 600; }
|
|
710
714
|
.section .hint { margin: -4px 0 10px; }
|
|
715
|
+
.field .count { font-variant-numeric: tabular-nums; color: var(--muted); }
|
|
716
|
+
.field .count::before { content: " · "; }
|
|
717
|
+
.field .count.over { color: var(--danger); font-weight: 800; }
|
|
711
718
|
.three-inline { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px; }
|
|
712
719
|
.three-inline label { margin-bottom: 4px; }
|
|
713
720
|
.rc-num { width: 76px; display: inline-flex; }
|