dsh-memory-cloud 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/nebula.css ADDED
@@ -0,0 +1,283 @@
1
+ :root {
2
+ --mc-bg: #070a1c;
3
+ --mc-bg-2: #0d1330;
4
+ --mc-fg: #eef1ff;
5
+ --mc-muted: #97a2cc;
6
+ --mc-line: rgba(150, 170, 255, 0.16);
7
+ --mc-panel: rgba(16, 22, 50, 0.82);
8
+ --mc-accent: #8aa0ff;
9
+ /* Respect the iOS notch / home indicator via viewport-fit=cover. */
10
+ --mc-safe-t: env(safe-area-inset-top, 0px);
11
+ --mc-safe-b: env(safe-area-inset-bottom, 0px);
12
+ --mc-safe-l: env(safe-area-inset-left, 0px);
13
+ --mc-safe-r: env(safe-area-inset-right, 0px);
14
+ }
15
+
16
+ * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
17
+ [hidden] { display: none !important; }
18
+ html, body { margin: 0; height: 100%; overflow: hidden; }
19
+ body {
20
+ background: radial-gradient(ellipse at 50% 40%, var(--mc-bg-2), var(--mc-bg) 70%);
21
+ color: var(--mc-fg);
22
+ font-family: "Inter", "PingFang SC", "Microsoft YaHei", system-ui, sans-serif;
23
+ -webkit-font-smoothing: antialiased;
24
+ overscroll-behavior: none;
25
+ touch-action: none;
26
+ }
27
+ #sky {
28
+ position: fixed; inset: 0; display: block; width: 100%; height: 100%;
29
+ cursor: grab; touch-action: none;
30
+ }
31
+ #sky.dragging { cursor: grabbing; }
32
+
33
+ .topbar {
34
+ position: fixed; top: 0; left: 0; right: 0;
35
+ display: flex; align-items: center; justify-content: space-between; gap: 16px;
36
+ padding: calc(14px + var(--mc-safe-t)) calc(20px + var(--mc-safe-r)) 14px calc(20px + var(--mc-safe-l));
37
+ background: linear-gradient(to bottom, rgba(6, 9, 26, 0.92), rgba(6, 9, 26, 0));
38
+ z-index: 5; pointer-events: none;
39
+ }
40
+ .brand { display: flex; align-items: center; gap: 12px; min-width: 0; }
41
+ .brand-mark {
42
+ width: 30px; height: 30px; border-radius: 9px; flex: 0 0 auto;
43
+ background: radial-gradient(circle at 40% 40%, #fff2c4 0%, #ffab5e 22%, #a25bd6 55%, rgba(60, 42, 128, 0) 78%),
44
+ radial-gradient(circle at 50% 50%, rgba(124, 92, 255, 0.55), transparent 70%);
45
+ box-shadow: 0 0 22px rgba(140, 110, 255, 0.55);
46
+ }
47
+ .brand-text h1 { margin: 0; font-size: 17px; font-weight: 650; letter-spacing: 0.5px; }
48
+ .brand-text p { margin: 2px 0 0; font-size: 11.5px; color: var(--mc-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 46vw; }
49
+ .topbar-actions { display: flex; align-items: center; gap: 8px; pointer-events: auto; }
50
+ .search {
51
+ display: flex; align-items: center; gap: 7px;
52
+ background: var(--mc-panel); border: 1px solid var(--mc-line); border-radius: 9px;
53
+ padding: 0 10px; height: 34px; backdrop-filter: blur(12px);
54
+ }
55
+ .search svg { width: 14px; height: 14px; fill: none; stroke: var(--mc-muted); stroke-width: 1.6; flex: 0 0 auto; }
56
+ .search input { border: 0; background: transparent; color: var(--mc-fg); font: inherit; font-size: 12.5px; outline: none; width: 190px; }
57
+ .search input::placeholder { color: var(--mc-muted); }
58
+ .chip {
59
+ height: 34px; min-width: 34px; padding: 0 12px; border-radius: 9px; cursor: pointer;
60
+ display: inline-flex; align-items: center; justify-content: center; gap: 5px;
61
+ background: var(--mc-panel); border: 1px solid var(--mc-line); color: var(--mc-muted);
62
+ font: inherit; font-size: 12px; font-weight: 550; white-space: nowrap; backdrop-filter: blur(12px);
63
+ transition: color .15s, border-color .15s, background .15s;
64
+ }
65
+ .chip:hover { color: var(--mc-fg); border-color: var(--mc-accent); }
66
+ .chip.active { color: #fff; border-color: rgba(138, 160, 255, .6); background: rgba(90, 110, 220, .42); }
67
+ .chip.close { width: 34px; padding: 0; font-size: 14px; }
68
+ /* Author `display` on .chip overrides the UA [hidden] rule, so state it here. */
69
+ .chip[hidden] { display: none; }
70
+ .chip-glyph { font-size: 15px; line-height: 1; }
71
+ .chip-label { display: inline; }
72
+
73
+ /* Detail-level badge: shows which zoom band the reader is currently in. */
74
+ .lod-badge {
75
+ display: inline-flex; align-items: center; height: 26px; padding: 0 10px;
76
+ border-radius: 7px; border: 1px solid var(--mc-line); background: var(--mc-panel);
77
+ color: var(--mc-muted); font-size: 11px; font-weight: 600; letter-spacing: .3px;
78
+ white-space: nowrap; backdrop-filter: blur(12px); user-select: none;
79
+ }
80
+ .lod-badge[data-lod="1"] { color: #9fd0ff; border-color: rgba(120, 180, 255, .45); }
81
+ .lod-badge[data-lod="2"] { color: #c3aaff; border-color: rgba(170, 140, 255, .5); background: rgba(70, 50, 130, .42); }
82
+ .lod-badge[data-lod="3"] { color: #7fe6bd; border-color: rgba(110, 230, 190, .55); background: rgba(30, 90, 75, .45); }
83
+
84
+ /* Skin switch: a two-option segmented control in the toolbar. */
85
+ .skin-switch {
86
+ display: inline-flex; align-items: center; gap: 2px; height: 34px; padding: 3px;
87
+ background: var(--mc-panel); border: 1px solid var(--mc-line); border-radius: 9px;
88
+ backdrop-filter: blur(12px);
89
+ }
90
+ .skin-btn {
91
+ display: inline-flex; align-items: center; gap: 5px; height: 26px; padding: 0 10px;
92
+ border: 0; border-radius: 6px; background: transparent; color: var(--mc-muted);
93
+ font: inherit; font-size: 11.5px; font-weight: 600; white-space: nowrap; cursor: pointer;
94
+ transition: color .15s, background .15s;
95
+ }
96
+ .skin-btn:hover { color: var(--mc-fg); }
97
+ .skin-btn.active { color: #fff; background: rgba(90, 110, 220, .5); }
98
+ .skin-ico { font-size: 13px; line-height: 1; }
99
+
100
+ /* Relation legend: what the links between clusters mean. */
101
+ .relation-key {
102
+ position: fixed; right: calc(20px + var(--mc-safe-r)); top: calc(74px + var(--mc-safe-t)); z-index: 4;
103
+ display: flex; flex-direction: column; gap: 5px;
104
+ background: var(--mc-panel); border: 1px solid var(--mc-line); border-radius: 9px;
105
+ padding: 9px 11px; backdrop-filter: blur(10px); pointer-events: none;
106
+ max-width: 210px;
107
+ }
108
+ .relation-key h3 { margin: 0 0 2px; font-size: 10.5px; font-weight: 650; color: var(--mc-muted); letter-spacing: .4px; }
109
+ .relation-key .rk-row { display: flex; align-items: center; gap: 7px; font-size: 10.5px; color: var(--mc-muted); }
110
+ .relation-key .rk-line { width: 26px; height: 0; border-top: 2px solid var(--mc-accent); flex: 0 0 auto; }
111
+ .relation-key .rk-line.fork { border-top: 2px dashed #ffd591; }
112
+ .relation-key .rk-line.topic { border-top-width: 2px; }
113
+ .relation-key .rk-line.workspace { border-top-width: 3px; }
114
+ .relation-key .rk-line.topic { border-top-color: #8aa0ff; }
115
+ .relation-key .rk-line.workspace { border-top-color: #7de0c0; }
116
+ .relation-key .rk-count { margin-top: 3px; font-size: 10px; color: var(--mc-fg); font-variant-numeric: tabular-nums; }
117
+
118
+ .legend {
119
+ position: fixed; left: calc(20px + var(--mc-safe-l)); top: calc(74px + var(--mc-safe-t)); z-index: 4;
120
+ display: flex; flex-direction: column; gap: 6px; max-height: 56vh; overflow: auto;
121
+ pointer-events: auto; scrollbar-width: none;
122
+ }
123
+ .legend::-webkit-scrollbar { display: none; }
124
+ .legend-item {
125
+ display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--mc-muted);
126
+ background: var(--mc-panel); border: 1px solid var(--mc-line); border-radius: 7px;
127
+ padding: 4px 9px 4px 7px; backdrop-filter: blur(10px); cursor: pointer; max-width: 260px;
128
+ }
129
+ .legend-item:hover { color: var(--mc-fg); }
130
+ .legend-item .dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; box-shadow: 0 0 8px currentColor; }
131
+ .legend-item .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
132
+ .legend-item .count { margin-left: auto; color: var(--mc-muted); opacity: .8; font-variant-numeric: tabular-nums; }
133
+ .legend-item.dim { opacity: .38; }
134
+
135
+ .stats {
136
+ position: fixed; left: calc(20px + var(--mc-safe-l)); bottom: calc(20px + var(--mc-safe-b)); z-index: 4;
137
+ display: flex; flex-wrap: wrap; gap: 8px 18px; max-width: 62vw;
138
+ background: var(--mc-panel); border: 1px solid var(--mc-line); border-radius: 10px;
139
+ padding: 10px 14px; backdrop-filter: blur(12px); pointer-events: none;
140
+ }
141
+ .stat { display: flex; flex-direction: column; gap: 2px; }
142
+ .stat b { font-size: 16px; font-weight: 650; font-variant-numeric: tabular-nums; }
143
+ .stat span { font-size: 10.5px; color: var(--mc-muted); letter-spacing: .3px; }
144
+
145
+ .hint {
146
+ position: fixed; right: calc(20px + var(--mc-safe-r)); bottom: calc(20px + var(--mc-safe-b)); z-index: 4;
147
+ font-size: 11px; color: var(--mc-muted); background: var(--mc-panel);
148
+ border: 1px solid var(--mc-line); border-radius: 8px; padding: 7px 11px; backdrop-filter: blur(10px);
149
+ pointer-events: none;
150
+ }
151
+
152
+ .tooltip {
153
+ position: fixed; z-index: 8; pointer-events: none; max-width: 300px;
154
+ background: var(--mc-panel); border: 1px solid var(--mc-accent); border-radius: 9px;
155
+ padding: 9px 11px; backdrop-filter: blur(14px); box-shadow: 0 8px 28px rgba(0,0,0,.35);
156
+ font-size: 12px; line-height: 1.5;
157
+ }
158
+ .tooltip .tt-title { font-weight: 650; margin-bottom: 3px; }
159
+ .tooltip .tt-sub { color: var(--mc-muted); font-size: 10.5px; }
160
+ .tooltip .tt-text { margin-top: 5px; color: var(--mc-fg); opacity: .92; word-break: break-word; }
161
+
162
+ .detail {
163
+ position: fixed; z-index: 7; top: calc(66px + var(--mc-safe-t)); right: calc(20px + var(--mc-safe-r));
164
+ bottom: calc(20px + var(--mc-safe-b)); width: min(380px, 40vw);
165
+ background: var(--mc-panel); border: 1px solid var(--mc-line); border-radius: 14px;
166
+ backdrop-filter: blur(18px); padding: 18px 18px 6px; overflow: auto;
167
+ box-shadow: 0 12px 40px rgba(0,0,0,.4);
168
+ -webkit-overflow-scrolling: touch; touch-action: pan-y;
169
+ }
170
+ .detail-grip { display: none; }
171
+ .detail h2 { margin: 0 40px 6px 0; font-size: 16px; font-weight: 650; word-break: break-word; }
172
+ .detail-close {
173
+ position: absolute; top: 14px; right: 14px; width: 30px; height: 30px;
174
+ border: 1px solid var(--mc-line); border-radius: 8px; background: transparent;
175
+ color: var(--mc-muted); cursor: pointer; font-size: 14px;
176
+ }
177
+ .detail-close:hover { color: var(--mc-fg); }
178
+ .detail-meta { font-size: 11px; color: var(--mc-muted); display: flex; flex-wrap: wrap; gap: 6px 14px; margin-bottom: 14px; }
179
+ .detail-fragments { display: flex; flex-direction: column; gap: 9px; padding-bottom: 12px; }
180
+ .frag { border: 1px solid var(--mc-line); border-radius: 9px; padding: 9px 11px; font-size: 12px; line-height: 1.55; word-break: break-word; }
181
+ .frag.user { border-left: 3px solid #6fd0ff; }
182
+ .frag.assistant { border-left: 3px solid #c08bff; }
183
+ .frag .who { display: block; font-size: 10px; color: var(--mc-muted); margin-bottom: 3px; letter-spacing: .4px; }
184
+
185
+ .loading {
186
+ position: fixed; inset: 0; z-index: 9; display: flex; align-items: center; justify-content: center; gap: 12px;
187
+ background: var(--mc-bg); color: var(--mc-muted); font-size: 13px;
188
+ padding-bottom: var(--mc-safe-b);
189
+ }
190
+ .loading[hidden] { display: none; }
191
+ .spinner { width: 18px; height: 18px; border: 2px solid var(--mc-line); border-top-color: var(--mc-accent); border-radius: 50%; animation: spin .8s linear infinite; }
192
+ @keyframes spin { to { transform: rotate(360deg); } }
193
+
194
+ /* ---------------------------------------------------------------------------
195
+ Mobile: compact toolbar, horizontal workspace chips, full-width bottom sheet,
196
+ and a stats strip pinned above the bottom edge.
197
+ --------------------------------------------------------------------------- */
198
+ @media (max-width: 820px) {
199
+ .topbar { gap: 8px; padding: calc(10px + var(--mc-safe-t)) calc(12px + var(--mc-safe-r)) 10px calc(12px + var(--mc-safe-l)); }
200
+ .brand-mark { width: 26px; height: 26px; border-radius: 8px; }
201
+ .brand-text h1 { font-size: 15px; }
202
+ .brand-text p { font-size: 10.5px; max-width: 40vw; }
203
+ .topbar-actions { gap: 6px; }
204
+ .lod-badge { height: 28px; padding: 0 8px; font-size: 10px; }
205
+ .skin-switch { height: 36px; padding: 3px; }
206
+ .skin-btn { height: 28px; padding: 0 8px; font-size: 0; gap: 0; }
207
+ .skin-ico { font-size: 15px; }
208
+ .skin-txt { display: none; }
209
+ .search { height: 36px; padding: 0 9px; }
210
+ .search input { width: 92px; font-size: 16px; /* 16px avoids iOS auto-zoom on focus */ }
211
+ .chip { height: 36px; min-width: 36px; padding: 0 9px; }
212
+ .chip-label { display: none; }
213
+ .chip.close { width: 36px; }
214
+
215
+ /* On a phone the relation key becomes a compact one-line chip under legend. */
216
+ .relation-key {
217
+ right: auto; left: calc(12px + var(--mc-safe-l));
218
+ top: calc(104px + var(--mc-safe-t)); max-width: none;
219
+ flex-direction: row; align-items: center; gap: 8px; padding: 6px 10px;
220
+ }
221
+ .relation-key h3, .relation-key .rk-row { display: none; }
222
+ .relation-key .rk-count { margin: 0; font-size: 10px; }
223
+
224
+ /* Legend becomes a single horizontal, swipeable row under the toolbar. */
225
+ .legend {
226
+ left: calc(12px + var(--mc-safe-l)); right: calc(12px + var(--mc-safe-r));
227
+ top: calc(60px + var(--mc-safe-t)); max-height: none;
228
+ flex-direction: row; gap: 6px; overflow-x: auto; overflow-y: hidden;
229
+ padding-bottom: 2px;
230
+ }
231
+ .legend-item { flex: 0 0 auto; max-width: 190px; font-size: 11px; padding: 6px 10px; }
232
+ .legend-item .name { max-width: 130px; }
233
+
234
+ /* Stats strip: one compact row, full width, above the safe bottom. */
235
+ .stats {
236
+ left: calc(12px + var(--mc-safe-l)); right: calc(12px + var(--mc-safe-r));
237
+ bottom: calc(12px + var(--mc-safe-b)); max-width: none;
238
+ gap: 4px 12px; padding: 8px 12px; justify-content: space-between;
239
+ flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
240
+ }
241
+ /* Keep the five numbers that fit a phone; deeper counts stay in the panel. */
242
+ .stats .stat:nth-child(n+6) { display: none; }
243
+ .stats::-webkit-scrollbar { display: none; }
244
+ .stat { flex: 0 0 auto; }
245
+ .stat b { font-size: 13px; }
246
+ .stat span { font-size: 9px; }
247
+
248
+ .hint { display: none; }
249
+
250
+ /* Detail becomes a bottom sheet occupying the lower ~62% of the screen. */
251
+ .detail {
252
+ top: auto; left: 0; right: 0; bottom: 0; width: 100%;
253
+ max-height: 62vh; border-radius: 18px 18px 0 0;
254
+ border-left: 0; border-right: 0; border-bottom: 0;
255
+ padding: 10px calc(16px + var(--mc-safe-l)) calc(16px + var(--mc-safe-b)) calc(16px + var(--mc-safe-r));
256
+ box-shadow: 0 -12px 40px rgba(0,0,0,.5);
257
+ }
258
+ .detail-grip {
259
+ display: block; width: 38px; height: 4px; border-radius: 999px;
260
+ background: var(--mc-line); margin: 0 auto 12px;
261
+ }
262
+ .detail h2 { font-size: 15px; margin-right: 44px; }
263
+ .detail-close { top: 10px; right: 12px; width: 32px; height: 32px; }
264
+ .frag { font-size: 12.5px; }
265
+
266
+ .tooltip { max-width: min(280px, 78vw); font-size: 11.5px; }
267
+ }
268
+
269
+ @media (max-width: 420px) {
270
+ .brand-text p { display: none; }
271
+ .brand-text h1 { font-size: 14px; }
272
+ .brand-mark { width: 24px; height: 24px; }
273
+ /* Four icon buttons + search + skin switch is too crowded at 390px: drop
274
+ the reset control (the same gesture is a double-tap-free pinch back). */
275
+ #reset { display: none; }
276
+ .search { flex: 1 1 auto; min-width: 0; }
277
+ .search input { width: 100%; min-width: 0; }
278
+ .skin-btn { padding: 0 7px; }
279
+ .stat b { font-size: 12px; }
280
+ .stat span { font-size: 8.5px; }
281
+ .stats { gap: 3px 9px; }
282
+ .stats .stat:last-child { flex: 0 0 auto; }
283
+ }
package/nebula.html ADDED
@@ -0,0 +1,61 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
6
+ <meta name="theme-color" content="#070a1c">
7
+ <meta name="color-scheme" content="dark">
8
+ <meta name="apple-mobile-web-app-capable" content="yes">
9
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
10
+ <title>记忆星云 · Memory Nebula</title>
11
+ <link rel="stylesheet" href="/memory-cloud/nebula.css">
12
+ </head>
13
+ <body>
14
+ <canvas id="sky" aria-label="3D 记忆星云图"></canvas>
15
+
16
+ <header class="topbar">
17
+ <div class="brand">
18
+ <span class="brand-mark" aria-hidden="true"></span>
19
+ <div class="brand-text">
20
+ <h1 id="title">记忆星云</h1>
21
+ <p id="subtitle">正在读取 DSH 会话记忆…</p>
22
+ </div>
23
+ </div>
24
+ <div class="topbar-actions">
25
+ <span id="lod-badge" class="lod-badge" data-lod="0" title="当前细节等级">全景</span>
26
+ <div class="skin-switch" role="group" aria-label="皮肤切换">
27
+ <button id="skin-nebula" class="skin-btn active" type="button" data-skin="nebula" aria-pressed="true"><span class="skin-ico">✦</span><span class="skin-txt">星云</span></button>
28
+ <button id="skin-neuron" class="skin-btn" type="button" data-skin="neuron" aria-pressed="false"><span class="skin-ico">⌁</span><span class="skin-txt">神经元</span></button>
29
+ </div>
30
+ <label class="search">
31
+ <svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="7" cy="7" r="4.5"/><path d="M10.5 10.5 L14 14"/></svg>
32
+ <input id="search" type="search" placeholder="搜索记忆 / 标题 / 工作区" autocomplete="off" spellcheck="false">
33
+ </label>
34
+ <button id="autorotate" class="chip active" type="button" aria-pressed="true" title="自动旋转" aria-label="自动旋转"><span class="chip-glyph">⟳</span><span class="chip-label">自动旋转</span></button>
35
+ <button id="reset" class="chip" type="button" title="复位视角" aria-label="复位视角"><span class="chip-glyph">⌖</span><span class="chip-label">复位视角</span></button>
36
+ <button id="close" class="chip close" type="button" title="关闭" aria-label="关闭">✕</button>
37
+ </div>
38
+ </header>
39
+
40
+ <aside id="legend" class="legend" aria-label="工作区图例"></aside>
41
+
42
+ <div id="relation-key" class="relation-key" aria-label="关联图例"></div>
43
+
44
+ <div id="stats" class="stats" aria-label="统计信息"></div>
45
+
46
+ <div id="tooltip" class="tooltip" hidden></div>
47
+
48
+ <section id="detail" class="detail" hidden aria-label="记忆详情">
49
+ <div class="detail-grip" aria-hidden="true"></div>
50
+ <button id="detail-close" class="detail-close" type="button" aria-label="关闭详情">✕</button>
51
+ <h2 id="detail-title"></h2>
52
+ <div id="detail-meta" class="detail-meta"></div>
53
+ <div id="detail-fragments" class="detail-fragments"></div>
54
+ </section>
55
+
56
+ <div id="hint" class="hint">拖拽旋转 · 滚轮缩放 · 悬停查看 · 点击星团展开记忆</div>
57
+ <div id="loading" class="loading"><span class="spinner"></span><span>正在解码会话日志…</span></div>
58
+
59
+ <script src="/memory-cloud/nebula.js"></script>
60
+ </body>
61
+ </html>