@unson/brainbase-mcp 0.23.6 → 0.23.8

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.
@@ -12,6 +12,14 @@ export interface CanonicalTaskContext extends JsonRecord {
12
12
  /** Opaque host-policy scope, not authority or task input. */
13
13
  storageScope?: string;
14
14
  }
15
+ /** Conditions to review; this metadata is not evidence of completion. */
16
+ export interface CanonicalTaskCompletionContract {
17
+ goal: string;
18
+ criteria: Array<{
19
+ id: string;
20
+ condition: string;
21
+ }>;
22
+ }
15
23
  export interface CanonicalTaskRecord extends JsonRecord {
16
24
  id: string;
17
25
  version: number;
@@ -29,11 +37,13 @@ export interface CanonicalTaskRecord extends JsonRecord {
29
37
  project_codes?: string[];
30
38
  /** What the work is for, in a few words (one per task); null when unlabelled. */
31
39
  purpose_label?: string | null;
40
+ completion_contract?: CanonicalTaskCompletionContract | null;
32
41
  created_at?: string | null;
33
42
  updated_at?: string | null;
34
43
  web_url?: string | null;
35
44
  }
36
45
  export interface CanonicalTaskCreateInput extends JsonRecord {
46
+ completion_contract?: unknown;
37
47
  title: string;
38
48
  description?: string | null;
39
49
  priority?: CanonicalTaskPriority | string | null;
@@ -46,6 +56,7 @@ export interface CanonicalTaskCreateInput extends JsonRecord {
46
56
  purpose_label?: string | null;
47
57
  }
48
58
  export interface CanonicalTaskUpdateInput extends JsonRecord {
59
+ completion_contract?: unknown;
49
60
  title?: string;
50
61
  description?: string | null;
51
62
  priority?: CanonicalTaskPriority | string | null;
@@ -26,6 +26,7 @@ const DEFAULT_LIMIT = 20;
26
26
  const MAX_LIMIT = 100;
27
27
  const RESERVED_IDEMPOTENCY_PREFIXES = ['api:', 'workflow:'];
28
28
  const CREATE_FIELDS = new Set([
29
+ 'completion_contract',
29
30
  'title',
30
31
  'description',
31
32
  'priority',
@@ -38,6 +39,7 @@ const CREATE_FIELDS = new Set([
38
39
  'purpose_label',
39
40
  ]);
40
41
  const MUTABLE_FIELDS = new Set([
42
+ 'completion_contract',
41
43
  'expected_version',
42
44
  'title',
43
45
  'description',
@@ -79,6 +81,39 @@ const MAX_PURPOSE_LABEL_LENGTH = 30;
79
81
  function normalizePurposeLabel(value) {
80
82
  return normalizeString(value, 'purpose_label', { max: MAX_PURPOSE_LABEL_LENGTH });
81
83
  }
84
+ function normalizeCompletionContract(value) {
85
+ if (value == null)
86
+ return null;
87
+ const object = (candidate, keys) => {
88
+ if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)
89
+ || ![Object.prototype, null].includes(Object.getPrototypeOf(candidate))
90
+ || Object.keys(candidate).some((key) => !keys.includes(key))) {
91
+ fail('validation_error', 'completion_contract contains an invalid object');
92
+ }
93
+ return candidate;
94
+ };
95
+ const text = (candidate, field) => {
96
+ if (typeof candidate !== 'string')
97
+ fail('validation_error', `${field} must be a string`);
98
+ return normalizeString(candidate, field, { required: true, max: 2000, multiline: true });
99
+ };
100
+ const contract = object(value, ['goal', 'criteria']);
101
+ const goal = text(contract.goal, 'completion_contract.goal');
102
+ if (!Array.isArray(contract.criteria) || contract.criteria.length < 1 || contract.criteria.length > 50) {
103
+ fail('validation_error', 'completion_contract.criteria must contain 1 to 50 conditions');
104
+ }
105
+ const ids = new Set();
106
+ const criteria = Array.from(contract.criteria, (candidate) => {
107
+ const criterion = object(candidate, ['id', 'condition']);
108
+ if (typeof criterion.id !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(criterion.id)
109
+ || ids.has(criterion.id)) {
110
+ fail('validation_error', 'completion_contract criterion id is invalid or duplicated');
111
+ }
112
+ ids.add(criterion.id);
113
+ return { id: criterion.id, condition: text(criterion.condition, 'completion_contract.criteria.condition') };
114
+ });
115
+ return { goal, criteria };
116
+ }
82
117
  function normalizeIsoDate(value, field) {
83
118
  const text = normalizeString(value, field);
84
119
  if (text == null)
@@ -635,7 +670,9 @@ export class CanonicalTaskService {
635
670
  if (!isCanonicalTaskPriority(priority))
636
671
  fail('validation_error', 'priority is invalid', 400, { field: 'priority' });
637
672
  const purposeLabel = normalizePurposeLabel(input.purpose_label);
673
+ const completionContract = normalizeCompletionContract(input.completion_contract);
638
674
  return {
675
+ ...(completionContract ? { completion_contract: completionContract } : {}),
639
676
  title,
640
677
  description: normalizeString(input.description, 'description', { max: 10000, multiline: true }),
641
678
  priority,
@@ -656,6 +693,8 @@ export class CanonicalTaskService {
656
693
  if (unknownFields.length > 0)
657
694
  fail('validation_error', 'Task update contains unsupported fields', 400, { fields: unknownFields });
658
695
  const patch = {};
696
+ if ('completion_contract' in input)
697
+ patch.completion_contract = normalizeCompletionContract(input.completion_contract);
659
698
  if ('title' in input)
660
699
  patch.title = normalizeString(input.title, 'title', { required: true, max: MAX_TITLE_LENGTH });
661
700
  if ('description' in input)
@@ -857,6 +896,12 @@ export class CanonicalTaskService {
857
896
  response.project_codes = [];
858
897
  if (response.purpose_label === undefined)
859
898
  response.purpose_label = null;
899
+ try {
900
+ response.completion_contract = normalizeCompletionContract(response.completion_contract);
901
+ }
902
+ catch {
903
+ fail('task_store_invalid', 'Stored Task completion contract is invalid', 503);
904
+ }
860
905
  return response;
861
906
  }
862
907
  }
@@ -347,7 +347,7 @@ export function createWorldExtension(options = {}) {
347
347
  return {
348
348
  id: WORLD_EXTENSION_ID,
349
349
  uiDir: fileURLToPath(new URL('../ui/world/', import.meta.url)),
350
- uiFiles: ['world-view.js', 'world-view.css', 'world-vendor.js', 'world-placement.js', 'world-work-rail.js', 'world-district.js', 'world-scenery.js'],
350
+ uiFiles: ['world-view.js', 'world-view.css', 'world-canvas-ui.js', 'world-canvas-ui.css', 'world-canvas-notes.js', 'world-vendor.js', 'world-placement.js', 'world-work-rail.js', 'world-district.js', 'world-scenery.js'],
351
351
  screenEntry: 'world-view.js',
352
352
  // P17: the world sits above the home and opens first.
353
353
  navPosition: 'first',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unson/brainbase-mcp",
3
- "version": "0.23.6",
3
+ "version": "0.23.8",
4
4
  "description": "Local-first Brainbase judgment OS that lets supported AI agents reuse a user's approved goals, priorities, decisions, and judgment criteria.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -423,5 +423,5 @@
423
423
  "engines": {
424
424
  "node": ">=20"
425
425
  },
426
- "gitHead": "d475d6282eda01482255c0a234999e194560d3c8"
426
+ "gitHead": "14fca779f1164269b86adef697693647db57df9a"
427
427
  }
@@ -0,0 +1,31 @@
1
+ /** Compact notices are projections of the same source-read states as the full rail.
2
+ * A task's needs_check is a record gap, never an unavailable read or failed job.
3
+ */
4
+ import { readReasonText, shortTime } from './world-work-rail.js';
5
+
6
+ const READ_LABELS = Object.freeze({ tasks: '仕事', persons: '人物', decisions: '方針の決定', relations: '関係' });
7
+ const STATE_TEXT = Object.freeze({ partial: '一部だけ読めた', failed: '読込失敗', forbidden: '権限がなく読めない', not_connected: '未接続', unavailable: '読めない' });
8
+
9
+ export function workCanvasNotes(work) {
10
+ if (!work) return [];
11
+ if (work.status !== 'ok') {
12
+ const state = STATE_TEXT[work.status] ?? '読めない';
13
+ return [{ label: '区画の仕事', tone: 'warning', summary: `仕事:${state}(0件ではありません)`, text: `仕事は${state}。${readReasonText(work.reason) ?? '理由不明'}。仕事が0件という意味ではありません。` }];
14
+ }
15
+ const notes = [];
16
+ for (const [key, label] of Object.entries(READ_LABELS)) {
17
+ const read = work.reads?.[key];
18
+ if (!read || read.state !== 'complete') {
19
+ const state = STATE_TEXT[read?.state] ?? '状態不明';
20
+ notes.push({ label: `区画の${label}`, tone: 'warning', summary: `${label}:${state}`, text: `${label}は${state}。${readReasonText(read?.reason) ?? '理由不明'}。読めていない記録は0件とは限りません。` });
21
+ }
22
+ }
23
+ const summary = work.summary;
24
+ if (summary?.state === 'complete' || summary?.state === 'partial') {
25
+ const needs = summary.needs_check?.length;
26
+ if (needs > 0) notes.push({ label: '記録の確認', tone: 'attention', summary: `記録の要確認 ${needs}件`, text: `読めた未完了の仕事のうち${needs}件に、記録から把握できていないことがあります。仕事が止まっているという意味ではありません。詳細の絞り込みから根拠を確認できます。` });
27
+ }
28
+ const at = work.reads?.tasks?.read_at;
29
+ notes.push({ label: '区画の時点', tone: 'info', text: `仕事の読み込み:${shortTime(at) ?? '時点不明'}。表示は読み込んだ時点の記録です。` });
30
+ return notes;
31
+ }
@@ -0,0 +1,352 @@
1
+ /* Only the opt-in canvas presentation and overlay subtree are styled here. */
2
+ .bb-world.bb-world--canvas {
3
+ width: 100%;
4
+ height: 100%;
5
+ min-height: 0;
6
+ gap: 0;
7
+ }
8
+ .bb-world.bb-world--canvas > .bb-world-stage {
9
+ flex: 1 1 auto;
10
+ width: 100%;
11
+ height: 100%;
12
+ min-height: 0;
13
+ border: 0;
14
+ border-radius: 0;
15
+ box-shadow: none;
16
+ }
17
+ .bb-world.bb-world--canvas .bb-world-fallback {
18
+ position: absolute;
19
+ inset: 76px 24px 52px 16px;
20
+ overflow: auto;
21
+ overscroll-behavior: contain;
22
+ padding: 12px 16px;
23
+ background: rgb(255 255 255 / 88%);
24
+ border-radius: 8px;
25
+ }
26
+
27
+ .bb-world-canvas-ui {
28
+ --bb-world-panel-width: min(360px, calc(100% - 24px));
29
+ --bb-world-right-inset: 16px;
30
+ --bb-world-toolbar-top: 16px;
31
+ --bb-world-panel-top: 76px;
32
+ position: absolute;
33
+ inset: 0;
34
+ z-index: 6;
35
+ pointer-events: none;
36
+ color: #0d192e;
37
+ font-family: var(--bb-font-sans, sans-serif);
38
+ font-size: 12px;
39
+ line-height: 1.65;
40
+ }
41
+
42
+ .bb-world-canvas-ui *,
43
+ .bb-world-canvas-ui *::before,
44
+ .bb-world-canvas-ui *::after { box-sizing: border-box; }
45
+ .bb-world-canvas-ui [hidden] { display: none !important; }
46
+ .bb-world-canvas-ui button,
47
+ .bb-world-canvas-ui select { font-family: inherit; }
48
+ .bb-world-canvas-ui button { cursor: pointer; }
49
+ .bb-world-canvas-ui :focus-visible { outline: 2px solid #0d192e; outline-offset: 3px; }
50
+
51
+ .bb-world-canvas-ui .bb-world-canvas-toolbar {
52
+ position: absolute;
53
+ top: var(--bb-world-toolbar-top);
54
+ right: 0;
55
+ width: var(--bb-world-panel-width);
56
+ padding-right: var(--bb-world-right-inset);
57
+ display: grid;
58
+ grid-template-columns: auto minmax(0, 1fr) 40px;
59
+ gap: 8px;
60
+ align-items: center;
61
+ pointer-events: auto;
62
+ }
63
+ .bb-world-canvas-ui .bb-world-canvas-control {
64
+ min-width: 0;
65
+ height: 40px;
66
+ padding: 8px 10px;
67
+ border: 1px solid rgb(13 25 46 / 12%);
68
+ border-radius: 8px;
69
+ background: rgb(255 255 255 / 94%);
70
+ color: #0d192e;
71
+ font-size: 12px;
72
+ line-height: 20px;
73
+ box-shadow: 0 2px 8px #0d192e0a;
74
+ }
75
+ .bb-world-canvas-ui .bb-world-canvas-control:hover { background: #e5f8e1; }
76
+ .bb-world-canvas-ui .bb-world-canvas-reset { white-space: nowrap; }
77
+ .bb-world-canvas-ui .bb-world-canvas-city { width: 100%; text-overflow: ellipsis; }
78
+ .bb-world-canvas-ui .bb-world-canvas-city:disabled { cursor: default; opacity: .65; }
79
+ .bb-world-canvas-ui .bb-world-canvas-sky { display: grid; place-items: center; padding: 0; }
80
+ .bb-world-canvas-ui .bb-world-canvas-sky-icon { font-size: 22px; line-height: 1; }
81
+ .bb-world-canvas-ui .bb-world-canvas-sky-menu {
82
+ position: absolute;
83
+ top: calc(var(--bb-world-toolbar-top) + 48px);
84
+ right: var(--bb-world-right-inset);
85
+ min-width: 150px;
86
+ padding: 5px;
87
+ border: 1px solid rgb(13 25 46 / 18%);
88
+ border-radius: 9px;
89
+ background: #fff;
90
+ box-shadow: 0 8px 25px #0d192e22;
91
+ pointer-events: auto;
92
+ }
93
+ .bb-world-canvas-ui .bb-world-canvas-sky-option {
94
+ display: block;
95
+ width: 100%;
96
+ padding: 8px 10px;
97
+ border: 0;
98
+ border-radius: 5px;
99
+ background: transparent;
100
+ color: inherit;
101
+ font-size: 12px;
102
+ text-align: left;
103
+ }
104
+ .bb-world-canvas-ui .bb-world-canvas-sky-option:hover,
105
+ .bb-world-canvas-ui .bb-world-canvas-sky-option[aria-checked="true"] { background: #e5f8e1; }
106
+
107
+ .bb-world-canvas-ui .bb-world-canvas-quests {
108
+ position: absolute;
109
+ top: 16px;
110
+ left: 16px;
111
+ display: grid;
112
+ justify-items: start;
113
+ gap: 6px;
114
+ max-width: max(0px, calc(100% - 400px));
115
+ pointer-events: none;
116
+ }
117
+ .bb-world-canvas-ui .bb-world-canvas-quest {
118
+ display: flex;
119
+ align-items: center;
120
+ gap: 8px;
121
+ max-width: 100%;
122
+ min-width: 0;
123
+ margin: 0;
124
+ padding: 7px 10px 7px 8px;
125
+ border: 1px solid rgb(255 255 255 / 18%);
126
+ border-left: 3px solid #9be891;
127
+ border-radius: 4px;
128
+ background: linear-gradient(110deg, rgb(13 25 46 / 88%), rgb(13 25 46 / 72%));
129
+ color: #fff;
130
+ font-size: 12px;
131
+ line-height: 18px;
132
+ box-shadow: 0 3px 12px #0d192e18;
133
+ backdrop-filter: blur(5px);
134
+ }
135
+ .bb-world-canvas-ui .bb-world-canvas-quest-symbol {
136
+ display: grid;
137
+ place-items: center;
138
+ flex: none;
139
+ width: 18px;
140
+ height: 18px;
141
+ border: 1px solid #9be89170;
142
+ border-radius: 3px;
143
+ background: #9be89112;
144
+ color: #b5f2ab;
145
+ font-size: 11px;
146
+ font-weight: 700;
147
+ }
148
+ .bb-world-canvas-ui .bb-world-canvas-quest-text { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
149
+
150
+ .bb-world-canvas-ui .bb-world-canvas-edge {
151
+ position: absolute;
152
+ top: calc(var(--bb-world-panel-top) + 8px);
153
+ right: 0;
154
+ bottom: 24px;
155
+ width: 20px;
156
+ padding: 0;
157
+ border: 0;
158
+ background: transparent;
159
+ pointer-events: auto;
160
+ }
161
+ .bb-world-canvas-ui .bb-world-canvas-edge::after {
162
+ content: '';
163
+ position: absolute;
164
+ right: 4px;
165
+ top: 50%;
166
+ width: 3px;
167
+ height: 38px;
168
+ border-radius: 4px;
169
+ background: #0d192e50;
170
+ }
171
+ .bb-world-canvas-ui .bb-world-canvas-edge:focus-visible { outline-offset: -2px; background: #ffffff70; }
172
+ .bb-world-canvas-ui .bb-world-canvas-drawer,
173
+ .bb-world-canvas-ui .bb-world-canvas-help,
174
+ .bb-world-canvas-ui .bb-world-canvas-task {
175
+ position: absolute;
176
+ display: flex;
177
+ flex-direction: column;
178
+ min-height: 0;
179
+ border: 1px solid rgb(13 25 46 / 16%);
180
+ border-radius: 12px;
181
+ background: rgb(255 255 255 / 98%);
182
+ box-shadow: 0 8px 35px #0d192e22;
183
+ pointer-events: none;
184
+ visibility: hidden;
185
+ }
186
+ .bb-world-canvas-ui .bb-world-canvas-drawer {
187
+ top: var(--bb-world-panel-top);
188
+ right: var(--bb-world-right-inset);
189
+ bottom: 14px;
190
+ width: calc(var(--bb-world-panel-width) - var(--bb-world-right-inset));
191
+ overflow: hidden;
192
+ transform: translateX(calc(100% + 20px));
193
+ transition: transform .24s ease, visibility .24s;
194
+ }
195
+ .bb-world-canvas-ui .bb-world-canvas-drawer.is-open { transform: translateX(0); }
196
+ .bb-world-canvas-ui .bb-world-canvas-panel-top {
197
+ display: flex;
198
+ flex: none;
199
+ align-items: center;
200
+ gap: 8px;
201
+ padding: 8px 12px;
202
+ border-bottom: 1px solid rgb(13 25 46 / 12%);
203
+ }
204
+ .bb-world-canvas-ui .bb-world-canvas-panel-top h2 { margin: 0; font-size: 14px; font-weight: 600; }
205
+ .bb-world-canvas-ui .bb-world-canvas-close {
206
+ display: grid;
207
+ place-items: center;
208
+ flex: none;
209
+ width: 32px;
210
+ height: 32px;
211
+ margin-left: auto;
212
+ padding: 0;
213
+ border: 0;
214
+ border-radius: 7px;
215
+ background: transparent;
216
+ color: inherit;
217
+ font-size: 22px;
218
+ line-height: 1;
219
+ }
220
+ .bb-world-canvas-ui .bb-world-canvas-close:hover { background: #e5f8e1; }
221
+ .bb-world-canvas-ui .bb-world-canvas-rail-body,
222
+ .bb-world-canvas-ui .bb-world-canvas-task-body,
223
+ .bb-world-canvas-ui .bb-world-canvas-help-body {
224
+ min-height: 0;
225
+ overflow: auto;
226
+ overscroll-behavior: contain;
227
+ overflow-wrap: anywhere;
228
+ scrollbar-width: thin;
229
+ padding: 14px 18px;
230
+ }
231
+ .bb-world-canvas-ui .bb-world-canvas-rail-body h2 { font-size: 18px; }
232
+ .bb-world-canvas-ui .bb-world-canvas-rail-body h3 { font-size: 13px; }
233
+ .bb-world-canvas-ui .bb-world-canvas-rail-body .bb-ws-rail-block { padding: 14px 0; }
234
+ .bb-world-canvas-ui .bb-world-canvas-rail-body .bb-ws-definition,
235
+ .bb-world-canvas-ui .bb-world-canvas-rail-body .bb-world-rail-note,
236
+ .bb-world-canvas-ui .bb-world-canvas-rail-body p,
237
+ .bb-world-canvas-ui .bb-world-canvas-rail-body button { font-size: 12px; }
238
+
239
+ .bb-world-canvas-ui .bb-world-canvas-task { width: 330px; background: #fffdf9; border-radius: 10px; }
240
+ .bb-world-canvas-ui .bb-world-canvas-task::after {
241
+ content: '';
242
+ position: absolute;
243
+ left: var(--bb-world-task-pointer-x, 50%);
244
+ bottom: -7px;
245
+ width: 12px;
246
+ height: 12px;
247
+ background: #fffdf9;
248
+ border-right: 1px solid rgb(13 25 46 / 16%);
249
+ border-bottom: 1px solid rgb(13 25 46 / 16%);
250
+ transform: translateX(-50%) rotate(45deg);
251
+ pointer-events: none;
252
+ }
253
+ .bb-world-canvas-ui .bb-world-canvas-task.is-below::after { top: -7px; bottom: auto; transform: translateX(-50%) rotate(225deg); }
254
+ .bb-world-canvas-ui .bb-world-canvas-task .bb-world-canvas-panel-top { padding: 3px 8px; border: 0; }
255
+ .bb-world-canvas-ui .bb-world-canvas-task .bb-world-canvas-close { width: 28px; height: 28px; font-size: 20px; }
256
+ .bb-world-canvas-ui .bb-world-canvas-task-body { padding: 0 16px 16px; }
257
+ .bb-world-canvas-ui .bb-world-canvas-task-body h2 { font-size: 15px; line-height: 1.5; }
258
+ .bb-world-canvas-ui .bb-world-canvas-task-body h3,
259
+ .bb-world-canvas-ui .bb-world-canvas-task-body p,
260
+ .bb-world-canvas-ui .bb-world-canvas-task-body .bb-ws-definition,
261
+ .bb-world-canvas-ui .bb-world-canvas-task-body .bb-ws-rail-block { font-size: 12px; line-height: 1.65; }
262
+ .bb-world-canvas-ui .bb-world-canvas-task-body .bb-ws-rail-block { padding: 12px 0; }
263
+ .bb-world-canvas-ui .bb-world-canvas-task-body .bb-ws-kicker { font-size: 10px; }
264
+
265
+ .bb-world-canvas-ui .bb-world-canvas-help-button {
266
+ position: absolute;
267
+ bottom: 14px;
268
+ left: 14px;
269
+ display: grid;
270
+ place-items: center;
271
+ width: 28px;
272
+ height: 28px;
273
+ padding: 0;
274
+ border: 1px solid #0d192e20;
275
+ border-radius: 50%;
276
+ background: #ffffffb8;
277
+ color: #0d192eb3;
278
+ font-size: 14px;
279
+ pointer-events: auto;
280
+ }
281
+ .bb-world-canvas-ui .bb-world-canvas-help-button:hover { color: #0d192e; background: #fff; }
282
+ .bb-world-canvas-ui .bb-world-canvas-help {
283
+ left: 14px;
284
+ bottom: 54px;
285
+ width: min(340px, calc(100% - 28px));
286
+ max-height: calc(100% - 138px);
287
+ overflow: hidden;
288
+ opacity: 0;
289
+ transform: translateY(24px);
290
+ transition: transform .24s ease, opacity .24s ease, visibility .24s;
291
+ }
292
+ .bb-world-canvas-ui .bb-world-canvas-help.is-open { opacity: 1; transform: translateY(0); }
293
+ .bb-world-canvas-ui .bb-world-canvas-help-body,
294
+ .bb-world-canvas-ui .bb-world-canvas-help-body h3,
295
+ .bb-world-canvas-ui .bb-world-canvas-help-body p { font-size: 12px; line-height: 1.7; }
296
+ .bb-world-canvas-ui .bb-world-canvas-help-body h3 { margin: 14px 0 6px; }
297
+ .bb-world-canvas-ui .bb-world-canvas-help-body p { margin: 0 0 10px; }
298
+ .bb-world-canvas-ui .bb-world-canvas-note { margin-top: 14px; padding-top: 4px; border-top: 1px solid rgb(13 25 46 / 12%); }
299
+ .bb-world-canvas-ui .bb-world-canvas-drawer.is-open,
300
+ .bb-world-canvas-ui .bb-world-canvas-help.is-open,
301
+ .bb-world-canvas-ui .bb-world-canvas-task.is-open { visibility: visible; pointer-events: auto; }
302
+
303
+ @media (max-width: 900px) {
304
+ .bb-world-canvas-ui .bb-world-canvas-quests { top: 70px; left: 12px; max-width: calc(100% - 40px); }
305
+ }
306
+ @media (max-width: 460px) {
307
+ .bb-world-canvas-ui {
308
+ --bb-world-right-inset: 10px;
309
+ --bb-world-toolbar-top: 12px;
310
+ --bb-world-panel-top: 72px;
311
+ }
312
+ .bb-world-canvas-ui .bb-world-canvas-toolbar { grid-template-columns: auto minmax(0, 1fr) 34px; }
313
+ .bb-world-canvas-ui .bb-world-canvas-control { height: 36px; padding: 6px; font-size: 10px; }
314
+ .bb-world-canvas-ui .bb-world-canvas-sky { padding: 0; }
315
+ .bb-world-canvas-ui .bb-world-canvas-sky-icon { font-size: 20px; }
316
+ .bb-world-canvas-ui .bb-world-canvas-quest { font-size: 10px; gap: 6px; padding: 6px 8px; }
317
+ .bb-world-canvas-ui .bb-world-canvas-help-button { left: 12px; bottom: 12px; }
318
+ .bb-world-canvas-ui .bb-world-canvas-help { left: 12px; bottom: 52px; width: calc(100% - 24px); }
319
+ }
320
+ @media (hover: none) {
321
+ .bb-world-canvas-ui .bb-world-canvas-edge { width: 28px; }
322
+ .bb-world-canvas-ui .bb-world-canvas-edge::after { width: 4px; background: #0d192e70; }
323
+ .bb-world-canvas-ui .bb-world-canvas-help-button { width: 36px; height: 36px; }
324
+ .bb-world-canvas-ui .bb-world-canvas-close { width: 36px; height: 36px; }
325
+ }
326
+ .bb-world-canvas-ui.is-reduced-motion .bb-world-canvas-help,
327
+ .bb-world-canvas-ui.is-reduced-motion .bb-world-canvas-drawer { transition: none; }
328
+ @media (prefers-reduced-motion: reduce) {
329
+ .bb-world-canvas-ui .bb-world-canvas-help,
330
+ .bb-world-canvas-ui .bb-world-canvas-drawer { transition: none; }
331
+ }
332
+
333
+ /* The same real legend nodes also serve the standard presentation. */
334
+ .bb-world-canvas-ui .bb-world-canvas-help-content .bb-world-legend,
335
+ .bb-world-canvas-ui .bb-world-canvas-help-content .bb-world-district-legend {
336
+ position: static;
337
+ inset: auto;
338
+ display: flex;
339
+ flex-wrap: wrap;
340
+ gap: 6px;
341
+ margin: 10px 0;
342
+ font-size: 12px;
343
+ }
344
+ .bb-world-canvas-ui .bb-world-canvas-help-content .bb-world-district-notice {
345
+ position: static;
346
+ inset: auto;
347
+ margin: 10px 0;
348
+ font-size: 12px;
349
+ }
350
+
351
+ /* A real record without a visible lot remains readable as detached details. */
352
+ .bb-world-canvas-ui .bb-world-canvas-task.is-unanchored::after { display: none; }