@stephen-netu/design-system 0.1.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.
Files changed (204) hide show
  1. package/README.md +66 -0
  2. package/package.json +130 -0
  3. package/src/b0-app-shell/clarification-overlay/ClarificationOverlay.svelte +221 -0
  4. package/src/b0-app-shell/clarification-overlay/index.ts +1 -0
  5. package/src/b0-app-shell/command-bar/CommandBar.svelte +565 -0
  6. package/src/b0-app-shell/index.ts +14 -0
  7. package/src/base.css +152 -0
  8. package/src/d0-data-viz/BarChart.svelte +236 -0
  9. package/src/d0-data-viz/CheckpointBar.svelte +574 -0
  10. package/src/d0-data-viz/CheckpointPill.svelte +297 -0
  11. package/src/d0-data-viz/MetricCard.svelte +159 -0
  12. package/src/d0-data-viz/SparkLine.svelte +66 -0
  13. package/src/d0-data-viz/StorageMap.svelte +182 -0
  14. package/src/d0-data-viz/Timeline.svelte +188 -0
  15. package/src/d0-data-viz/checkpoint-types.ts +162 -0
  16. package/src/d0-data-viz/d0-tokens.css +32 -0
  17. package/src/d0-data-viz/index.ts +58 -0
  18. package/src/d0-data-viz/storage-map-types.ts +142 -0
  19. package/src/d0-data-viz/theme-integration.ts +134 -0
  20. package/src/d0-data-viz/types.ts +53 -0
  21. package/src/demos/BspSizingInfographic.svelte +386 -0
  22. package/src/demos/BspZoneDemo.svelte +255 -0
  23. package/src/demos/ControlRoomDemo.svelte +570 -0
  24. package/src/demos/NodepadBlocksShowcase.svelte +189 -0
  25. package/src/demos/index.ts +7 -0
  26. package/src/editor/CodeEditor.svelte +188 -0
  27. package/src/editor/FindReplaceDialog.svelte +516 -0
  28. package/src/editor/MarkdownEditor.svelte +660 -0
  29. package/src/editor/index.ts +2 -0
  30. package/src/entity-graph-view/WorldView.svelte +171 -0
  31. package/src/entity-graph-view/index.ts +6 -0
  32. package/src/entity-graph-view/types.ts +364 -0
  33. package/src/epistemic/index.ts +2 -0
  34. package/src/epistemic/types.ts +134 -0
  35. package/src/epistemic/utils.ts +32 -0
  36. package/src/f0-forms/checkbox/Checkbox.svelte +158 -0
  37. package/src/f0-forms/field/FormField.svelte +91 -0
  38. package/src/f0-forms/index.ts +11 -0
  39. package/src/f0-forms/radio/RadioGroup.svelte +192 -0
  40. package/src/f0-forms/search/SearchBar.svelte +99 -0
  41. package/src/f0-forms/select/Select.svelte +198 -0
  42. package/src/f0-forms/textarea/TextArea.svelte +170 -0
  43. package/src/f0-forms/toggle/FormToggle.svelte +147 -0
  44. package/src/f0-forms/validation/validate.ts +47 -0
  45. package/src/fabric/ai/ChatPanel.svelte +648 -0
  46. package/src/fabric/ai/index.ts +11 -0
  47. package/src/fabric/data/index.ts +21 -0
  48. package/src/fabric/feedback/StatusBar.svelte +274 -0
  49. package/src/fabric/feedback/ToastContainer.svelte +122 -0
  50. package/src/fabric/feedback/index.ts +14 -0
  51. package/src/fabric/index.ts +37 -0
  52. package/src/fabric/input/CommandBar.svelte +79 -0
  53. package/src/fabric/input/KeyboardShortcuts.svelte +388 -0
  54. package/src/fabric/input/index.ts +12 -0
  55. package/src/fabric/layout/GlassBox.svelte +193 -0
  56. package/src/fabric/layout/KanbanBoard.svelte +158 -0
  57. package/src/fabric/layout/KanbanCard.svelte +226 -0
  58. package/src/fabric/layout/KanbanColumn.svelte +278 -0
  59. package/src/fabric/layout/index.ts +17 -0
  60. package/src/fabric/navigation/ActivityRail.svelte +271 -0
  61. package/src/fabric/navigation/index.ts +11 -0
  62. package/src/g0-geo/SovereignMap.svelte +149 -0
  63. package/src/g0-geo/index.ts +2 -0
  64. package/src/index.ts +68 -0
  65. package/src/l0-layout/block-writer/BlockConnectionLines.svelte +98 -0
  66. package/src/l0-layout/block-writer/BlockContent.svelte +213 -0
  67. package/src/l0-layout/block-writer/BlockHeader.svelte +230 -0
  68. package/src/l0-layout/block-writer/BlockItem.svelte +177 -0
  69. package/src/l0-layout/block-writer/BlockWriter.svelte +219 -0
  70. package/src/l0-layout/block-writer/block-writer-types.ts +63 -0
  71. package/src/l0-layout/block-writer/index.ts +24 -0
  72. package/src/l0-layout/block-writer/markdown.ts +110 -0
  73. package/src/l0-layout/chat-view/ChatViewLayout.svelte +61 -0
  74. package/src/l0-layout/epistemic/AllayDetailPanel.svelte +113 -0
  75. package/src/l0-layout/epistemic/AssayDetailPanel.svelte +164 -0
  76. package/src/l0-layout/epistemic/AvenueTrace.svelte +253 -0
  77. package/src/l0-layout/epistemic/KnowledgeDetailPanel.svelte +204 -0
  78. package/src/l0-layout/epistemic/ParadigmLens.svelte +170 -0
  79. package/src/l0-layout/epistemic/ProvenanceChain.svelte +164 -0
  80. package/src/l0-layout/epistemic/TrustView.svelte +195 -0
  81. package/src/l0-layout/epistemic/index.ts +7 -0
  82. package/src/l0-layout/grove/AccordCard.svelte +156 -0
  83. package/src/l0-layout/grove/GroveCard.svelte +197 -0
  84. package/src/l0-layout/grove/MemberBadge.svelte +96 -0
  85. package/src/l0-layout/grove/MemberList.svelte +59 -0
  86. package/src/l0-layout/grove/grove-types.ts +29 -0
  87. package/src/l0-layout/grove/index.ts +14 -0
  88. package/src/l0-layout/index.ts +45 -0
  89. package/src/l0-layout/kanban/KanbanBoard.svelte +71 -0
  90. package/src/l0-layout/kanban/KanbanCard.svelte +276 -0
  91. package/src/l0-layout/kanban/KanbanColumn.svelte +198 -0
  92. package/src/l0-layout/kanban/KanbanViewLayout.svelte +61 -0
  93. package/src/l0-layout/workspace/WorkspaceLayout.svelte +93 -0
  94. package/src/n0-node-graph/GraphEditor.svelte +342 -0
  95. package/src/n0-node-graph/GraphHistory.svelte +122 -0
  96. package/src/n0-node-graph/Node.svelte +269 -0
  97. package/src/n0-node-graph/NodeCanvas.svelte +187 -0
  98. package/src/n0-node-graph/NodeEdge.svelte +152 -0
  99. package/src/n0-node-graph/NodePalette.svelte +318 -0
  100. package/src/n0-node-graph/NodePort.svelte +128 -0
  101. package/src/n0-node-graph/index.ts +33 -0
  102. package/src/n0-node-graph/n0-tokens.css +29 -0
  103. package/src/n0-node-graph/node-port-types.ts +19 -0
  104. package/src/n0-node-graph/types.ts +96 -0
  105. package/src/n0-node-graph/validate.test.ts +722 -0
  106. package/src/n0-node-graph/validate.ts +371 -0
  107. package/src/n1-flow-canvas/FlowCanvas.svelte +202 -0
  108. package/src/n1-flow-canvas/graph-state.svelte.ts +95 -0
  109. package/src/n1-flow-canvas/index.ts +21 -0
  110. package/src/n1-flow-canvas/layout.ts +118 -0
  111. package/src/n1-flow-canvas/layout.worker.ts +128 -0
  112. package/src/n1-flow-canvas/types.ts +102 -0
  113. package/src/n1-flow-canvas/utils.ts +146 -0
  114. package/src/n2-manifold-disk/ManifoldDisk.svelte +314 -0
  115. package/src/n2-manifold-disk/index.ts +6 -0
  116. package/src/n2-manifold-disk/poincare.ts +184 -0
  117. package/src/n3-force-graph/ForceGraph.svelte +180 -0
  118. package/src/n3-force-graph/GraphTooltip.svelte +99 -0
  119. package/src/n3-force-graph/animated-edge.ts +107 -0
  120. package/src/n3-force-graph/health-node.ts +128 -0
  121. package/src/n3-force-graph/index.ts +31 -0
  122. package/src/n3-force-graph/types.ts +94 -0
  123. package/src/n4-adaptive-manifold/AdaptiveManifold.svelte +1165 -0
  124. package/src/n4-adaptive-manifold/DetailCard.svelte +212 -0
  125. package/src/n4-adaptive-manifold/SuggestionIndicator.svelte +91 -0
  126. package/src/n4-adaptive-manifold/classifier.ts +139 -0
  127. package/src/n4-adaptive-manifold/index.ts +45 -0
  128. package/src/n4-adaptive-manifold/n4-tokens.css +155 -0
  129. package/src/n4-adaptive-manifold/orchestrator.ts +301 -0
  130. package/src/n4-adaptive-manifold/phase-theme.ts +92 -0
  131. package/src/n4-adaptive-manifold/pinnacle-events.ts +135 -0
  132. package/src/n4-adaptive-manifold/providers/gravitational-provider.ts +191 -0
  133. package/src/n4-adaptive-manifold/providers/manifold-provider.ts +276 -0
  134. package/src/n4-adaptive-manifold/transition.ts +90 -0
  135. package/src/n4-adaptive-manifold/types.ts +313 -0
  136. package/src/p0-primitives/index.ts +6 -0
  137. package/src/p0-primitives/tokens/control-room.css +309 -0
  138. package/src/p0-primitives/tokens/index.ts +6 -0
  139. package/src/p0-primitives/tokens/tokens.css +140 -0
  140. package/src/p0-primitives/tokens/tokens.json +132 -0
  141. package/src/p0-primitives/types/index.ts +123 -0
  142. package/src/p0-primitives/types/plugin-types.ts +74 -0
  143. package/src/p0-primitives/utils/index.ts +39 -0
  144. package/src/platform/hud-protocol.ts +20 -0
  145. package/src/platform/index.ts +3 -0
  146. package/src/s0-state/create-store.ts +84 -0
  147. package/src/s0-state/derived.ts +41 -0
  148. package/src/s0-state/index.ts +6 -0
  149. package/src/showcase/Showcase.svelte +401 -0
  150. package/src/spatial/BspTilingCanvas.svelte +232 -0
  151. package/src/spatial/ForceCanvas.svelte +289 -0
  152. package/src/spatial/LodRenderer.svelte +334 -0
  153. package/src/spatial/SnapZoomCamera.svelte +185 -0
  154. package/src/spatial/ZoneTiler.svelte +311 -0
  155. package/src/spatial/camera-types.ts +45 -0
  156. package/src/spatial/content-proportional-bsp.ts +253 -0
  157. package/src/spatial/force-canvas-types.ts +102 -0
  158. package/src/spatial/index.ts +15 -0
  159. package/src/spatial/lod-types.ts +70 -0
  160. package/src/spatial/zone-tiler-types.ts +177 -0
  161. package/src/stores/vaultStore.svelte +14 -0
  162. package/src/t0-transport/connection.ts +34 -0
  163. package/src/t0-transport/error.ts +37 -0
  164. package/src/t0-transport/events.ts +50 -0
  165. package/src/t0-transport/index.ts +6 -0
  166. package/src/t0-transport/invoke.ts +30 -0
  167. package/src/tauri/index.ts +13 -0
  168. package/src/u0-primitives/allay-gauge/AllayGauge.svelte +90 -0
  169. package/src/u0-primitives/allay-gauge/index.ts +1 -0
  170. package/src/u0-primitives/animated-icon/AnimatedIcon.svelte +273 -0
  171. package/src/u0-primitives/animated-icon/README.md +53 -0
  172. package/src/u0-primitives/animated-icon/index.ts +31 -0
  173. package/src/u0-primitives/assay-badge/AssayBadge.svelte +121 -0
  174. package/src/u0-primitives/assay-badge/AssaySignalBar.svelte +83 -0
  175. package/src/u0-primitives/assay-badge/index.ts +2 -0
  176. package/src/u0-primitives/avatar/Avatar.svelte +177 -0
  177. package/src/u0-primitives/badge/Badge.svelte +149 -0
  178. package/src/u0-primitives/button/Button.svelte +304 -0
  179. package/src/u0-primitives/card/Card.svelte +182 -0
  180. package/src/u0-primitives/dropdown/Dropdown.svelte +305 -0
  181. package/src/u0-primitives/expert-badge/ExpertBadge.svelte +89 -0
  182. package/src/u0-primitives/expert-badge/index.ts +1 -0
  183. package/src/u0-primitives/index.ts +35 -0
  184. package/src/u0-primitives/input/Input.svelte +237 -0
  185. package/src/u0-primitives/modal/Modal.svelte +331 -0
  186. package/src/u0-primitives/modal/index.ts +1 -0
  187. package/src/u0-primitives/spinner/Spinner.svelte +129 -0
  188. package/src/u0-primitives/tabs/Tabs.svelte +225 -0
  189. package/src/u0-primitives/tabs/tabs-types.ts +11 -0
  190. package/src/u0-primitives/toggle/Toggle.svelte +161 -0
  191. package/src/u0-primitives/tooltip/Tooltip.svelte +234 -0
  192. package/src/v0-diagrams/DiagramPanel.svelte +68 -0
  193. package/src/v0-diagrams/MarkmapDiagram.svelte +56 -0
  194. package/src/v0-diagrams/MermaidDiagram.svelte +53 -0
  195. package/src/v0-diagrams/index.ts +4 -0
  196. package/src/v0-diagrams/types.ts +28 -0
  197. package/src/v0-diagrams/v0-tokens.css +6 -0
  198. package/src/vite-env.d.ts +7 -0
  199. package/src/x0-enchanted-blocks/ENCHANTED_BLOCKS_SPEC.md +457 -0
  200. package/src/x0-enchanted-blocks/components/EnchantedBlock.svelte +485 -0
  201. package/src/x0-enchanted-blocks/components/NodepadBlock.svelte +262 -0
  202. package/src/x0-enchanted-blocks/core/block-layout.ts +211 -0
  203. package/src/x0-enchanted-blocks/core/block-types.ts +227 -0
  204. package/src/x0-enchanted-blocks/index.ts +46 -0
