@musnows/scriverse 0.5.6 → 0.5.7

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.
@@ -6,7 +6,7 @@ const RELATION_STYLE = Object.freeze({
6
6
  uncertain: { label: "未确定", color: "#9aa5b5" }
7
7
  });
8
8
 
9
- /** Obsidian Graph View 风格:为浅色和深色背景分别提供可辨识的低饱和度分组配色 */
9
+ /** Obsidian Graph View 风格:为浅色和深色背景提供可辨识的低饱和度均衡配色 */
10
10
  export const OBSIDIAN_NODE_PALETTE = Object.freeze([
11
11
  Object.freeze({ key: "blue", color: "#3f6f99", darkColor: "#7fb7e3", glow: "rgba(63,111,153,.42)", darkGlow: "rgba(127,183,227,.56)" }),
12
12
  Object.freeze({ key: "indigo", color: "#5b5fa3", darkColor: "#9ea6ed", glow: "rgba(91,95,163,.42)", darkGlow: "rgba(158,166,237,.56)" }),
@@ -25,7 +25,13 @@ export const OBSIDIAN_NODE_PALETTE = Object.freeze([
25
25
  Object.freeze({ key: "steel", color: "#576f86", darkColor: "#98afc5", glow: "rgba(87,111,134,.42)", darkGlow: "rgba(152,175,197,.56)" }),
26
26
  Object.freeze({ key: "slate", color: "#626b78", darkColor: "#aab3c0", glow: "rgba(98,107,120,.42)", darkGlow: "rgba(170,179,192,.56)" }),
27
27
  Object.freeze({ key: "sand", color: "#806e58", darkColor: "#c5ad91", glow: "rgba(128,110,88,.42)", darkGlow: "rgba(197,173,145,.56)" }),
28
- Object.freeze({ key: "taupe", color: "#75636d", darkColor: "#b9a0ad", glow: "rgba(117,99,109,.42)", darkGlow: "rgba(185,160,173,.56)" })
28
+ Object.freeze({ key: "taupe", color: "#75636d", darkColor: "#b9a0ad", glow: "rgba(117,99,109,.42)", darkGlow: "rgba(185,160,173,.56)" }),
29
+ Object.freeze({ key: "gold", color: "#8b702b", darkColor: "#dfbd63", glow: "rgba(139,112,43,.42)", darkGlow: "rgba(223,189,99,.56)" }),
30
+ Object.freeze({ key: "lime", color: "#5e7a35", darkColor: "#a4cd74", glow: "rgba(94,122,53,.42)", darkGlow: "rgba(164,205,116,.56)" }),
31
+ Object.freeze({ key: "mint", color: "#397d59", darkColor: "#78ce99", glow: "rgba(57,125,89,.42)", darkGlow: "rgba(120,206,153,.56)" }),
32
+ Object.freeze({ key: "turquoise", color: "#287d78", darkColor: "#69cec8", glow: "rgba(40,125,120,.42)", darkGlow: "rgba(105,206,200,.56)" }),
33
+ Object.freeze({ key: "azure", color: "#326ea3", darkColor: "#76b7ee", glow: "rgba(50,110,163,.42)", darkGlow: "rgba(118,183,238,.56)" }),
34
+ Object.freeze({ key: "peach", color: "#a35d50", darkColor: "#eda197", glow: "rgba(163,93,80,.42)", darkGlow: "rgba(237,161,151,.56)" })
29
35
  ]);
30
36
 
31
37
  const clamp = (value, minimum, maximum) => Math.min(maximum, Math.max(minimum, value));
@@ -87,6 +93,8 @@ const GALAXY_CELESTIAL_TYPES = Object.freeze({
87
93
  outer: Object.freeze(["rocky", "ocean", "ice", "volcanic", "dwarf", "ringed"])
88
94
  });
89
95
  export const GALAXY_ROTATION_RADIANS_PER_MS = 0.000012;
96
+ export const GALAXY_BASE_STAR_COUNT = 7200;
97
+ export const GALAXY_EDGE_STAR_BOOST_RATIO = 1.1 * 1.1 - 1;
90
98
  export const GALAXY_LAYOUT_CONFIG = Object.freeze({
91
99
  minimumRadius: 220,
92
100
  radialSpan: 830,
@@ -220,15 +228,9 @@ export function resolveRelationshipNodeGroup(node) {
220
228
  return { type: "default", key: "default", label: "未分组" };
221
229
  }
222
230
 
223
- export function getObsidianNodeAppearance(node, maxDegree = 1) {
224
- const group = resolveRelationshipNodeGroup(node);
231
+ function createObsidianNodeAppearance(node, maxDegree, group, paletteIndex) {
225
232
  const degree = Math.max(0, Number(node?.degree) || 0);
226
233
  const normalizedDegree = clamp(degree / Math.max(1, Number(maxDegree) || 1), 0, 1);
227
- // 未分组角色没有可共享的分组语义,按角色稳定标识散列,避免资料不完整时大片同色。
228
- const colorKey = group.key === "default"
229
- ? `node:${String(node?.id ?? node?.name ?? "default")}`
230
- : group.key;
231
- const paletteIndex = hashString(colorKey) % OBSIDIAN_NODE_PALETTE.length;
232
234
  const palette = OBSIDIAN_NODE_PALETTE[paletteIndex];
233
235
  return {
234
236
  group,
@@ -242,6 +244,52 @@ export function getObsidianNodeAppearance(node, maxDegree = 1) {
242
244
  };
243
245
  }
244
246
 
247
+ function getObsidianNodeColorSeed(node, group = resolveRelationshipNodeGroup(node)) {
248
+ return mixHash(hashString([
249
+ String(node?.id ?? ""),
250
+ String(node?.name ?? ""),
251
+ group.key,
252
+ String(node?.species ?? ""),
253
+ String(node?.identity ?? "")
254
+ ].join("|")));
255
+ }
256
+
257
+ export function getObsidianNodeAppearance(node, maxDegree = 1) {
258
+ const group = resolveRelationshipNodeGroup(node);
259
+ const paletteIndex = getObsidianNodeColorSeed(node, group) % OBSIDIAN_NODE_PALETTE.length;
260
+ return createObsidianNodeAppearance(node, maxDegree, group, paletteIndex);
261
+ }
262
+
263
+ export function assignBalancedObsidianNodeAppearances(nodes, maxDegree = 1) {
264
+ // 高连接节点优先占用尚未使用的颜色,再按稳定散列打散同一轮的色槽顺序。
265
+ const orderedNodes = [...(Array.isArray(nodes) ? nodes : [])].sort((left, right) => (
266
+ Number(right?.degree ?? 0) - Number(left?.degree ?? 0)
267
+ || Number(right?.weightedDegree ?? 0) - Number(left?.weightedDegree ?? 0)
268
+ || String(left?.name ?? "").localeCompare(String(right?.name ?? ""), "zh-CN")
269
+ || String(left?.id ?? "").localeCompare(String(right?.id ?? ""))
270
+ ));
271
+ const paletteUsage = Array.from({ length: OBSIDIAN_NODE_PALETTE.length }, () => 0);
272
+ const appearances = new Map();
273
+ for (const node of orderedNodes) {
274
+ const group = resolveRelationshipNodeGroup(node);
275
+ const seed = getObsidianNodeColorSeed(node, group);
276
+ const minimumUsage = Math.min(...paletteUsage);
277
+ let paletteIndex = 0;
278
+ let bestScore = -1;
279
+ for (let index = 0; index < paletteUsage.length; index += 1) {
280
+ if (paletteUsage[index] !== minimumUsage) continue;
281
+ const score = mixHash(seed ^ Math.imul(index + 1, 0x9e3779b1));
282
+ if (score > bestScore) {
283
+ bestScore = score;
284
+ paletteIndex = index;
285
+ }
286
+ }
287
+ paletteUsage[paletteIndex] += 1;
288
+ appearances.set(String(node?.id ?? node?.name ?? appearances.size), createObsidianNodeAppearance(node, maxDegree, group, paletteIndex));
289
+ }
290
+ return appearances;
291
+ }
292
+
245
293
  function hashString(value) {
246
294
  let hash = 2166136261;
247
295
  for (let index = 0; index < value.length; index += 1) {
@@ -341,7 +389,10 @@ export function buildRelationshipGraph(characters, relationships) {
341
389
  const maxDegree = Math.max(1, ...nodes.map((node) => node.degree));
342
390
  for (const node of nodes) {
343
391
  node.importance = node.weightedDegree + Math.sqrt(node.degree) * 0.8;
344
- const appearance = getObsidianNodeAppearance(node, maxDegree);
392
+ }
393
+ const nodeAppearances = assignBalancedObsidianNodeAppearances(nodes, maxDegree);
394
+ for (const node of nodes) {
395
+ const appearance = nodeAppearances.get(node.id) ?? getObsidianNodeAppearance(node, maxDegree);
345
396
  node.color = appearance.color;
346
397
  node.darkColor = appearance.darkColor;
347
398
  node.glow = appearance.glow;
@@ -854,7 +905,7 @@ export function renderRelationshipMindMap(container, graph, options = {}) {
854
905
 
855
906
  const focusBadge = document.createElement("div");
856
907
  focusBadge.className = "relationship-network-focus";
857
- focusBadge.innerHTML = "<strong>人物关系图谱</strong><span>力导向布局 · 按阵营/种族着色</span>";
908
+ focusBadge.innerHTML = "<strong>人物关系图谱</strong><span>力导向布局 · 稳定均衡配色</span>";
858
909
  const focusText = focusBadge.querySelector("span");
859
910
  const help = document.createElement("div");
860
911
  help.className = "relationship-network-help";
@@ -1212,7 +1263,7 @@ export function renderRelationshipMindMap(container, graph, options = {}) {
1212
1263
  edgeElements.forEach((item) => {
1213
1264
  item.path.classList.remove("is-highlighted", "is-dimmed");
1214
1265
  });
1215
- focusText.textContent = "力导向布局 · 按阵营/种族着色";
1266
+ focusText.textContent = "力导向布局 · 稳定均衡配色";
1216
1267
  edgeDetail.classList.add("hidden");
1217
1268
  edgeDetail.replaceChildren();
1218
1269
  };
@@ -1629,26 +1680,32 @@ export function layoutGalaxy(graph, seed) {
1629
1680
  return { nodes, byId };
1630
1681
  }
1631
1682
 
1632
- export function createGalaxyStarfield(seed, count = 7200) {
1683
+ export function createGalaxyStarfield(seed, count) {
1633
1684
  const random = seededRandom(hashString(seed));
1634
1685
  const stars = [];
1635
1686
  const armCount = 4;
1636
- for (let index = 0; index < count; index += 1) {
1687
+ const baseCount = count === undefined ? GALAXY_BASE_STAR_COUNT : Math.max(0, Math.floor(Number(count) || 0));
1688
+ const edgeBoostCount = count === undefined ? Math.round(baseCount * GALAXY_EDGE_STAR_BOOST_RATIO) : 0;
1689
+ const totalCount = baseCount + edgeBoostCount;
1690
+ for (let index = 0; index < totalCount; index += 1) {
1691
+ const isEdgeBoost = index >= baseCount;
1637
1692
  const population = random();
1638
- const isCore = population < 0.62;
1639
- const isHalo = !isCore && population > 0.9;
1640
- const radius = isCore
1693
+ const isCore = !isEdgeBoost && population < 0.62;
1694
+ const isHalo = !isEdgeBoost && !isCore && population > 0.9;
1695
+ const radius = isEdgeBoost
1696
+ ? 900 + Math.pow(random(), 0.82) * 900
1697
+ : isCore
1641
1698
  ? 32 + Math.pow(random(), 1.72) * 720
1642
1699
  : 160 + Math.pow(random(), 0.68) * 1510;
1643
1700
  const arm = index % armCount;
1644
1701
  const armAngle = arm / armCount * Math.PI * 2;
1645
1702
  const angle = isHalo
1646
1703
  ? random() * Math.PI * 2
1647
- : armAngle + radius * 0.0065 + (random() - 0.5) * (isCore ? 0.72 : 0.42 + radius / 1100);
1648
- const thickness = isHalo ? 70 + radius * 0.2 : (isCore ? 8 + radius * 0.045 : 22 + radius * 0.105);
1704
+ : armAngle + radius * 0.0065 + (random() - 0.5) * (isEdgeBoost ? 0.36 + radius / 1700 : isCore ? 0.72 : 0.42 + radius / 1100);
1705
+ const thickness = isHalo ? 70 + radius * 0.2 : (isEdgeBoost ? 26 + radius * 0.11 : isCore ? 8 + radius * 0.045 : 22 + radius * 0.105);
1649
1706
  const temperature = random();
1650
- const x = Math.cos(angle) * radius + (random() - 0.5) * (isCore ? 42 : 78);
1651
- const z = Math.sin(angle) * radius + (random() - 0.5) * (isCore ? 42 : 78);
1707
+ const x = Math.cos(angle) * radius + (random() - 0.5) * (isCore ? 42 : isEdgeBoost ? 68 : 78);
1708
+ const z = Math.sin(angle) * radius + (random() - 0.5) * (isCore ? 42 : isEdgeBoost ? 68 : 78);
1652
1709
  stars.push({
1653
1710
  x,
1654
1711
  y: (random() + random() + random() - 1.5) * thickness,
@@ -1659,7 +1716,8 @@ export function createGalaxyStarfield(seed, count = 7200) {
1659
1716
  vz: 0,
1660
1717
  size: isCore && random() > 0.94 ? 1.25 + random() * 1.25 : 0.38 + random() * 0.82,
1661
1718
  brightness: isCore ? 0.3 + random() * 0.7 : 0.16 + random() * 0.66,
1662
- color: temperature < 0.2 ? "255,218,176" : temperature > 0.78 ? "174,211,255" : "226,237,255"
1719
+ color: temperature < 0.2 ? "255,218,176" : temperature > 0.78 ? "174,211,255" : "226,237,255",
1720
+ region: isEdgeBoost ? "edge-arm" : isCore ? "core" : isHalo ? "halo" : "arm"
1663
1721
  });
1664
1722
  }
1665
1723
  return stars;
@@ -547,28 +547,39 @@ body.is-panel-resizing { cursor: col-resize; user-select: none; }
547
547
  .settings-parent-button { white-space: nowrap; }
548
548
  .settings-hub-header { align-items: center; }
549
549
  .settings-hub-header h1 { font-size: clamp(26px, 3vw, 36px); line-height: 1.15; letter-spacing: -.02em; }
550
- .settings-hub-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; max-width: 1000px; margin: 0 auto; }
550
+ .settings-hub-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; width: 100%; max-width: 1000px; margin: 0 auto; }
551
551
  .settings-hub-card { display: grid; grid-template-columns: 48px minmax(0, 1fr); grid-template-rows: auto auto; gap: 4px 14px; align-items: center; min-height: 128px; padding: 22px; border: 1px solid var(--line); border-radius: 7px; background: var(--surface-soft); text-align: left; }
552
552
  .settings-hub-card:hover, .settings-hub-card:focus-visible { border-color: var(--accent); background: var(--paper-deep); box-shadow: var(--shadow); transform: translateY(-2px); }
553
553
  .settings-hub-card:disabled { cursor: not-allowed; opacity: .42; box-shadow: none; transform: none; }
554
554
  .settings-card-mark { display: grid; grid-row: 1 / 3; place-items: center; width: 48px; height: 48px; border: 1px solid var(--line); border-radius: 50%; color: var(--accent-dark); background: var(--surface); font-size: 14px; font-weight: 700; }
555
555
  .settings-hub-card strong { align-self: end; font-size: 16px; }
556
556
  .settings-hub-card small { align-self: start; color: var(--muted); font-size: 10px; line-height: 1.5; }
557
- .writing-progress-dialog { width: min(900px, 94vw); }
557
+ .dialog.writing-progress-dialog { width: min(900px, 94vw); }
558
558
  .writing-progress-dialog form { display: grid; }
559
- .writing-progress-body { display: grid; gap: 18px; padding: 20px 24px; }
559
+ .writing-progress-body { display: grid; gap: 18px; max-height: calc(88vh - 150px); padding: 20px 24px; overflow-y: auto; }
560
560
  .writing-progress-stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
561
- .writing-progress-stats article { display: grid; gap: 6px; padding: 13px; border: 1px solid var(--line); border-radius: 5px; background: var(--surface-soft); }
561
+ .writing-progress-stats article { display: grid; align-content: center; gap: 7px; min-height: 78px; padding: 13px 15px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface-soft); }
562
562
  .writing-progress-stats small { color: var(--muted); font-size: 9px; }
563
- .writing-progress-stats strong { font: 20px/1.2 var(--font-mono), monospace; }
564
- .writing-trend-chart { display: grid; grid-template-columns: repeat(30, minmax(4px, 1fr)); align-items: end; gap: 3px; height: 190px; padding: 12px 10px 24px; border: 1px solid var(--line); border-radius: 5px; background: linear-gradient(to top, var(--line) 1px, transparent 1px) 0 0 / 100% 25%; }
565
- .writing-trend-bar { display: grid; grid-template-rows: 1fr 18px; align-items: end; height: 100%; min-width: 0; }
566
- .writing-trend-bar i { display: block; width: 100%; height: var(--bar-height); min-height: 3px; border-radius: 2px 2px 0 0; background: color-mix(in srgb, var(--accent) 62%, var(--surface)); }
563
+ .writing-progress-stats strong { font: 20px/1.2 var(--font-latin), monospace; }
564
+ .writing-trend-chart { position: relative; display: grid; grid-template-columns: repeat(30, minmax(4px, 1fr)); align-items: end; gap: 3px; height: 210px; margin-top: 14px; padding: 14px 10px 10px; border: 1px solid var(--line); border-radius: 6px; background: linear-gradient(to top, var(--panel) 0 22px, transparent 22px), linear-gradient(to top, var(--line) 1px, transparent 1px) 0 0 / 100% 25%; }
565
+ .writing-trend-bar { display: grid; grid-template-rows: minmax(0, 1fr) 22px; align-items: end; height: 100%; min-width: 0; cursor: default; }
566
+ .writing-trend-bar:focus-visible { outline: none; }
567
+ .writing-trend-bar i { display: block; justify-self: center; width: clamp(4px, 68%, 14px); height: var(--bar-height); min-height: 3px; border-radius: 4px 4px 1px 1px; background: color-mix(in srgb, var(--accent) 62%, var(--surface)); transition: background-color .14s ease, box-shadow .14s ease, transform .14s ease; }
567
568
  .writing-trend-bar.is-today i { background: var(--accent); }
568
- .writing-trend-bar small { overflow: visible; color: var(--muted); font: 7px/18px var(--font-mono), monospace; white-space: nowrap; transform: rotate(-42deg); transform-origin: left center; }
569
+ .writing-trend-bar:hover i, .writing-trend-bar:focus-visible i { background: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 20%, transparent); transform: translateY(-2px); }
570
+ .writing-trend-bar small { align-self: center; justify-self: center; overflow: visible; color: var(--muted); font: 7px/22px var(--font-latin), monospace; white-space: nowrap; }
571
+ .writing-trend-daily-line { position: absolute; z-index: 1; top: 14px; right: 10px; bottom: 32px; left: 10px; width: calc(100% - 20px); height: calc(100% - 46px); overflow: visible; pointer-events: none; }
572
+ .writing-trend-daily-line polyline { fill: none; stroke: var(--green); stroke-width: 3; vector-effect: non-scaling-stroke; }
573
+ .writing-trend-daily-points { position: absolute; z-index: 2; top: 14px; right: 10px; bottom: 32px; left: 10px; pointer-events: none; }
574
+ .writing-trend-daily-points i { position: absolute; top: var(--point-y); left: var(--point-x); box-sizing: border-box; width: 7px; height: 7px; border: 2px solid var(--green); border-radius: 50%; background: var(--panel); transform: translate(-50%, -50%); }
575
+ .writing-trend-legend { position: absolute; z-index: 2; top: -27px; right: 0; display: flex; align-items: center; gap: 5px; padding: 3px 0; color: var(--muted); font: 8px/1.4 var(--font-latin), monospace; pointer-events: none; }
576
+ .writing-trend-legend i { width: 10px; height: 5px; border-radius: 2px 2px 0 0; background: color-mix(in srgb, var(--accent) 62%, var(--surface)); }
577
+ .writing-trend-legend b { width: 12px; height: 0; margin-left: 3px; border-top: 2px solid var(--green); }
578
+ .writing-trend-tooltip { position: absolute; z-index: 3; width: 250px; max-width: calc(100% - 16px); padding: 7px 9px; border: 1px solid color-mix(in srgb, var(--ink) 24%, transparent); border-radius: 5px; background: color-mix(in srgb, var(--ink) 94%, transparent); box-shadow: 0 8px 22px rgba(0,0,0,.22); color: var(--panel); font: 10px/1.45 var(--font-latin), monospace; overflow-wrap: anywhere; text-align: center; pointer-events: none; transform: translate(-50%, -100%); }
579
+ .writing-trend-tooltip[hidden] { display: none; }
569
580
  .writing-goal-fields { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
570
- .writing-goal-fields label { display: grid; gap: 5px; color: var(--muted); font-size: 10px; }
571
- .writing-goal-fields input { width: 100%; }
581
+ .writing-goal-fields label { display: grid; gap: 7px; color: var(--muted); font-size: 10px; font-weight: 650; }
582
+ .writing-goal-fields input { width: 100%; min-height: 38px; padding: 8px 10px; background: var(--surface); font-size: 12px; font-weight: 500; }
572
583
  @media (max-width: 680px) { .writing-progress-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } .writing-goal-fields { grid-template-columns: 1fr; } }
573
584
  .work-audit-dialog { width: min(860px, 94vw); }
574
585
  .work-audit-list { display: grid; max-height: 62vh; overflow: auto; }
@@ -1080,7 +1091,9 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1080
1091
  #module-more-button { grid-column: 1 / -1; color: var(--accent-dark); text-align: center; }
1081
1092
  .panel-heading { display: flex; align-items: center; gap: 8px; padding: 15px 0 9px 7px; color: var(--muted); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; }
1082
1093
  .panel-heading-actions { display: flex; align-items: center; gap: 7px; margin-left: auto; }
1083
- .chapter-batch-button { min-height: 26px; padding: 4px 7px; border: 1px solid var(--line); border-radius: 4px; background: transparent; color: var(--muted); font-size: 9px; white-space: nowrap; }
1094
+ .chapter-batch-button { display: grid; flex: none; place-items: center; width: 24px; height: 24px; padding: 0; border: 1px solid var(--line); border-radius: 4px; background: transparent; color: var(--muted); }
1095
+ .chapter-batch-button svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
1096
+ .chapter-batch-button:hover, .chapter-batch-button:focus-visible { border-color: var(--accent); color: var(--accent-dark); outline: none; }
1084
1097
  .novel-tree { font-size: 13px; }
1085
1098
  .empty-copy { color: var(--muted); padding: 14px 7px; line-height: 1.5; }
1086
1099
  .volume-node { margin-bottom: 8px; }
@@ -1108,14 +1121,25 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1108
1121
  .chapter-batch-dialog form { display: grid; gap: 14px; }
1109
1122
  .chapter-batch-toolbar { display: flex; align-items: center; gap: 8px; margin: 0 24px; }
1110
1123
  .chapter-batch-toolbar output { margin-left: auto; color: var(--muted); font-size: 11px; }
1124
+ .chapter-batch-search { display: grid; gap: 6px; margin: 0 24px; }
1125
+ .chapter-batch-search > label { color: var(--muted); font-size: 10px; font-weight: 650; }
1126
+ .chapter-batch-search-field { position: relative; display: flex; align-items: center; }
1127
+ .chapter-batch-search-field svg { position: absolute; left: 11px; width: 15px; height: 15px; fill: none; stroke: var(--muted); stroke-width: 1.7; stroke-linecap: round; pointer-events: none; }
1128
+ .chapter-batch-search-field input { width: 100%; min-height: 38px; padding: 8px 68px 8px 34px; background: var(--surface); font-size: 12px; }
1129
+ .chapter-batch-search-field input::-webkit-search-cancel-button { cursor: pointer; }
1130
+ .chapter-batch-search-field output { position: absolute; right: 11px; color: var(--muted); font-size: 10px; font-variant-numeric: tabular-nums; pointer-events: none; }
1111
1131
  .chapter-batch-list { display: grid; max-height: min(44vh, 420px); margin: 0 24px; overflow: auto; border: 1px solid var(--line); border-radius: 5px; }
1112
1132
  .chapter-batch-item { display: grid; grid-template-columns: 18px minmax(0, 1fr); align-items: center; gap: 9px; padding: 9px 11px; border-bottom: 1px solid var(--line); }
1113
1133
  .chapter-batch-item:last-child { border-bottom: 0; }
1114
1134
  .chapter-batch-item span { display: grid; min-width: 0; gap: 2px; }
1115
1135
  .chapter-batch-item strong { overflow: hidden; font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
1116
1136
  .chapter-batch-item small { color: var(--muted); font-size: 9px; }
1117
- .chapter-batch-controls { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 0 24px; }
1118
- .chapter-batch-controls label { display: grid; gap: 5px; color: var(--muted); font-size: 10px; }
1137
+ .chapter-batch-controls { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; margin: 0 24px; padding: 14px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface-soft); }
1138
+ .chapter-batch-controls label { display: grid; min-width: 0; gap: 7px; color: var(--muted); font-size: 11px; font-weight: 650; line-height: 1.35; }
1139
+ .chapter-batch-controls select { width: 100%; min-width: 0; min-height: 40px; appearance: none; -webkit-appearance: none; padding: 8px 40px 8px 12px; border-color: color-mix(in srgb, var(--line) 82%, var(--ink)); border-radius: 5px; background-color: var(--surface); background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%); background-position: right 20px center, right 15px center; background-repeat: no-repeat; background-size: 5px 5px, 5px 5px; color: var(--ink); font-size: 12px; font-weight: 500; line-height: 1.4; cursor: pointer; transition: border-color .15s ease, box-shadow .15s ease, background-color .15s ease; }
1140
+ .chapter-batch-controls select:hover:not(:disabled) { border-color: color-mix(in srgb, var(--accent) 58%, var(--line)); }
1141
+ .chapter-batch-controls select:focus-visible { border-color: var(--accent); outline: none; box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
1142
+ .chapter-batch-controls select:disabled { cursor: not-allowed; opacity: .52; }
1119
1143
  @media (max-width: 620px) { .chapter-batch-controls { grid-template-columns: 1fr; } }
1120
1144
 
1121
1145
  .ghost-button, .primary-button { border-radius: 4px; padding: 8px 13px; font-size: 12px; }
@@ -2684,7 +2708,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
2684
2708
  .task-auto-run-progress { grid-column: 1; grid-row: auto; justify-self: stretch; width: 100%; }
2685
2709
  .task-auto-run-progress-ring { display: none; }
2686
2710
  .task-auto-run-progress-bar-layout { display: grid; gap: 6px; width: 100%; }
2687
- .settings-hub-grid { grid-template-columns: 1fr; }
2711
+ .settings-hub-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
2688
2712
  .usage-stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
2689
2713
  .settings-layout-toolbar, .module-layout-toolbar { justify-content: stretch; }
2690
2714
  .settings-layout-toggle, .module-layout-toggle { width: 100%; }
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const APP_VERSION = "0.5.6";
1
+ export const APP_VERSION = "0.5.7";
2
2
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@musnows/scriverse",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "Command-line client and local server for the Scriverse long-form fiction workspace",
5
5
  "license": "AGPL-3.0-only",
6
6
  "author": "musnows",
@@ -51,6 +51,7 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "express": "^5.1.0",
54
+ "jszip": "^3.10.1",
54
55
  "mammoth": "^1.10.0",
55
56
  "multer": "^2.0.2",
56
57
  "pinyin-pro": "^3.28.2",
@@ -64,7 +65,6 @@
64
65
  "@types/multer": "^2.0.0",
65
66
  "@types/node": "^24.10.0",
66
67
  "@types/supertest": "^6.0.3",
67
- "jszip": "^3.10.1",
68
68
  "supertest": "^7.1.4",
69
69
  "tsx": "^4.20.6",
70
70
  "typescript": "^5.9.3",