@musnows/scriverse 0.4.3 → 0.4.5

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.
@@ -1551,29 +1551,87 @@ export function layoutGalaxy(graph, seed) {
1551
1551
  return { nodes, byId };
1552
1552
  }
1553
1553
 
1554
- export function createGalaxyStarfield(seed, count = 3600) {
1554
+ export function createGalaxyStarfield(seed, count = 7200) {
1555
1555
  const random = seededRandom(hashString(seed));
1556
1556
  const stars = [];
1557
1557
  const armCount = 4;
1558
1558
  for (let index = 0; index < count; index += 1) {
1559
- const radius = 55 + Math.pow(random(), 0.62) * 1380;
1559
+ const population = random();
1560
+ const isCore = population < 0.62;
1561
+ const isHalo = !isCore && population > 0.9;
1562
+ const radius = isCore
1563
+ ? 32 + Math.pow(random(), 1.72) * 720
1564
+ : 160 + Math.pow(random(), 0.68) * 1510;
1560
1565
  const arm = index % armCount;
1561
1566
  const armAngle = arm / armCount * Math.PI * 2;
1562
- const angle = armAngle + radius * 0.0065 + (random() - 0.5) * (0.42 + radius / 1100);
1563
- const thickness = 22 + radius * 0.105;
1567
+ const angle = isHalo
1568
+ ? random() * Math.PI * 2
1569
+ : armAngle + radius * 0.0065 + (random() - 0.5) * (isCore ? 0.72 : 0.42 + radius / 1100);
1570
+ const thickness = isHalo ? 70 + radius * 0.2 : (isCore ? 8 + radius * 0.045 : 22 + radius * 0.105);
1564
1571
  const temperature = random();
1572
+ const x = Math.cos(angle) * radius + (random() - 0.5) * (isCore ? 42 : 78);
1573
+ const z = Math.sin(angle) * radius + (random() - 0.5) * (isCore ? 42 : 78);
1565
1574
  stars.push({
1566
- x: Math.cos(angle) * radius + (random() - 0.5) * 62,
1575
+ x,
1567
1576
  y: (random() + random() + random() - 1.5) * thickness,
1568
- z: Math.sin(angle) * radius + (random() - 0.5) * 62,
1569
- size: random() > 0.965 ? 1.7 + random() * 1.4 : 0.45 + random() * 0.85,
1570
- brightness: 0.22 + random() * 0.78,
1577
+ z,
1578
+ originX: x,
1579
+ originZ: z,
1580
+ vx: 0,
1581
+ vz: 0,
1582
+ size: isCore && random() > 0.94 ? 1.25 + random() * 1.25 : 0.38 + random() * 0.82,
1583
+ brightness: isCore ? 0.3 + random() * 0.7 : 0.16 + random() * 0.66,
1571
1584
  color: temperature < 0.2 ? "255,218,176" : temperature > 0.78 ? "174,211,255" : "226,237,255"
1572
1585
  });
1573
1586
  }
1574
1587
  return stars;
1575
1588
  }
1576
1589
 
1590
+ export function stepGalaxyStarfieldPhysics(stars, attractor = null, options = {}) {
1591
+ const deltaMs = clamp(Number(options.deltaMs) || 16.667, 1, 50);
1592
+ const timeScale = deltaMs / 16.667;
1593
+ const influenceRadius = Math.max(1, Number(options.influenceRadius) || 210);
1594
+ const repulsionStrength = Number(options.repulsionStrength) || 5.5;
1595
+ const springStrength = Number(options.springStrength) || 0.006;
1596
+ const damping = clamp(Number(options.damping) || 0.84, 0, 1);
1597
+ let energy = 0;
1598
+
1599
+ for (const star of stars) {
1600
+ const originX = Number(star.originX ?? star.x);
1601
+ const originZ = Number(star.originZ ?? star.z);
1602
+ star.originX = originX;
1603
+ star.originZ = originZ;
1604
+ star.vx = Number(star.vx) || 0;
1605
+ star.vz = Number(star.vz) || 0;
1606
+
1607
+ if (attractor) {
1608
+ const deltaX = star.x - attractor.x;
1609
+ const deltaZ = star.z - attractor.z;
1610
+ const distance = Math.hypot(deltaX, deltaZ);
1611
+ if (distance < influenceRadius) {
1612
+ const angle = Math.atan2(star.originZ, star.originX);
1613
+ const normalX = distance > 0.001 ? deltaX / distance : Math.cos(angle);
1614
+ const normalZ = distance > 0.001 ? deltaZ / distance : Math.sin(angle);
1615
+ const falloff = 1 - distance / influenceRadius;
1616
+ const force = falloff * falloff * repulsionStrength * timeScale;
1617
+ star.vx += normalX * force;
1618
+ star.vz += normalZ * force;
1619
+ }
1620
+ }
1621
+
1622
+ star.vx += (originX - star.x) * springStrength * timeScale;
1623
+ star.vz += (originZ - star.z) * springStrength * timeScale;
1624
+ const frameDamping = Math.pow(damping, timeScale);
1625
+ star.vx *= frameDamping;
1626
+ star.vz *= frameDamping;
1627
+ star.x += star.vx * timeScale;
1628
+ star.z += star.vz * timeScale;
1629
+ energy += Math.abs(star.vx) + Math.abs(star.vz) + Math.abs(originX - star.x) + Math.abs(originZ - star.z);
1630
+ }
1631
+
1632
+ return energy;
1633
+ }
1634
+
1577
1635
  export function projectGalaxyPoint(point, camera, viewport) {
1578
1636
  const relativeX = point.x - Number(camera.targetX ?? 0);
1579
1637
  const relativeY = point.y - Number(camera.targetY ?? 0);
@@ -1697,13 +1755,18 @@ export function createGalaxyRenderer(dialog, graph, options = {}) {
1697
1755
  let animationFrame = 0;
1698
1756
  let previousFrameTime = 0;
1699
1757
  let cameraFocus = null;
1758
+ let draggedNode = null;
1759
+ let starPhysicsEnergy = 0;
1700
1760
  let paused = false;
1701
1761
  let starsVisible = true;
1762
+ let gridVisible = false;
1702
1763
  let destroyed = false;
1703
1764
 
1704
1765
  shell.classList.add("is-three-dimensional");
1705
1766
  shell.dataset.sceneDimension = "3";
1706
1767
  shell.dataset.starCount = String(stars.length);
1768
+ shell.dataset.gridVisible = "false";
1769
+ shell.dataset.starPhysicsEnergy = "0";
1707
1770
  shell.dataset.rotationSpeed = String(GALAXY_ROTATION_RADIANS_PER_MS);
1708
1771
  shell.dataset.layoutMinimumRadius = String(GALAXY_LAYOUT_CONFIG.minimumRadius);
1709
1772
  shell.dataset.layoutRadialSpan = String(GALAXY_LAYOUT_CONFIG.radialSpan);
@@ -1743,24 +1806,26 @@ export function createGalaxyRenderer(dialog, graph, options = {}) {
1743
1806
  context.fillStyle = backdrop;
1744
1807
  context.fillRect(0, 0, width, height);
1745
1808
 
1746
- context.lineWidth = 1;
1747
- context.strokeStyle = "rgba(105,142,182,.06)";
1748
- for (let offset = -1200; offset <= 1200; offset += 160) {
1749
- const horizontalStart = project({ x: -1200, y: 0, z: offset }, width, height);
1750
- const horizontalEnd = project({ x: 1200, y: 0, z: offset }, width, height);
1751
- const verticalStart = project({ x: offset, y: 0, z: -1200 }, width, height);
1752
- const verticalEnd = project({ x: offset, y: 0, z: 1200 }, width, height);
1753
- if (horizontalStart.visible && horizontalEnd.visible) {
1754
- context.beginPath();
1755
- context.moveTo(horizontalStart.x, horizontalStart.y);
1756
- context.lineTo(horizontalEnd.x, horizontalEnd.y);
1757
- context.stroke();
1758
- }
1759
- if (verticalStart.visible && verticalEnd.visible) {
1760
- context.beginPath();
1761
- context.moveTo(verticalStart.x, verticalStart.y);
1762
- context.lineTo(verticalEnd.x, verticalEnd.y);
1763
- context.stroke();
1809
+ if (gridVisible) {
1810
+ context.lineWidth = 1;
1811
+ context.strokeStyle = "rgba(105,142,182,.06)";
1812
+ for (let offset = -1200; offset <= 1200; offset += 160) {
1813
+ const horizontalStart = project({ x: -1200, y: 0, z: offset }, width, height);
1814
+ const horizontalEnd = project({ x: 1200, y: 0, z: offset }, width, height);
1815
+ const verticalStart = project({ x: offset, y: 0, z: -1200 }, width, height);
1816
+ const verticalEnd = project({ x: offset, y: 0, z: 1200 }, width, height);
1817
+ if (horizontalStart.visible && horizontalEnd.visible) {
1818
+ context.beginPath();
1819
+ context.moveTo(horizontalStart.x, horizontalStart.y);
1820
+ context.lineTo(horizontalEnd.x, horizontalEnd.y);
1821
+ context.stroke();
1822
+ }
1823
+ if (verticalStart.visible && verticalEnd.visible) {
1824
+ context.beginPath();
1825
+ context.moveTo(verticalStart.x, verticalStart.y);
1826
+ context.lineTo(verticalEnd.x, verticalEnd.y);
1827
+ context.stroke();
1828
+ }
1764
1829
  }
1765
1830
  }
1766
1831
 
@@ -1938,6 +2003,10 @@ export function createGalaxyRenderer(dialog, graph, options = {}) {
1938
2003
  }
1939
2004
  }
1940
2005
  if (!paused && !cameraDrag) camera.yaw += elapsed * GALAXY_ROTATION_RADIANS_PER_MS;
2006
+ if (draggedNode || starPhysicsEnergy > 0.01) {
2007
+ starPhysicsEnergy = stepGalaxyStarfieldPhysics(stars, draggedNode, { deltaMs: elapsed || 16.667 });
2008
+ shell.dataset.starPhysicsEnergy = starPhysicsEnergy.toFixed(3);
2009
+ }
1941
2010
  drawScene();
1942
2011
  animationFrame = window.requestAnimationFrame(renderFrame);
1943
2012
  };
@@ -2072,6 +2141,7 @@ export function createGalaxyRenderer(dialog, graph, options = {}) {
2072
2141
  scale: Number(button.dataset.projectedScale) || 1,
2073
2142
  dragged: false
2074
2143
  };
2144
+ draggedNode = node;
2075
2145
  button.setPointerCapture(event.pointerId);
2076
2146
  button.classList.add("is-dragging");
2077
2147
  button.setAttribute("aria-grabbed", "true");
@@ -2099,6 +2169,7 @@ export function createGalaxyRenderer(dialog, graph, options = {}) {
2099
2169
  if (!nodeDrag || event.pointerId !== nodeDrag.pointerId) return;
2100
2170
  suppressClick = nodeDrag.dragged;
2101
2171
  nodeDrag = null;
2172
+ if (draggedNode === node) draggedNode = null;
2102
2173
  button.classList.remove("is-dragging");
2103
2174
  button.setAttribute("aria-grabbed", "false");
2104
2175
  if (button.hasPointerCapture(event.pointerId)) button.releasePointerCapture(event.pointerId);
@@ -2156,9 +2227,13 @@ export function createGalaxyRenderer(dialog, graph, options = {}) {
2156
2227
  if (!dialog.open) dialog.showModal();
2157
2228
  paused = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
2158
2229
  starsVisible = true;
2230
+ gridVisible = false;
2159
2231
  background.classList.remove("hidden-stars");
2232
+ shell.dataset.gridVisible = "false";
2160
2233
  dialog.querySelector("#galaxy-stars").setAttribute("aria-pressed", "true");
2161
2234
  dialog.querySelector("#galaxy-stars").textContent = "隐藏背景星点";
2235
+ dialog.querySelector("#galaxy-grid").setAttribute("aria-pressed", "false");
2236
+ dialog.querySelector("#galaxy-grid").textContent = "显示空间网格";
2162
2237
  updateRotationControl();
2163
2238
  stats.value = `${graph.stats.nodeCount} 个节点 / ${graph.stats.edgeCount} 条关系`;
2164
2239
  renderNodes();
@@ -2182,6 +2257,13 @@ export function createGalaxyRenderer(dialog, graph, options = {}) {
2182
2257
  event.currentTarget.textContent = starsVisible ? "隐藏背景星点" : "显示背景星点";
2183
2258
  drawScene();
2184
2259
  });
2260
+ listen(dialog.querySelector("#galaxy-grid"), "click", (event) => {
2261
+ gridVisible = !gridVisible;
2262
+ shell.dataset.gridVisible = String(gridVisible);
2263
+ event.currentTarget.setAttribute("aria-pressed", String(gridVisible));
2264
+ event.currentTarget.textContent = gridVisible ? "隐藏空间网格" : "显示空间网格";
2265
+ drawScene();
2266
+ });
2185
2267
  listen(dialog.querySelector("#galaxy-rotation"), "click", () => {
2186
2268
  paused = !paused;
2187
2269
  updateRotationControl();
@@ -65,6 +65,9 @@
65
65
  }
66
66
 
67
67
  .auth-pending .app-shell { pointer-events: none; user-select: none; }
68
+ .dev-auth-bypass .auth-view { display: none !important; }
69
+ .dev-auth-bypass .auth-pending .app-shell { pointer-events: auto; user-select: auto; }
70
+ .dev-auth-bypass .auth-loading { display: none !important; }
68
71
  .auth-loading { display: none; }
69
72
  .auth-pending .auth-loading { position: fixed; inset: 0; z-index: 900; display: grid; place-items: center; pointer-events: none; }
70
73
  .auth-loading::before { content: ""; width: 32px; height: 32px; border: 3px solid color-mix(in srgb, var(--accent) 25%, transparent); border-top-color: var(--accent); border-radius: 50%; animation: auth-loading-spin .8s linear infinite; }
@@ -342,7 +345,7 @@ input[type="checkbox"][hidden] { display: none; }
342
345
  align-items: center;
343
346
  border-bottom: 1px solid var(--line);
344
347
  background: var(--panel);
345
- z-index: 5;
348
+ z-index: 20;
346
349
  }
347
350
 
348
351
  .brand-block { display: flex; align-items: center; gap: 11px; padding: 0 20px; border: 0; background: transparent; text-align: left; }
@@ -352,6 +355,8 @@ input[type="checkbox"][hidden] { display: none; }
352
355
  .brand-block small { color: var(--muted); font-size: 10px; margin-top: 2px; }
353
356
  .work-meta { font-size: 13px; color: var(--muted); padding-left: 26px; }
354
357
  .top-actions { position: relative; display: flex; justify-content: flex-end; gap: 10px; align-items: center; padding-right: 18px; }
358
+ .mobile-module-tab { display: none; }
359
+ .mobile-panel-backdrop { display: none; }
355
360
  .presence-control { position: relative; }
356
361
  .presence-button { display: flex; align-items: center; gap: 7px; min-height: 34px; padding: 0 11px; border: 1px solid var(--line); border-radius: 18px; background: transparent; color: var(--muted); font-size: 11px; }
357
362
  .presence-button:hover, .presence-button:focus-visible, .presence-button[aria-expanded="true"] { border-color: var(--accent); background: var(--paper-deep); color: var(--accent-dark); outline: none; }
@@ -872,7 +877,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
872
877
  .chapter-add-button { flex: none; }
873
878
  .volume-chapters { display: block; }
874
879
  .volume-node.is-collapsed .volume-chapters { display: none; }
875
- .chapter-node { display: grid; grid-template-columns: minmax(0, 1fr) max-content; gap: 8px; width: 100%; padding: 9px 0 9px 20px; border: 0; background: transparent; border-radius: 4px; text-align: left; font-size: 12px; }
880
+ .chapter-node { display: grid; grid-template-columns: minmax(0, 1fr) max-content; gap: 8px; width: calc(100% + 6px); padding: 9px 6px 9px 20px; border: 0; background: transparent; border-radius: 4px; text-align: left; font-size: 12px; }
876
881
  .chapter-node:hover, .chapter-node.active { background: var(--row-active); }
877
882
  .chapter-node.active { color: var(--accent-dark); font-weight: 700; }
878
883
  .chapter-node > span:first-child { min-width: 0; overflow-wrap: anywhere; line-height: 1.45; white-space: normal; }
@@ -970,6 +975,7 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
970
975
  .module-row-path { display: block; margin-top: 2px; color: var(--accent); font-family: var(--font-latin), monospace; font-size: 11px; font-weight: 500; }
971
976
  .module-row-span { grid-column: 1 / -1; }
972
977
  .setting-row .card-actions, .module-row .card-actions { margin-top: 0; flex-wrap: wrap; justify-content: flex-end; }
978
+ .module-row .card-actions > .record-card-edit { position: static; }
973
979
  .character-card { cursor: pointer; }
974
980
  .character-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
975
981
  .character-audit-panel { display: flex; align-items: center; justify-content: space-between; gap: 18px; margin-bottom: 18px; padding: 15px 17px; border: 1px solid var(--line); background: var(--surface-soft); }
@@ -981,8 +987,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
981
987
  .character-duplicate-pair section > small { color: var(--muted); }
982
988
  .character-duplicate-pair section p { margin: 0; }
983
989
  .character-duplicate-evidence { display: grid; gap: 8px; margin: 13px 0; padding: 0; list-style: none; }
984
- .character-duplicate-evidence li { display: grid; gap: 5px; padding: 10px 12px; border-left: 2px solid var(--accent); background: var(--surface-soft); }
985
- .character-duplicate-evidence q { color: var(--text); line-height: 1.55; }
990
+ .character-duplicate-evidence li { display: grid; gap: 5px; padding: 10px 12px; border-left: 2px solid var(--accent); background: var(--surface-soft); font-size: 12px; }
991
+ .character-duplicate-evidence q { color: var(--text); font-size: 12px; line-height: 1.45; }
986
992
  .character-duplicate-evidence small, .review-resolution-note { color: var(--muted); }
987
993
  .character-duplicate-actions { flex-wrap: wrap; }
988
994
  .card-actions { display: flex; gap: 6px; margin-top: 15px; }
@@ -1333,9 +1339,9 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1333
1339
  .galaxy-node { --node-size: 12px; position: absolute; left: 0; top: 0; display: grid; grid-template-rows: var(--node-size) auto; justify-items: center; align-items: start; gap: 5px; padding: 8px; border: 0; background: transparent; color: #edf7ff; cursor: grab; touch-action: none; pointer-events: auto; transform-origin: center; opacity: var(--depth-opacity, 1); will-change: transform, opacity; }
1334
1340
  .galaxy-node i { position: relative; isolation: isolate; width: var(--node-size); height: var(--node-size); border-radius: 50%; background: radial-gradient(circle at 32% 27%, var(--node-core, #fff) 0 7%, color-mix(in srgb, var(--node-color) 62%, #fff) 18%, var(--node-color) 52%, var(--node-rim, #07101f) 100%); box-shadow: inset -3px -4px 7px color-mix(in srgb, var(--node-rim, #07101f) 74%, transparent), 0 0 4px rgba(255,255,255,.82), 0 0 calc(7px + var(--node-glow) * 11px) var(--node-color), 0 0 calc(15px + var(--node-glow) * 25px) var(--node-atmosphere, color-mix(in srgb, var(--node-color) 72%, transparent)); filter: brightness(var(--node-brightness)); transition: filter .18s ease, box-shadow .18s ease; }
1335
1341
  .galaxy-node i::before, .galaxy-node i::after { content: ""; position: absolute; pointer-events: none; }
1336
- .galaxy-node i::before { z-index: -1; inset: -42%; border-radius: 50%; background: radial-gradient(circle, var(--node-atmosphere) 0, transparent 68%); opacity: .64; }
1342
+ .galaxy-node i::before { z-index: -1; inset: -34%; border-radius: 50%; background: radial-gradient(circle, var(--node-atmosphere) 0, transparent 68%); opacity: .64; }
1337
1343
  .galaxy-node[data-celestial-type="star"] i { background: radial-gradient(circle at 38% 34%, #fff 0 10%, var(--node-core) 22%, var(--node-color) 53%, var(--node-rim) 100%); box-shadow: 0 0 5px #fff, 0 0 calc(10px + var(--node-glow) * 15px) var(--node-color), 0 0 calc(24px + var(--node-glow) * 34px) var(--node-atmosphere); }
1338
- .galaxy-node[data-celestial-type="star"] i::before { inset: -72%; opacity: .92; background: radial-gradient(circle, var(--node-atmosphere) 0 12%, transparent 66%); }
1344
+ .galaxy-node[data-celestial-type="star"] i::before { inset: -48%; opacity: .92; background: radial-gradient(circle, var(--node-atmosphere) 0 12%, transparent 66%); }
1339
1345
  .galaxy-node[data-celestial-type="gas-giant"] i { background: linear-gradient(168deg, transparent 0 17%, color-mix(in srgb, var(--node-core) 78%, transparent) 18% 25%, transparent 26% 40%, color-mix(in srgb, var(--node-rim) 64%, transparent) 41% 50%, transparent 51% 68%, color-mix(in srgb, var(--node-core) 54%, transparent) 69% 75%, transparent 76%), radial-gradient(circle at 34% 28%, var(--node-core) 0 7%, var(--node-color) 42%, var(--node-rim) 100%); }
1340
1346
  .galaxy-node[data-celestial-type="ringed"] i::after { z-index: 2; left: 50%; top: 50%; width: 168%; height: 48%; border: 1px solid var(--node-ring); border-left-color: color-mix(in srgb, var(--node-ring) 28%, transparent); border-right-color: color-mix(in srgb, var(--node-ring) 88%, #fff); border-radius: 50%; box-shadow: 0 0 3px var(--node-atmosphere); transform: translate(-50%, -50%) rotate(-19deg); }
1341
1347
  .galaxy-node[data-celestial-type="ocean"] i { background: radial-gradient(ellipse at 67% 63%, color-mix(in srgb, var(--node-core) 48%, transparent) 0 10%, transparent 12%), radial-gradient(circle at 28% 24%, var(--node-core) 0 6%, var(--node-color) 38%, color-mix(in srgb, var(--node-color) 58%, #0b2f62) 72%, var(--node-rim) 100%); }
@@ -1695,6 +1701,8 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1695
1701
  .entity-editor-header, .character-editor-header { display: grid; grid-template-columns: minmax(160px, 1fr) minmax(0, 2fr) minmax(160px, 1fr); align-items: center; gap: 24px; min-height: 78px; padding: 14px clamp(18px, 3vw, 44px); border-bottom: 1px solid var(--line); background: var(--panel); }
1696
1702
  .entity-editor-heading { min-width: 0; text-align: center; }
1697
1703
  .entity-editor-heading h1 { overflow: hidden; margin: 2px 0 0; font-size: 24px; font-weight: 600; text-overflow: ellipsis; white-space: nowrap; }
1704
+ .entity-editor-readonly-badge { display: inline-block; width: fit-content; margin: 0 auto; padding: 3px 7px; border: 1px solid color-mix(in srgb, var(--muted) 55%, var(--line)); border-radius: 999px; color: var(--muted); font-size: 10px; line-height: 1.2; }
1705
+ .character-editor-title-row .entity-editor-readonly-badge { margin-left: 6px; vertical-align: middle; }
1698
1706
  .entity-editor-header > .primary-button { justify-self: end; min-width: 112px; }
1699
1707
  .entity-editor-back { justify-self: start; min-height: 36px; padding: 8px 12px; border: 1px solid var(--line); border-radius: 4px; background: transparent; color: var(--muted); font-size: 11px; }
1700
1708
  .entity-editor-back::before { content: "←"; margin-right: 7px; }
@@ -1725,7 +1733,13 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1725
1733
  .vditor-editor-host.vditor { height: 100% !important; min-height: 0; overflow: hidden; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--ink); }
1726
1734
  .vditor-editor-host.vditor:not(.vditor--dark) { --border-color: var(--line); --panel-background-color: var(--surface); --toolbar-background-color: var(--surface-soft); --toolbar-icon-color: var(--muted); --toolbar-icon-hover-color: var(--accent-dark); --textarea-background-color: var(--surface); --textarea-text-color: var(--ink); --heading-border-color: var(--line); --blockquote-color: var(--muted); }
1727
1735
  .vditor-editor-host.vditor--dark { --border-color: var(--line); --panel-background-color: var(--surface); --toolbar-background-color: var(--surface-soft); --toolbar-icon-color: var(--muted); --toolbar-icon-hover-color: var(--accent); --textarea-background-color: var(--surface); --textarea-text-color: var(--ink); --heading-border-color: var(--line); --blockquote-color: var(--muted); }
1728
- .vditor-editor-host .vditor-toolbar { flex: 0 0 auto; }
1736
+ .vditor-editor-host .vditor-toolbar { display: flex; align-items: center; flex: 0 0 auto; }
1737
+ .vditor-editor-host .markdown-word-count { display: inline-flex; align-items: center; align-self: center; min-height: 28px; margin-left: 24px; padding: 0 10px; color: var(--muted); font: 10px/1 var(--font-latin), var(--font-cjk), sans-serif; white-space: nowrap; }
1738
+ .vditor-editor-host .vditor-content { position: relative; }
1739
+ .vditor-editor-host .vditor-line-numbers { display: none; position: absolute; z-index: 1; inset: 0 auto 0 0; width: 42px; padding: 16px 8px 16px 0; overflow: hidden; border-right: 1px solid var(--line); background: color-mix(in srgb, var(--surface-soft) 88%, transparent); color: var(--muted); font: 12px/1.75 var(--font-latin), monospace; text-align: right; user-select: none; pointer-events: none; }
1740
+ .vditor-editor-host.vditor.show-line-numbers .vditor-line-numbers { display: grid; align-content: start; }
1741
+ .vditor-editor-host.vditor.show-line-numbers .vditor-ir, .vditor-editor-host.vditor.show-line-numbers .vditor-wysiwyg, .vditor-editor-host.vditor.show-line-numbers .vditor-sv textarea { padding-left: 42px; }
1742
+ .vditor-editor-host .vditor-line-number-button[aria-pressed="true"] { color: var(--accent-dark); background: color-mix(in srgb, var(--accent) 16%, transparent); }
1729
1743
  .vditor-editor-host .vditor-content { min-height: 0; }
1730
1744
  .vditor-editor-host .vditor-reset { color: var(--ink); font-family: var(--font-cjk), var(--font-latin), sans-serif; }
1731
1745
  .vditor-editor-host .vditor-ir, .vditor-editor-host .vditor-sv, .vditor-editor-host .vditor-wysiwyg { background: var(--surface); color: var(--ink); }
@@ -1830,11 +1844,14 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1830
1844
  .character-section-editor-header > div { grid-column: 1; grid-row: 1; justify-self: center; text-align: center; }
1831
1845
  .character-section-editor-header .entity-editor-back { grid-column: 1; grid-row: 1; z-index: 1; }
1832
1846
  .character-section-editor-header h2 { margin: 3px 0 0; font-size: 22px; font-weight: 600; }
1847
+ .character-section-editor-title-input { width: min(100%, 520px); margin: 3px auto 0; padding: 0 0 4px; border: 0; border-bottom: 1px solid transparent; border-radius: 0; background: transparent; color: var(--ink); font-size: 22px; font-weight: 600; line-height: 1.2; text-align: center; }
1848
+ .character-section-editor-title-input::placeholder { color: var(--ink); opacity: .92; }
1849
+ .character-section-editor-title-input:focus { border-color: var(--accent); box-shadow: none; }
1833
1850
  .character-markdown-editor { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; gap: 12px; min-height: 0; padding: 16px 22px 18px; overflow: hidden; background: var(--surface-soft); }
1834
1851
  .character-markdown-editor > .vditor-editor-host { width: 100%; max-width: 960px; justify-self: center; }
1835
1852
  .character-markdown-editor > .vditor-editor-host.vditor { border-color: color-mix(in srgb, var(--accent) 22%, var(--line)); box-shadow: 0 12px 34px color-mix(in srgb, var(--ink) 12%, transparent); transition: border-color .16s ease, box-shadow .16s ease; }
1836
1853
  .character-markdown-editor > .vditor-editor-host.vditor:focus-within { border-color: color-mix(in srgb, var(--accent) 62%, var(--line)); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 15%, transparent), 0 16px 42px color-mix(in srgb, var(--ink) 16%, transparent); }
1837
- .character-markdown-editor-meta { display: grid; grid-template-columns: minmax(140px, .55fr) minmax(220px, 1fr) minmax(280px, 1.45fr); gap: 12px; }
1854
+ .character-markdown-editor-meta { display: grid; grid-template-columns: minmax(160px, .55fr) minmax(280px, 1.45fr); gap: 12px; }
1838
1855
  .character-markdown-editor label { display: grid; gap: 6px; color: var(--muted); font-size: 10px; }
1839
1856
  .character-markdown-editor input, .character-markdown-editor select, .character-markdown-editor textarea { width: 100%; padding: 9px 10px; border: 1px solid var(--line); border-radius: 4px; background: var(--surface); color: var(--ink); font-size: 12px; }
1840
1857
  .character-markdown-summary-field textarea { min-height: 42px; height: 42px; resize: vertical; }
@@ -1987,6 +2004,234 @@ select:focus, input:focus, textarea:focus { border-color: #a77768; box-shadow: 0
1987
2004
  .account-settings-form .primary-button { width: 100%; }
1988
2005
  }
1989
2006
 
2007
+ /* 移动端工作台:仅在窄屏覆盖桌面三栏布局,保留桌面端原有尺寸与排列。 */
2008
+ @media (max-width: 850px) {
2009
+ :root { --mobile-gutter: 14px; }
2010
+
2011
+ html, body { min-width: 0; overflow-x: hidden; }
2012
+ body { -webkit-text-size-adjust: 100%; }
2013
+ button, select, input, textarea, [contenteditable="true"] { touch-action: manipulation; }
2014
+ button, .ghost-button, .primary-button, .danger-button, select { min-height: 40px; }
2015
+
2016
+ .app-shell {
2017
+ grid-template-columns: minmax(0, 1fr);
2018
+ grid-template-rows: auto auto minmax(0, 1fr);
2019
+ min-height: 100dvh;
2020
+ height: 100dvh;
2021
+ overflow-x: hidden;
2022
+ overflow-y: hidden;
2023
+ }
2024
+ .app-shell.shelf-mode {
2025
+ grid-template-rows: auto minmax(0, 1fr);
2026
+ min-height: 100dvh;
2027
+ }
2028
+ .topbar {
2029
+ grid-column: 1;
2030
+ grid-row: 1;
2031
+ grid-template-columns: minmax(0, 1fr) auto;
2032
+ grid-template-rows: 56px auto;
2033
+ min-width: 0;
2034
+ padding: 0 var(--mobile-gutter);
2035
+ }
2036
+ .app-shell.shelf-mode .topbar { grid-template-columns: minmax(0, 1fr) auto; }
2037
+ .brand-block { min-width: 0; padding: 0; }
2038
+ .app-shell:not(.shelf-mode):not(.left-panel-collapsed) .topbar .brand-block > div { visibility: hidden; }
2039
+ .brand-block .brand-mark { width: 30px; height: 30px; }
2040
+ .brand-block strong { font-size: 14px; }
2041
+ .brand-block small { display: none; }
2042
+ .mobile-module-tab { position: fixed; z-index: 32; top: 132px; right: 0; display: grid; width: 34px; height: 42px; padding: 0; place-items: center; border: 1px solid var(--line); border-right: 0; border-radius: 5px 0 0 5px; background: var(--surface-soft); color: var(--accent-dark); font-size: 10px; writing-mode: vertical-rl; }
2043
+ .mobile-panel-backdrop { position: fixed; z-index: 30; inset: 0; width: 100%; height: 100%; padding: 0; border: 0; background: rgba(20, 17, 14, .28); }
2044
+ .app-shell:not(.shelf-mode):not(.left-panel-collapsed) .mobile-panel-backdrop { display: block; }
2045
+ .work-meta {
2046
+ grid-column: 1 / -1;
2047
+ grid-row: 2;
2048
+ min-width: 0;
2049
+ padding: 8px 0 10px;
2050
+ border-top: 1px solid var(--line);
2051
+ overflow: hidden;
2052
+ text-overflow: ellipsis;
2053
+ white-space: nowrap;
2054
+ font-size: 11px;
2055
+ }
2056
+ .top-actions { gap: 5px; padding: 0; }
2057
+ .top-actions .save-state, .top-actions .presence-control { display: none; }
2058
+ .account-button { width: 34px; height: 34px; padding: 3px; }
2059
+ .account-button > span:last-child { display: none; }
2060
+ .account-button { width: 36px; min-width: 36px; height: 36px; padding: 3px; justify-content: center; border-radius: 50%; }
2061
+ .account-avatar { width: 28px; height: 28px; }
2062
+ .account-menu { top: 46px; right: 0; width: min(220px, calc(100vw - 28px)); }
2063
+ .topbar-icon-button, .theme-toggle, .settings-button { width: 36px; height: 36px; min-height: 0; }
2064
+ button.account-button { width: 36px; height: 36px; min-height: 0; }
2065
+
2066
+ .left-panel {
2067
+ position: fixed;
2068
+ z-index: 31;
2069
+ top: 0;
2070
+ bottom: 0;
2071
+ right: 0;
2072
+ width: min(300px, 84vw);
2073
+ grid-column: auto;
2074
+ grid-row: auto;
2075
+ min-width: 0;
2076
+ padding: 76px var(--mobile-gutter) 18px;
2077
+ border-left: 1px solid var(--line);
2078
+ border-bottom: 1px solid var(--line);
2079
+ box-shadow: -12px 0 36px rgba(36, 30, 24, .2);
2080
+ overflow-y: auto;
2081
+ transform: translateX(0);
2082
+ transition: transform .2s ease;
2083
+ }
2084
+ .app-shell.left-panel-collapsed .left-panel { padding: 76px var(--mobile-gutter) 18px; transform: translateX(100%); }
2085
+ .app-shell.left-panel-collapsed .left-panel > :not(.left-actions) { display: block; }
2086
+ .app-shell.left-panel-collapsed .left-panel > .module-nav { display: grid; }
2087
+ .left-panel > .panel-resize-handle { display: none; }
2088
+ .left-actions { margin: 0 0 8px; }
2089
+ .left-primary-actions { gap: 6px; }
2090
+ .left-primary-actions .file-button { display: none; }
2091
+ .left-primary-actions #left-panel-toggle { position: absolute; top: 14px; right: 14px; display: grid; place-items: center; width: 32px; height: 32px; font-size: 22px; }
2092
+ .module-nav { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 5px; margin: 0; padding: 0 0 8px; overflow: visible; }
2093
+ .module-nav > button {
2094
+ flex: 0 0 auto;
2095
+ min-height: 42px;
2096
+ padding: 8px 12px;
2097
+ white-space: nowrap;
2098
+ }
2099
+ .module-nav .nav-icon { width: 16px; height: 16px; }
2100
+ .module-nav .module-nav-secondary.hidden { display: none !important; }
2101
+ .panel-heading, #novel-tree { display: none; }
2102
+ .app-shell.left-panel-collapsed .left-panel { padding: 8px var(--mobile-gutter) 0; }
2103
+ .app-shell.left-panel-collapsed .left-actions { display: block; padding: 0; }
2104
+
2105
+ .main-panel {
2106
+ grid-column: 1;
2107
+ grid-row: 2;
2108
+ min-width: 0;
2109
+ min-height: 0;
2110
+ overflow: hidden;
2111
+ }
2112
+ .app-shell.shelf-mode .main-panel { grid-column: 1; grid-row: 2; min-height: 0; }
2113
+ .ai-panel {
2114
+ position: fixed;
2115
+ z-index: 25;
2116
+ right: 0;
2117
+ bottom: 0;
2118
+ left: 0;
2119
+ grid-column: 1;
2120
+ grid-row: 4;
2121
+ min-width: 0;
2122
+ height: min(72dvh, 620px);
2123
+ min-height: 0;
2124
+ padding: 12px var(--mobile-gutter) 18px;
2125
+ border-top: 1px solid var(--line);
2126
+ border-left: 0;
2127
+ box-shadow: 0 -14px 36px rgba(36, 30, 24, .14);
2128
+ transform: translateY(0);
2129
+ transition: transform .2s ease;
2130
+ }
2131
+ .app-shell.shelf-mode .ai-panel { display: none; }
2132
+ .ai-panel > .panel-resize-handle { display: none; }
2133
+ .app-shell.ai-panel-collapsed .ai-panel { height: 58px; padding-block: 8px; transform: translateY(0); }
2134
+ .app-shell.ai-panel-collapsed .ai-panel > :not(.ai-heading) { display: none; }
2135
+ .app-shell.ai-panel-collapsed .ai-heading { display: flex; justify-content: space-between; padding: 0; }
2136
+
2137
+ .shelf-view { height: auto; min-height: 100%; padding: 28px var(--mobile-gutter) 42px; }
2138
+ #shelf-view, #settings-hub-view { padding-bottom: 0; }
2139
+ #shelf-view .product-footer, #settings-hub-view .product-footer { margin-bottom: 0; padding-bottom: env(safe-area-inset-bottom); }
2140
+ .shelf-header, .module-header { align-items: stretch; flex-direction: column; gap: 14px; margin-bottom: 20px; }
2141
+ .shelf-header h1 { font-size: clamp(24px, 7.5vw, 32px); }
2142
+ .shelf-header .primary-button { align-self: flex-start; min-width: 150px; min-height: 40px; padding: 10px 18px; }
2143
+ .module-header-actions { width: 100%; }
2144
+ .book-shelf { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px 14px; }
2145
+ .book-info { padding-top: 10px; padding-right: 0; }
2146
+ .book-info strong { font-size: 14px; }
2147
+ .book-card-settings { opacity: 1; }
2148
+ .welcome-view { padding: 28px var(--mobile-gutter); }
2149
+ .welcome-view h1 { font-size: clamp(30px, 9vw, 44px); }
2150
+ .welcome-grid { grid-template-columns: 1fr; margin-top: 34px; }
2151
+
2152
+ .editor-toolbar { padding: 18px var(--mobile-gutter) 14px; }
2153
+ .editor-actions { position: fixed; z-index: 34; right: 0; bottom: 18px; left: auto; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); width: min(300px, 84vw); max-width: none; gap: 6px; padding: 10px; border: 1px solid var(--line); border-right: 0; border-radius: 6px 0 0 6px; background: var(--panel); box-shadow: -12px 0 36px rgba(36, 30, 24, .2); transform: translateX(0); transition: transform .2s ease; }
2154
+ .app-shell.left-panel-collapsed .editor-actions { transform: translateX(100%); pointer-events: none; }
2155
+ .editor-actions .chapter-stats { grid-column: 1 / -1; }
2156
+ .editor-actions > button { min-width: 0; min-height: 36px; padding-inline: 8px; font-size: 11px; }
2157
+ .chapter-title { font-size: 23px; }
2158
+ .chapter-content { padding: 22px 16px 64px 12px; font-size: max(16px, var(--editor-font-size)); }
2159
+ .chapter-line-numbers { width: 32px; font-size: 11px; }
2160
+ .chapter-editor-frame { grid-template-columns: 32px minmax(0, 1fr); }
2161
+ .chapter-line-measure { left: 32px; }
2162
+ .chapter-insight { margin: 10px var(--mobile-gutter) 0; }
2163
+
2164
+ .module-view { padding: 26px var(--mobile-gutter) 46px; }
2165
+ .module-header { padding-bottom: 18px; }
2166
+ .module-header h1 { font-size: 29px; }
2167
+ .module-header-actions { justify-content: stretch; }
2168
+ .module-header-actions > * { flex: 1 1 auto; }
2169
+ .module-content { padding: 18px 0 56px; }
2170
+ .card-grid, .provider-card-grid { grid-template-columns: minmax(0, 1fr); }
2171
+ .record-card { padding: 15px; }
2172
+ .card-actions, .task-row-actions, .module-header-actions { gap: 8px; }
2173
+ .card-actions button, .task-row-actions button { flex: 1 1 auto; min-height: 38px; }
2174
+ .setting-row, .module-row { padding: 13px; }
2175
+ .settings-layout-toolbar, .module-layout-toolbar { align-items: stretch; flex-direction: column; }
2176
+ .settings-layout-toggle, .module-layout-toggle { width: 100%; }
2177
+ .settings-layout-toggle button { flex: 1; }
2178
+
2179
+ .dialog, .wide-dialog, .account-dialog {
2180
+ width: calc(100vw - 20px);
2181
+ max-width: calc(100vw - 20px);
2182
+ max-height: calc(100dvh - 20px);
2183
+ margin: auto;
2184
+ border-radius: 10px;
2185
+ }
2186
+ .dialog-header { padding: 14px 16px 12px; }
2187
+ .dialog-header h2 { font-size: 20px; }
2188
+ .dialog-fields, .appearance-grid, .access-dialog-body, .account-settings-body, .versions-list, .entity-history-list { padding-inline: 16px; }
2189
+ .dialog-actions { flex-wrap: wrap; padding: 12px 16px 16px; }
2190
+ .dialog-actions > button { flex: 1 1 120px; }
2191
+ .search-form { grid-template-columns: minmax(0, 1fr); }
2192
+ .search-form .primary-button { width: 100%; }
2193
+ .appearance-grid { grid-template-columns: minmax(0, 1fr); }
2194
+ .appearance-whitespace-field, .font-preview { grid-column: auto; }
2195
+ .access-row { grid-template-columns: minmax(0, 1fr); align-items: stretch; }
2196
+ .access-row select, .access-row button, .work-access-field .ghost-button { width: 100%; }
2197
+ .work-access-field { align-items: stretch; flex-direction: column; }
2198
+
2199
+ .entity-editor-view { min-width: 0; }
2200
+ .entity-editor-header, .character-editor-header { grid-template-columns: auto minmax(0, 1fr); gap: 8px; padding: 12px var(--mobile-gutter); }
2201
+ .entity-editor-mode-actions, .character-editor-header-actions { grid-column: 1 / -1; justify-content: stretch; flex-wrap: wrap; }
2202
+ .entity-editor-mode-actions > button, .character-editor-header-actions > button { flex: 1 1 auto; }
2203
+ .setting-editor-header { min-height: 0; }
2204
+ .setting-editor-content { padding: 12px; }
2205
+ .setting-markdown-field { height: 68dvh; }
2206
+ .character-editor-actions { padding: 12px var(--mobile-gutter) 16px; }
2207
+ .character-editor-actions > div { justify-content: stretch; flex-wrap: wrap; }
2208
+ .character-editor-actions > div > button { flex: 1 1 auto; }
2209
+ .character-markdown-editor { padding: 12px; }
2210
+ .character-markdown-editor-actions { justify-content: stretch; flex-wrap: wrap; }
2211
+ .character-markdown-editor-actions button { flex: 1 1 auto; }
2212
+
2213
+ .relationship-table-wrapper, .data-table-wrapper, .table-scroll, .module-table-wrapper { max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
2214
+ table { min-width: 620px; }
2215
+ }
2216
+
2217
+ @media (max-width: 480px) {
2218
+ .top-actions .settings-button { display: none; }
2219
+ .topbar-icon-button, .theme-toggle, .account-button { flex: 0 0 32px; width: 32px; min-width: 32px; height: 32px; }
2220
+ button.account-button { width: 32px; height: 32px; }
2221
+ .top-actions { gap: 3px; }
2222
+ .left-primary-actions .file-button { font-size: 10px; }
2223
+ .editor-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
2224
+ .editor-actions .chapter-stats { display: none; }
2225
+ .editor-actions > button { width: auto; }
2226
+ .book-shelf { grid-template-columns: minmax(0, 1fr); max-width: 280px; }
2227
+ .book-add-card { min-height: 220px; }
2228
+ .onboarding-popover { width: calc(100vw - 20px); }
2229
+ }
2230
+
2231
+ @media (max-width: 850px) {
2232
+ #onboarding-menu-button { display: none; }
2233
+ }
2234
+
1990
2235
  @media (prefers-reduced-motion: reduce) {
1991
2236
  *, *::before, *::after { scroll-behavior: auto !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
1992
2237
  }