@@ -0,0 +1,213 @@
1
+ <script lang="ts">
2
+ import { renderMarkdown } from './markdown.js';
3
+
4
+ interface Props {
5
+ content: string;
6
+ isEditing: boolean;
7
+ onEditStart?: () => void;
8
+ onEditEnd?: () => void;
9
+ onContentChange?: (content: string) => void;
10
+ }
11
+
12
+ let {
13
+ content = '',
14
+ isEditing = false,
15
+ onEditStart,
16
+ onEditEnd,
17
+ onContentChange,
18
+ }: Props = $props();
19
+
20
+ let editableEl: HTMLDivElement | undefined = $state();
21
+ let renderedHtml = $derived(renderMarkdown(content));
22
+ /** Guard: skip external sync while user is actively typing */
23
+ let isUserTyping = $state(false);
24
+ let typingTimer: ReturnType<typeof setTimeout> | undefined;
25
+
26
+ function handleFocus() {
27
+ onEditStart?.();
28
+ }
29
+
30
+ function handleBlur() {
31
+ isUserTyping = false;
32
+ clearTimeout(typingTimer);
33
+ onEditEnd?.();
34
+ }
35
+
36
+ function handleInput() {
37
+ if (!editableEl) return;
38
+ isUserTyping = true;
39
+ clearTimeout(typingTimer);
40
+ typingTimer = setTimeout(() => { isUserTyping = false; }, 500);
41
+ onContentChange?.(editableEl.innerText);
42
+ }
43
+
44
+ function handleKeydown(e: KeyboardEvent) {
45
+ if (e.key === 'Escape') {
46
+ e.preventDefault();
47
+ editableEl?.blur();
48
+ }
49
+ // Tab inserts two spaces via Selection/Range (no deprecated execCommand)
50
+ if (e.key === 'Tab') {
51
+ e.preventDefault();
52
+ const sel = window.getSelection();
53
+ if (sel && sel.rangeCount > 0) {
54
+ const range = sel.getRangeAt(0);
55
+ range.deleteContents();
56
+ range.insertNode(document.createTextNode(' '));
57
+ range.collapse(false);
58
+ sel.removeAllRanges();
59
+ sel.addRange(range);
60
+ handleInput();
61
+ }
62
+ }
63
+ }
64
+
65
+ // Sync external content changes into the contenteditable — skip while user is typing
66
+ $effect(() => {
67
+ if (editableEl && isEditing && !isUserTyping && editableEl.innerText !== content) {
68
+ editableEl.innerText = content;
69
+ }
70
+ });
71
+ </script>
72
+
73
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
74
+ <div class="ds-block-content" ondblclick={onEditStart}>
75
+ {#if isEditing}
76
+ <!-- Editing mode: contenteditable div -->
77
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
78
+ <div
79
+ bind:this={editableEl}
80
+ class="ds-block-editable"
81
+ contenteditable="true"
82
+ role="textbox"
83
+ aria-multiline="true"
84
+ tabindex="0"
85
+ onfocus={handleFocus}
86
+ onblur={handleBlur}
87
+ oninput={handleInput}
88
+ onkeydown={handleKeydown}
89
+ ></div>
90
+ {:else}
91
+ <!-- Rendered mode: parsed markdown -->
92
+ <div class="ds-block-rendered">
93
+ {@html renderedHtml}
94
+ </div>
95
+ {/if}
96
+ </div>
97
+
98
+ <style>
99
+ .ds-block-content {
100
+ flex: 1;
101
+ min-height: 0;
102
+ overflow-y: auto;
103
+ padding: var(--space-2, 8px) var(--space-3, 12px);
104
+ font-family: var(--font-mono, monospace);
105
+ font-size: var(--text-sm, 13px);
106
+ line-height: 1.6;
107
+ color: var(--color-text-primary, #e8e0d0);
108
+ }
109
+
110
+ .ds-block-editable {
111
+ min-height: 100%;
112
+ outline: none;
113
+ white-space: pre-wrap;
114
+ word-break: break-word;
115
+ caret-color: var(--color-accent, #b87333);
116
+ }
117
+
118
+ .ds-block-editable:empty::before {
119
+ content: 'Start writing...';
120
+ color: var(--color-text-muted, #a09880);
121
+ pointer-events: none;
122
+ }
123
+
124
+ .ds-block-rendered {
125
+ min-height: 2em;
126
+ }
127
+
128
+ /* Markdown rendered styles — scoped under :global for {@html} content */
129
+ .ds-block-rendered :global(.md-h) {
130
+ margin: 12px 0 6px 0;
131
+ font-weight: 600;
132
+ color: var(--color-text-primary, #e8e0d0);
133
+ }
134
+
135
+ .ds-block-rendered :global(.md-h1) { font-size: 1.4em; }
136
+ .ds-block-rendered :global(.md-h2) { font-size: 1.2em; }
137
+ .ds-block-rendered :global(.md-h3) { font-size: 1.1em; }
138
+ .ds-block-rendered :global(.md-h4),
139
+ .ds-block-rendered :global(.md-h5),
140
+ .ds-block-rendered :global(.md-h6) { font-size: 1em; }
141
+
142
+ .ds-block-rendered :global(.md-p) {
143
+ margin: 6px 0;
144
+ }
145
+
146
+ .ds-block-rendered :global(.md-ul) {
147
+ margin: 6px 0;
148
+ padding-left: 20px;
149
+ list-style: none;
150
+ }
151
+
152
+ .ds-block-rendered :global(.md-li) {
153
+ margin: 3px 0;
154
+ }
155
+
156
+ .ds-block-rendered :global(.md-li)::before {
157
+ content: '• ';
158
+ color: var(--color-accent, #b87333);
159
+ }
160
+
161
+ .ds-block-rendered :global(.md-bq) {
162
+ margin: 6px 0;
163
+ padding: 6px 10px;
164
+ border-left: 3px solid var(--color-accent, #b87333);
165
+ background: rgba(184, 115, 51, 0.08);
166
+ font-style: italic;
167
+ }
168
+
169
+ .ds-block-rendered :global(.md-hr) {
170
+ border: none;
171
+ border-top: 1px solid var(--color-border, #333);
172
+ margin: 12px 0;
173
+ }
174
+
175
+ .ds-block-rendered :global(.md-gap) {
176
+ height: 6px;
177
+ }
178
+
179
+ .ds-block-rendered :global(.md-code) {
180
+ padding: 1px 5px;
181
+ background: rgba(0, 0, 0, 0.3);
182
+ border-radius: var(--radius-sm, 3px);
183
+ font-family: var(--font-mono, monospace);
184
+ font-size: 0.9em;
185
+ color: var(--color-accent, #b87333);
186
+ }
187
+
188
+ .ds-block-rendered :global(strong) {
189
+ font-weight: 600;
190
+ color: var(--color-text-primary, #e8e0d0);
191
+ }
192
+
193
+ .ds-block-rendered :global(em) {
194
+ font-style: italic;
195
+ color: var(--color-text-muted, #a09880);
196
+ }
197
+
198
+ .ds-block-rendered :global(del) {
199
+ text-decoration: line-through;
200
+ color: var(--color-text-muted, #a09880);
201
+ }
202
+
203
+ .ds-block-rendered :global(.md-wiki) {
204
+ color: var(--color-accent, #b87333);
205
+ text-decoration: underline;
206
+ text-decoration-style: dotted;
207
+ cursor: pointer;
208
+ }
209
+
210
+ .ds-block-rendered :global(.md-wiki:hover) {
211
+ text-decoration-style: solid;
212
+ }
213
+ </style>
@@ -0,0 +1,230 @@
1
+ <script lang="ts">
2
+ import { GripVertical, SplitSquareHorizontal, SplitSquareVertical, X } from '@lucide/svelte';
3
+ import Badge from '../../u0-primitives/badge/Badge.svelte';
4
+ import type { WritingPhase } from './block-writer-types.js';
5
+ import type { SplitDirection } from '../../spatial/zone-tiler-types.js';
6
+ import { PHASE_CONFIG } from './block-writer-types.js';
7
+
8
+ interface Props {
9
+ blockId: string;
10
+ title: string;
11
+ phase: WritingPhase;
12
+ isFocused: boolean;
13
+ allowHorizontalSplit?: boolean;
14
+ onPhaseChange?: (blockId: string, phase: WritingPhase) => void;
15
+ onSplit?: (blockId: string, direction: SplitDirection) => void;
16
+ onDelete?: (blockId: string) => void;
17
+ onTitleChange?: (blockId: string, title: string) => void;
18
+ }
19
+
20
+ let {
21
+ blockId,
22
+ title,
23
+ phase,
24
+ isFocused,
25
+ allowHorizontalSplit = true,
26
+ onPhaseChange,
27
+ onSplit,
28
+ onDelete,
29
+ onTitleChange,
30
+ }: Props = $props();
31
+
32
+ const phaseConfig = $derived(PHASE_CONFIG[phase]);
33
+
34
+ const phases: WritingPhase[] = ['seed', 'sprout', 'go', 'freeze', 'compost'];
35
+
36
+ function cyclePhase() {
37
+ const idx = phases.indexOf(phase);
38
+ const next = phases[(idx + 1) % phases.length];
39
+ onPhaseChange?.(blockId, next);
40
+ }
41
+
42
+ function handleTitleInput(e: Event) {
43
+ const target = e.target as HTMLElement;
44
+ onTitleChange?.(blockId, target.textContent ?? '');
45
+ }
46
+
47
+ function handleTitleKeydown(e: KeyboardEvent) {
48
+ if (e.key === 'Enter') {
49
+ e.preventDefault();
50
+ (e.target as HTMLElement).blur();
51
+ }
52
+ }
53
+ </script>
54
+
55
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
56
+ <div class="ds-block-header" class:ds-block-header-focused={isFocused}>
57
+ <!-- Phase color bar (echoes KanbanCard priority bar) -->
58
+ <div
59
+ class="ds-block-phase-bar"
60
+ style="background-color: var(--color-{phaseConfig.color});"
61
+ title="Phase: {phaseConfig.label}"
62
+ ></div>
63
+
64
+ <!-- Drag handle -->
65
+ <div class="ds-block-drag-handle" title="Drag to reorder">
66
+ <GripVertical size={14} />
67
+ </div>
68
+
69
+ <!-- Phase badge (click to cycle) -->
70
+ <button
71
+ class="ds-block-phase-btn"
72
+ onclick={cyclePhase}
73
+ title="Cycle phase (current: {phaseConfig.label})"
74
+ >
75
+ <Badge variant="status" color={phaseConfig.color} size="sm">
76
+ {phaseConfig.label}
77
+ </Badge>
78
+ </button>
79
+
80
+ <!-- Editable title -->
81
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
82
+ <div
83
+ class="ds-block-title"
84
+ contenteditable="true"
85
+ spellcheck="false"
86
+ tabindex="0"
87
+ role="textbox"
88
+ aria-label="Block title"
89
+ oninput={handleTitleInput}
90
+ onkeydown={handleTitleKeydown}
91
+ >{title}</div>
92
+
93
+ <!-- Actions (visible on focus/hover) -->
94
+ <div class="ds-block-actions">
95
+ {#if onSplit}
96
+ <button
97
+ class="ds-block-action-btn"
98
+ onclick={() => onSplit(blockId, 'vertical')}
99
+ title="Split below (Ctrl+Shift+V)"
100
+ aria-label="Split block below"
101
+ >
102
+ <SplitSquareVertical size={14} />
103
+ </button>
104
+ {#if allowHorizontalSplit}
105
+ <button
106
+ class="ds-block-action-btn"
107
+ onclick={() => onSplit(blockId, 'horizontal')}
108
+ title="Split beside (Ctrl+Shift+H)"
109
+ aria-label="Split block beside"
110
+ >
111
+ <SplitSquareHorizontal size={14} />
112
+ </button>
113
+ {/if}
114
+ {/if}
115
+ {#if onDelete}
116
+ <button
117
+ class="ds-block-action-btn ds-block-action-delete"
118
+ onclick={() => onDelete(blockId)}
119
+ title="Delete block"
120
+ aria-label="Delete block"
121
+ >
122
+ <X size={14} />
123
+ </button>
124
+ {/if}
125
+ </div>
126
+ </div>
127
+
128
+ <style>
129
+ .ds-block-header {
130
+ display: flex;
131
+ align-items: center;
132
+ gap: var(--space-2, 8px);
133
+ padding: var(--space-2, 8px) var(--space-3, 12px);
134
+ position: relative;
135
+ min-height: 32px;
136
+ }
137
+
138
+ .ds-block-phase-bar {
139
+ position: absolute;
140
+ top: 0;
141
+ left: 0;
142
+ width: 3px;
143
+ height: 100%;
144
+ border-radius: var(--radius-xs, 2px) 0 0 var(--radius-xs, 2px);
145
+ }
146
+
147
+ .ds-block-drag-handle {
148
+ color: var(--color-text-muted, #a09880);
149
+ cursor: grab;
150
+ padding: 2px;
151
+ border-radius: var(--radius-sm, 4px);
152
+ flex-shrink: 0;
153
+ transition: color var(--transition-fast, 150ms ease);
154
+ }
155
+
156
+ .ds-block-drag-handle:hover {
157
+ color: var(--color-text-secondary, #c0b8a8);
158
+ background: var(--color-bg-panel, #222);
159
+ }
160
+
161
+ .ds-block-phase-btn {
162
+ background: none;
163
+ border: none;
164
+ padding: 0;
165
+ cursor: pointer;
166
+ flex-shrink: 0;
167
+ }
168
+
169
+ .ds-block-title {
170
+ flex: 1;
171
+ min-width: 0;
172
+ font-size: var(--text-sm, 13px);
173
+ font-weight: 500;
174
+ color: var(--color-text-primary, #e8e0d0);
175
+ line-height: 1.3;
176
+ outline: none;
177
+ cursor: text;
178
+ padding: 1px 4px;
179
+ border-radius: var(--radius-sm, 4px);
180
+ transition: background var(--transition-fast, 150ms ease);
181
+ }
182
+
183
+ .ds-block-title:focus {
184
+ background: rgba(255, 255, 255, 0.04);
185
+ }
186
+
187
+ .ds-block-title:empty::before {
188
+ content: 'Untitled block';
189
+ color: var(--color-text-muted, #a09880);
190
+ pointer-events: none;
191
+ }
192
+
193
+ .ds-block-actions {
194
+ display: flex;
195
+ align-items: center;
196
+ gap: 2px;
197
+ opacity: 0;
198
+ transition: opacity var(--transition-fast, 150ms ease);
199
+ flex-shrink: 0;
200
+ }
201
+
202
+ .ds-block-header-focused .ds-block-actions,
203
+ .ds-block-header:hover .ds-block-actions {
204
+ opacity: 1;
205
+ }
206
+
207
+ .ds-block-action-btn {
208
+ display: flex;
209
+ align-items: center;
210
+ justify-content: center;
211
+ background: transparent;
212
+ border: none;
213
+ color: var(--color-text-muted, #a09880);
214
+ padding: 4px;
215
+ border-radius: var(--radius-sm, 4px);
216
+ cursor: pointer;
217
+ transition: color var(--transition-fast, 150ms ease),
218
+ background var(--transition-fast, 150ms ease);
219
+ }
220
+
221
+ .ds-block-action-btn:hover {
222
+ color: var(--color-text-secondary, #c0b8a8);
223
+ background: rgba(255, 255, 255, 0.06);
224
+ }
225
+
226
+ .ds-block-action-delete:hover {
227
+ color: var(--color-error, #a65e5e);
228
+ background: rgba(166, 94, 94, 0.15);
229
+ }
230
+ </style>
@@ -0,0 +1,177 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { WritingBlock, WritingPhase, SplitDirection } from './block-writer-types.js';
4
+ import type { ZoneRect } from '../../spatial/zone-tiler-types.js';
5
+ import BlockHeader from './BlockHeader.svelte';
6
+ import BlockContent from './BlockContent.svelte';
7
+
8
+ interface Props {
9
+ block: WritingBlock;
10
+ zoneRect: ZoneRect;
11
+ isFocused: boolean;
12
+ isEditing: boolean;
13
+ allowHorizontalSplit?: boolean;
14
+ onEditStart?: (blockId: string) => void;
15
+ onEditEnd?: (blockId: string) => void;
16
+ onContentChange?: (blockId: string, content: string) => void;
17
+ onPhaseChange?: (blockId: string, phase: WritingPhase) => void;
18
+ onSplit?: (blockId: string, direction: SplitDirection) => void;
19
+ onDelete?: (blockId: string) => void;
20
+ onTitleChange?: (blockId: string, title: string) => void;
21
+ footer?: Snippet<[WritingBlock]>;
22
+ }
23
+
24
+ let {
25
+ block,
26
+ zoneRect,
27
+ isFocused,
28
+ isEditing,
29
+ allowHorizontalSplit = true,
30
+ onEditStart,
31
+ onEditEnd,
32
+ onContentChange,
33
+ onPhaseChange,
34
+ onSplit,
35
+ onDelete,
36
+ onTitleChange,
37
+ footer,
38
+ }: Props = $props();
39
+
40
+ const isTiny = $derived(!isEditing && (zoneRect.width * zoneRect.height < 2000 || zoneRect.width < 80 || zoneRect.height < 60));
41
+ </script>
42
+
43
+ <div
44
+ class="ds-block-item"
45
+ class:ds-block-item-focused={isFocused}
46
+ class:ds-block-item-editing={isEditing}
47
+ class:ds-block-item-tiny={isTiny}
48
+ style:width="{zoneRect.width}px"
49
+ style:height="{zoneRect.height}px"
50
+ >
51
+ {#if isTiny}
52
+ <div class="ds-tiny-indicator ds-phase-{block.phase}">
53
+ <div class="ds-tiny-id">{block.id.slice(0, 4)}</div>
54
+ {#if isFocused}
55
+ <div class="ds-tiny-hint">Press Enter to edit</div>
56
+ {/if}
57
+ </div>
58
+ {:else}
59
+ <BlockHeader
60
+ blockId={block.id}
61
+ title={block.title}
62
+ phase={block.phase}
63
+ isFocused={isFocused}
64
+ allowHorizontalSplit={allowHorizontalSplit}
65
+ onPhaseChange={onPhaseChange}
66
+ onSplit={onSplit}
67
+ onDelete={onDelete}
68
+ onTitleChange={onTitleChange}
69
+ />
70
+
71
+ <BlockContent
72
+ content={block.content}
73
+ isEditing={isEditing}
74
+ onEditStart={() => onEditStart?.(block.id)}
75
+ onEditEnd={() => onEditEnd?.(block.id)}
76
+ onContentChange={(content) => onContentChange?.(block.id, content)}
77
+ />
78
+ {/if}
79
+
80
+ {#if footer}
81
+ <div class="ds-block-footer">
82
+ {@render footer(block)}
83
+ </div>
84
+ {/if}
85
+
86
+ {#if block.annotation}
87
+ <div class="ds-block-annotation">
88
+ {block.annotation}
89
+ </div>
90
+ {/if}
91
+ </div>
92
+
93
+ <style>
94
+ .ds-block-item {
95
+ display: flex;
96
+ flex-direction: column;
97
+ background: var(--color-bg-panel, #222);
98
+ border-radius: var(--radius-md, 8px);
99
+ overflow: hidden;
100
+ position: relative;
101
+ transition: box-shadow 0.15s ease;
102
+ }
103
+
104
+ .ds-block-item-focused {
105
+ box-shadow: inset 0 0 0 1.5px var(--color-accent, #b87333);
106
+ }
107
+
108
+ .ds-block-item-editing {
109
+ box-shadow: inset 0 0 0 1.5px var(--color-accent, #b87333),
110
+ 0 0 12px rgba(184, 115, 51, 0.12);
111
+ }
112
+
113
+ .ds-block-item-tiny {
114
+ background: transparent;
115
+ }
116
+
117
+ .ds-tiny-indicator {
118
+ width: 100%;
119
+ height: 100%;
120
+ display: flex;
121
+ flex-direction: column;
122
+ align-items: center;
123
+ justify-content: center;
124
+ padding: var(--space-1, 4px);
125
+ border-radius: var(--radius-md, 8px);
126
+ background: var(--color-bg-panel, #222);
127
+ cursor: pointer;
128
+ overflow: hidden;
129
+ }
130
+
131
+ .ds-tiny-indicator.ds-phase-seed { border-left: 3px solid var(--color-phase-seed, #8fbc8f); }
132
+ .ds-tiny-indicator.ds-phase-sprout { border-left: 3px solid var(--color-phase-sprout, #90ee90); }
133
+ .ds-tiny-indicator.ds-phase-flower { border-left: 3px solid var(--color-phase-flower, #ffa500); }
134
+ .ds-tiny-indicator.ds-phase-scelle { border-left: 3px solid var(--color-phase-scelle, #ff4500); }
135
+
136
+ .ds-tiny-id {
137
+ font-family: var(--font-mono, monospace);
138
+ font-size: var(--text-xs, 10px);
139
+ color: var(--color-text-muted, #a09880);
140
+ opacity: 0.6;
141
+ }
142
+
143
+ .ds-tiny-hint {
144
+ font-size: var(--text-xs, 10px);
145
+ color: var(--color-accent, #b87333);
146
+ margin-top: 4px;
147
+ text-align: center;
148
+ line-height: 1.1;
149
+ }
150
+
151
+ .ds-block-footer {
152
+ display: flex;
153
+ align-items: center;
154
+ gap: var(--space-2, 8px);
155
+ padding: var(--space-1, 4px) var(--space-3, 12px) var(--space-2, 8px);
156
+ font-size: var(--text-xs, 11px);
157
+ color: var(--color-text-muted, #a09880);
158
+ border-top: 1px solid var(--color-border, #333);
159
+ flex-shrink: 0;
160
+ }
161
+
162
+ .ds-block-annotation {
163
+ position: absolute;
164
+ top: var(--space-2, 8px);
165
+ right: var(--space-2, 8px);
166
+ max-width: 200px;
167
+ padding: var(--space-1, 4px) var(--space-2, 8px);
168
+ background: rgba(184, 115, 51, 0.12);
169
+ border: 1px solid rgba(184, 115, 51, 0.25);
170
+ border-radius: var(--radius-sm, 4px);
171
+ font-size: var(--text-xs, 11px);
172
+ color: var(--color-accent, #b87333);
173
+ font-style: italic;
174
+ pointer-events: none;
175
+ z-index: 1;
176
+ }
177
+ </style>