create-pmos 0.151.0 → 0.152.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.
@@ -0,0 +1,395 @@
1
+ /* pm/c3-md.js — markdown for the console3 document sheet (initiative c3-doc-sheet-rich): read it as rich text, edit it as rich
2
+ * text, and show a change as a rendered diff.
3
+ *
4
+ * parseMd(md) → blocks the subset PMOS documents use: headings, paragraphs, quotes, bulleted, numbered and
5
+ * checklist lists, tables, dividers and fenced code; inline bold, italic, code and links
6
+ * toMd(blocks) → md the canonical markdown of blocks; toMd(parseMd(toMd(b))) === toMd(b)
7
+ * canonMd(md) → md toMd(parseMd(md)): two texts that read the same compare equal
8
+ * diffMd(before, after) → md `after`, with the words removed and added since `before` wrapped in markers that
9
+ * toDom draws as <del> and <ins>; a line or sentence rewritten in most of its words is
10
+ * shown whole, removed then added
11
+ * toDom(blocks, doc?) → Node[] the blocks as elements (no innerHTML: every text is a text node); links are http(s),
12
+ * mailto or relative only
13
+ * fromDom(root) → blocks what the rich-text editor holds, read back as blocks
14
+ *
15
+ * parseMd, toMd, canonMd and diffMd are pure (Node-testable: c3-md.test.js); toDom and fromDom need a document.
16
+ */
17
+
18
+ export const INS = '\uE000';
19
+ export const INS_END = '\uE001';
20
+ export const DEL = '\uE002';
21
+ export const DEL_END = '\uE003';
22
+ const MARK = /[\uE000-\uE003]/g;
23
+
24
+ /* ── inline ── */
25
+
26
+ /** Inline markdown → [{ t:'text', s } | { t:'b'|'i', c:[…] } | { t:'code', s } | { t:'a', href, c:[…] }]. */
27
+ export function parseInline(src) {
28
+ const s = String(src == null ? '' : src);
29
+ const out = [];
30
+ let buf = '';
31
+ const flush = () => { if (buf) { out.push({ t: 'text', s: buf }); buf = ''; } };
32
+ let i = 0;
33
+ while (i < s.length) {
34
+ const c = s[i];
35
+ if (c === '\\' && i + 1 < s.length && /[\\`*_[\]()#|>-]/.test(s[i + 1])) { buf += s[i + 1]; i += 2; continue; }
36
+ if (c === '`') {
37
+ const j = s.indexOf('`', i + 1);
38
+ if (j > i) { flush(); out.push({ t: 'code', s: s.slice(i + 1, j) }); i = j + 1; continue; }
39
+ }
40
+ if (c === '[') {
41
+ const m = /^\[((?:[^\]\\]|\\.)+)\]\(([^)\s]+)\)/.exec(s.slice(i));
42
+ if (m) { flush(); out.push({ t: 'a', href: m[2], c: parseInline(m[1]) }); i += m[0].length; continue; }
43
+ }
44
+ if ((c === '*' || c === '_') && s[i + 1] === c) {
45
+ const j = s.indexOf(c + c, i + 2);
46
+ if (j > i + 2) { flush(); out.push({ t: 'b', c: parseInline(s.slice(i + 2, j)) }); i = j + 2; continue; }
47
+ }
48
+ if ((c === '*' || c === '_') && s[i + 1] !== c && s[i + 1] !== ' ' && (c === '*' || !/\w/.test(s[i - 1] || ''))) {
49
+ let j = i + 1;
50
+ while (j < s.length && !(s[j] === c && s[j + 1] !== c && s[j - 1] !== ' ' && s[j - 1] !== c)) j++;
51
+ if (j < s.length && j > i + 1 && (c === '*' || !/\w/.test(s[j + 1] || ''))) { flush(); out.push({ t: 'i', c: parseInline(s.slice(i + 1, j)) }); i = j + 1; continue; }
52
+ }
53
+ buf += c; i++;
54
+ }
55
+ flush();
56
+ return out;
57
+ }
58
+
59
+ // Text is escaped only where markdown would read it as syntax: a backslash before punctuation, `, *, [ and ], and an _ that
60
+ // is not inside a word (snake_case stays as written).
61
+ const escText = (s) => String(s).replace(/\\(?=[\\`*_[\]()#|>-])/g, '\\\\').replace(/[`*[\]]/g, '\\$&').replace(/(^|\W)_|_(?=\W|$)/g, (m) => m.replace('_', '\\_'));
62
+ /** Inline nodes → markdown. `raw`: text as it is, unescaped (for a plain-text label). */
63
+ export function inlineMd(nodes, raw) {
64
+ return (nodes || []).map((n) => {
65
+ if (n.t === 'text') return raw ? n.s : escText(n.s);
66
+ if (n.t === 'code') return raw ? n.s : '`' + n.s + '`';
67
+ const inner = inlineMd(n.c, raw);
68
+ if (raw || !inner.trim()) return inner;
69
+ const lead = inner.match(/^\s*/)[0]; const tail = inner.match(/\s*$/)[0]; const core = inner.trim();
70
+ if (n.t === 'b') return lead + '**' + core + '**' + tail;
71
+ if (n.t === 'i') return lead + '*' + core + '*' + tail;
72
+ if (n.t === 'a') return lead + '[' + core + '](' + n.href + ')' + tail;
73
+ return inner;
74
+ }).join('');
75
+ }
76
+ // a paragraph or list item whose text would start a heading, quote, list or rule is escaped at its start
77
+ const lineSafe = (s) => s.replace(/^(#{1,6}\s|>|[-+]\s|\d+[.)]\s|-{3,}\s*$)/, '\\$1');
78
+
79
+ /* ── blocks ── */
80
+
81
+ const LIST = /^(\s*)([-*+]|\d+[.)])\s+/;
82
+ const TASK = /^\[( |x|X)\]\s+/;
83
+ const cells = (r) => {
84
+ const t = r.trim().replace(/^\|/, '').replace(/\|\s*$/, '');
85
+ const out = []; let cur = ''; for (let i = 0; i < t.length; i++) { if (t[i] === '\\' && t[i + 1] === '|') { cur += '|'; i++; } else if (t[i] === '|') { out.push(cur.trim()); cur = ''; } else cur += t[i]; }
86
+ out.push(cur.trim()); return out;
87
+ };
88
+ const isRule = (l) => /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/.test(l);
89
+ const startsBlock = (l) => /^(#{1,6}\s|>|```|~~~|\s*(-{3,}|\*{3,}|_{3,})\s*$)/.test(l) || LIST.test(l) || /^\s*\|/.test(l);
90
+
91
+ /** Markdown → blocks: { t:'h', level, c } · { t:'p', c } · { t:'quote', blocks } · { t:'list', ordered, items:[{ task, c }] } ·
92
+ * { t:'table', head:[c], rows:[[c]] } · { t:'hr' } · { t:'code', lang, s }. Marker characters (diffMd) pass through as text. */
93
+ export function parseMd(md) {
94
+ const L = String(md == null ? '' : md).replace(/\r\n?/g, '\n').split('\n');
95
+ const out = []; let i = 0;
96
+ const bare = (l) => l.replace(MARK, '');
97
+ while (i < L.length) {
98
+ const l = L[i]; const b = bare(l); let m;
99
+ if (!b.trim()) { i++; continue; }
100
+ if ((m = /^(```|~~~)\s*([\w-]*)/.exec(b))) {
101
+ const fence = m[1]; const body = []; i++;
102
+ while (i < L.length && !bare(L[i]).startsWith(fence)) body.push(L[i++]);
103
+ i++; out.push({ t: 'code', lang: m[2] || '', s: body.join('\n') }); continue;
104
+ }
105
+ if ((m = /^(#{1,6})\s+(.*?)\s*#*\s*$/.exec(l))) { out.push({ t: 'h', level: m[1].length, c: parseInline(m[2]) }); i++; continue; }
106
+ if (/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(b)) { out.push({ t: 'hr' }); i++; continue; }
107
+ if (/^\s*>/.test(b)) {
108
+ const body = [];
109
+ while (i < L.length && /^\s*>/.test(bare(L[i]))) body.push(L[i++].replace(/^(\s*[\uE000-\uE003]*)\s*>\s?/, '$1'));
110
+ out.push({ t: 'quote', blocks: parseMd(body.join('\n')) }); continue;
111
+ }
112
+ if (/^\s*\|/.test(b) && i + 1 < L.length && isRule(bare(L[i + 1]))) {
113
+ const head = cells(l); i += 2; const rows = [];
114
+ while (i < L.length && /^\s*\|/.test(bare(L[i]))) rows.push(cells(L[i++]));
115
+ out.push({ t: 'table', head: head.map(parseInline), rows: rows.map((r) => head.map((_, k) => parseInline(r[k] || ''))) }); continue;
116
+ }
117
+ if (LIST.test(b)) {
118
+ const ordered = /^\s*\d/.test(b); const items = [];
119
+ while (i < L.length && LIST.test(bare(L[i])) && /^\s*\d/.test(bare(L[i])) === ordered) {
120
+ const lead = (/^[\uE000-\uE003]*/.exec(L[i]) || [''])[0];
121
+ let t = lead + L[i].slice(lead.length).replace(LIST, ''); i++;
122
+ while (i < L.length && /^\s{2,}\S/.test(L[i]) && !LIST.test(bare(L[i]))) t += ' ' + L[i++].trim();
123
+ const tk = TASK.exec(t.replace(MARK, ''));
124
+ if (tk) { const k = t.indexOf('['); t = t.slice(0, k) + t.slice(k).replace(TASK, ''); }
125
+ items.push({ task: tk ? tk[1] !== ' ' : null, c: parseInline(t) });
126
+ }
127
+ out.push({ t: 'list', ordered, items }); continue;
128
+ }
129
+ const body = [];
130
+ while (i < L.length && bare(L[i]).trim() && (!body.length || !startsBlock(bare(L[i])))) body.push(L[i++].trim());
131
+ out.push({ t: 'p', c: parseInline(body.join(' ')) });
132
+ }
133
+ return out;
134
+ }
135
+
136
+ /** Blocks → markdown, one blank line between blocks. */
137
+ export function toMd(blocks) {
138
+ return (blocks || []).map((b) => {
139
+ if (b.t === 'h') return '#'.repeat(b.level || 2) + ' ' + inlineMd(b.c).trim();
140
+ if (b.t === 'p') return lineSafe(inlineMd(b.c).trim());
141
+ if (b.t === 'hr') return '---';
142
+ if (b.t === 'code') return '```' + (b.lang || '') + '\n' + b.s + '\n```';
143
+ if (b.t === 'quote') return toMd(b.blocks).split('\n').map((x) => (x ? '> ' + x : '>')).join('\n');
144
+ if (b.t === 'list') return b.items.map((it, k) => (b.ordered ? (k + 1) + '. ' : '- ') + (it.task == null ? '' : it.task ? '[x] ' : '[ ] ') + inlineMd(it.c).trim()).join('\n');
145
+ if (b.t === 'table') {
146
+ const cell = (c) => inlineMd(c).trim().replace(/\|/g, '\\|');
147
+ const w = Math.max(b.head.length, ...b.rows.map((r) => r.length));
148
+ const line = (r) => '| ' + Array.from({ length: w }, (_, k) => (r[k] ? cell(r[k]) : '')).join(' | ') + ' |';
149
+ return [line(b.head), '|' + Array.from({ length: w }, () => '---').join('|') + '|', ...b.rows.map(line)].join('\n');
150
+ }
151
+ return '';
152
+ }).filter((x) => x !== '').join('\n\n');
153
+ }
154
+
155
+ export const canonMd = (md) => toMd(parseMd(md));
156
+
157
+ /* ── the rendered diff ── */
158
+
159
+ function lcs(a, b) {
160
+ const n = a.length, m = b.length;
161
+ if (n * m > 400000) return [...a.map((x) => ['-', x]), ...b.map((x) => ['+', x])];
162
+ const T = Array.from({ length: n + 1 }, () => new Int32Array(m + 1));
163
+ for (let i = n - 1; i >= 0; i--) for (let j = m - 1; j >= 0; j--) T[i][j] = a[i] === b[j] ? T[i + 1][j + 1] + 1 : Math.max(T[i + 1][j], T[i][j + 1]);
164
+ const ops = []; let i = 0, j = 0;
165
+ while (i < n && j < m) { if (a[i] === b[j]) { ops.push(['=', a[i]]); i++; j++; } else if (T[i + 1][j] >= T[i][j + 1]) ops.push(['-', a[i++]]); else ops.push(['+', b[j++]]); }
166
+ while (i < n) ops.push(['-', a[i++]]); while (j < m) ops.push(['+', b[j++]]);
167
+ return ops;
168
+ }
169
+ /** Runs of ops: [{ eq:true, items } | { eq:false, del:[…], add:[…] }]. */
170
+ function runs(ops) {
171
+ const out = [];
172
+ for (const [op, x] of ops) {
173
+ const last = out[out.length - 1];
174
+ if (op === '=') { if (last && last.eq) last.items.push(x); else out.push({ eq: true, items: [x] }); }
175
+ else { if (!last || last.eq) out.push({ eq: false, del: [], add: [] }); out[out.length - 1][op === '-' ? 'del' : 'add'].push(x); }
176
+ }
177
+ return out;
178
+ }
179
+ const wrap = (s, a, z) => (s && s.trim() ? a + s + z : s);
180
+ function words(a, b) {
181
+ const tok = (x) => x.match(/\s+|[^\s]+/g) || [];
182
+ const A = tok(a), B = tok(b); const ops = lcs(A, B);
183
+ const real = (x) => /\S/.test(x);
184
+ const same = ops.filter(([o, x]) => o === '=' && real(x)).length;
185
+ const tot = Math.max(A.filter(real).length, B.filter(real).length);
186
+ if (tot > 4 && same / tot < 0.6) return wrap(a, DEL, DEL_END) + ' ' + wrap(b, INS, INS_END);
187
+ return runs(ops).map((r) => (r.eq ? r.items.join('') : wrap(r.del.join(''), DEL, DEL_END) + (r.del.length && r.add.length ? ' ' : '') + wrap(r.add.join(''), INS, INS_END))).join('');
188
+ }
189
+ function sentences(a, b) {
190
+ const cut = (x) => x.split(/(?<=[.;!?])\s+/);
191
+ const A = cut(a), B = cut(b);
192
+ if (A.length < 2 && B.length < 2) return words(a, b);
193
+ return runs(lcs(A, B)).map((r) => {
194
+ if (r.eq) return r.items.join(' ');
195
+ const n = Math.min(r.del.length, r.add.length); const parts = [];
196
+ for (let k = 0; k < n; k++) parts.push(words(r.del[k], r.add[k]));
197
+ r.del.slice(n).forEach((x) => parts.push(wrap(x, DEL, DEL_END)));
198
+ r.add.slice(n).forEach((x) => parts.push(wrap(x, INS, INS_END)));
199
+ return parts.join(' ');
200
+ }).join(' ');
201
+ }
202
+ const PREFIX = /^(\s*(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s+)?|\s*>\s?|#{1,6}\s+)/;
203
+ function wrapLine(l, a, z) {
204
+ if (!l.trim() || isRule(l)) return l;
205
+ if (/^\s*\|/.test(l)) return '| ' + cells(l).map((c) => wrap(c, a, z)).join(' | ') + ' |';
206
+ if (/^(```|~~~)/.test(l) || /^\s*(-{3,}|\*{3,})\s*$/.test(l)) return l;
207
+ const p = (PREFIX.exec(l) || [''])[0];
208
+ return p + wrap(l.slice(p.length), a, z);
209
+ }
210
+ function lineDiff(a, b) {
211
+ const pa = (PREFIX.exec(a) || [''])[0], pb = (PREFIX.exec(b) || [''])[0];
212
+ if (/^\s*\|/.test(a) && /^\s*\|/.test(b) && !isRule(a) && !isRule(b)) {
213
+ const ca = cells(a), cb = cells(b);
214
+ return '| ' + cb.map((c, k) => (ca[k] === c ? c : sentences(ca[k] || '', c))).join(' | ') + ' |';
215
+ }
216
+ if (pa === pb) return pb + sentences(a.slice(pa.length), b.slice(pb.length));
217
+ return null;
218
+ }
219
+
220
+ /** `after` with the change since `before` marked. `before` null: the whole of `after` is new. */
221
+ export function diffMd(before, after) {
222
+ const B = String(after == null ? '' : after).split('\n');
223
+ if (before == null) return B.map((l) => wrapLine(l, INS, INS_END)).join('\n');
224
+ const A = String(before).split('\n');
225
+ const out = [];
226
+ for (const r of runs(lcs(A, B))) {
227
+ if (r.eq) { out.push(...r.items); continue; }
228
+ const n = Math.min(r.del.length, r.add.length);
229
+ const removed = []; const added = [];
230
+ for (let k = 0; k < n; k++) {
231
+ const d = lineDiff(r.del[k], r.add[k]);
232
+ if (d == null) { removed.push(wrapLine(r.del[k], DEL, DEL_END)); added.push(wrapLine(r.add[k], INS, INS_END)); } else added.push(d);
233
+ }
234
+ r.del.slice(n).forEach((l) => removed.push(wrapLine(l, DEL, DEL_END)));
235
+ r.add.slice(n).forEach((l) => added.push(wrapLine(l, INS, INS_END)));
236
+ // a removed table row or list item stays in its table or list: put it with the rows after it
237
+ out.push(...removed.filter((l) => l.trim()), ...added);
238
+ }
239
+ return out.join('\n');
240
+ }
241
+
242
+ /** Words added and removed between two texts: [added, removed]. */
243
+ export function wordCounts(before, after) {
244
+ const A = String(before || '').match(/\S+/g) || [], B = String(after || '').match(/\S+/g) || [];
245
+ let add = 0, del = 0;
246
+ for (const [o] of lcs(A, B)) { if (o === '+') add++; else if (o === '-') del++; }
247
+ return [add, del];
248
+ }
249
+
250
+ /* ── DOM ── */
251
+
252
+ /** A safe href: http(s), mailto, a fragment or a relative path; anything else (javascript:, data:) is dropped. */
253
+ export function safeHref(h) {
254
+ const s = String(h || '').trim();
255
+ if (/^(https?:|mailto:)/i.test(s)) return s;
256
+ if (/^[a-z][\w+.-]*:/i.test(s)) return null;
257
+ return s;
258
+ }
259
+
260
+ /** Text with diff markers → nodes: the marked runs inside <ins> and <del>. `st` carries an open marker across inline nodes. */
261
+ function textNodes(doc, s, st) {
262
+ const out = []; let buf = '';
263
+ const push = () => {
264
+ if (!buf) return;
265
+ const t = doc.createTextNode(buf); buf = '';
266
+ if (st.mode) { const w = doc.createElement(st.mode); w.appendChild(t); out.push(w); } else out.push(t);
267
+ };
268
+ for (const ch of String(s)) {
269
+ if (ch === INS || ch === DEL) { push(); st.mode = ch === INS ? 'ins' : 'del'; }
270
+ else if (ch === INS_END || ch === DEL_END) { push(); st.mode = null; }
271
+ else buf += ch;
272
+ }
273
+ push();
274
+ return out;
275
+ }
276
+ function inlineDom(doc, nodes, st) {
277
+ const out = [];
278
+ for (const n of nodes || []) {
279
+ if (n.t === 'text') { out.push(...textNodes(doc, n.s, st)); continue; }
280
+ if (n.t === 'code') { const c = doc.createElement('code'); c.append(...textNodes(doc, n.s, st)); out.push(c); continue; }
281
+ const tag = n.t === 'b' ? 'strong' : n.t === 'i' ? 'em' : 'a';
282
+ const e = doc.createElement(tag);
283
+ if (n.t === 'a') {
284
+ const h = safeHref(n.href);
285
+ if (h != null) { e.setAttribute('href', h); if (/^https?:/i.test(h)) { e.setAttribute('target', '_blank'); e.setAttribute('rel', 'noopener'); } }
286
+ }
287
+ e.append(...inlineDom(doc, n.c, st));
288
+ out.push(e);
289
+ }
290
+ return out;
291
+ }
292
+
293
+ /** Blocks → elements. `opts.editable` draws checklist boxes that can be ticked (the editor). */
294
+ export function toDom(blocks, doc, opts = {}) {
295
+ const d = doc || document;
296
+ const mk = (tag, cls) => { const e = d.createElement(tag); if (cls) e.className = cls; return e; };
297
+ return (blocks || []).map((b) => {
298
+ const st = { mode: null };
299
+ if (b.t === 'h') { const e = mk('h' + Math.min(6, Math.max(1, b.level || 2))); e.append(...inlineDom(d, b.c, st)); return e; }
300
+ if (b.t === 'p') { const e = mk('p'); e.append(...inlineDom(d, b.c, st)); return e; }
301
+ if (b.t === 'hr') return mk('hr');
302
+ if (b.t === 'code') { const p = mk('pre'); if (b.lang) p.setAttribute('data-lang', b.lang); const c = mk('code'); c.append(...textNodes(d, b.s, st)); p.appendChild(c); return p; }
303
+ if (b.t === 'quote') { const q = mk('blockquote'); q.append(...toDom(b.blocks, d, opts)); return q; }
304
+ if (b.t === 'list') {
305
+ const l = mk(b.ordered ? 'ol' : 'ul', b.items.some((it) => it.task != null) ? 'tasks' : '');
306
+ for (const it of b.items) {
307
+ const li = mk('li');
308
+ if (it.task != null) {
309
+ li.className = 'task' + (it.task ? ' done' : '');
310
+ const box = mk('input'); box.type = 'checkbox'; box.checked = !!it.task; box.setAttribute('aria-label', 'Done');
311
+ if (opts.editable) box.setAttribute('contenteditable', 'false'); else box.disabled = true;
312
+ const sp = mk('span'); sp.append(...inlineDom(d, it.c, st));
313
+ li.append(box, sp);
314
+ } else li.append(...inlineDom(d, it.c, st));
315
+ l.appendChild(li);
316
+ }
317
+ return l;
318
+ }
319
+ if (b.t === 'table') {
320
+ const w = mk('div', 'tw'); const t = mk('table'); const th = mk('thead'); const tr = mk('tr');
321
+ for (const c of b.head) { const x = mk('th'); x.append(...inlineDom(d, c, st)); tr.appendChild(x); }
322
+ th.appendChild(tr); const tb = mk('tbody');
323
+ for (const r of b.rows) { const row = mk('tr'); for (const c of r) { const x = mk('td'); x.append(...inlineDom(d, c, st)); row.appendChild(x); } tb.appendChild(row); }
324
+ t.append(th, tb); w.appendChild(t); return w;
325
+ }
326
+ return d.createTextNode('');
327
+ });
328
+ }
329
+
330
+ /* reading the editor back */
331
+ function inlineOf(n) {
332
+ const out = [];
333
+ const text = (s) => { if (!s) return; const last = out[out.length - 1]; if (last && last.t === 'text') last.s += s; else out.push({ t: 'text', s }); };
334
+ for (const c of Array.from(n.childNodes || [])) {
335
+ if (c.nodeType === 3) { text(c.textContent.replace(/\u00a0/g, ' ').replace(/\u200b/g, '').replace(/\s+/g, ' ')); continue; }
336
+ if (c.nodeType !== 1) continue;
337
+ const t = c.tagName;
338
+ if (t === 'BR') { text(' '); continue; }
339
+ if (t === 'INPUT' || t === 'UL' || t === 'OL') continue;
340
+ if (t === 'CODE') { out.push({ t: 'code', s: c.textContent.replace(/\u200b/g, '') }); continue; }
341
+ if (t === 'STRONG' || t === 'B') { out.push({ t: 'b', c: inlineOf(c) }); continue; }
342
+ if (t === 'EM' || t === 'I') { out.push({ t: 'i', c: inlineOf(c) }); continue; }
343
+ if (t === 'A') { out.push({ t: 'a', href: c.getAttribute('href') || '', c: inlineOf(c) }); continue; }
344
+ if (t === 'SPAN' && /font-weight:\s*(bold|[6-9]00)/.test(c.getAttribute('style') || '')) { out.push({ t: 'b', c: inlineOf(c) }); continue; }
345
+ if (t === 'SPAN' && /font-style:\s*italic/.test(c.getAttribute('style') || '')) { out.push({ t: 'i', c: inlineOf(c) }); continue; }
346
+ for (const x of inlineOf(c)) { if (x.t === 'text') text(x.s); else out.push(x); }
347
+ }
348
+ return out;
349
+ }
350
+ const trimInline = (c) => {
351
+ const a = c.slice();
352
+ while (a.length && a[0].t === 'text' && !a[0].s.trim()) a.shift();
353
+ while (a.length && a[a.length - 1].t === 'text' && !a[a.length - 1].s.trim()) a.pop();
354
+ if (a.length && a[0].t === 'text') a[0] = { ...a[0], s: a[0].s.replace(/^\s+/, '') };
355
+ if (a.length && a[a.length - 1].t === 'text') a[a.length - 1] = { ...a[a.length - 1], s: a[a.length - 1].s.replace(/\s+$/, '') };
356
+ return a;
357
+ };
358
+ const BLOCK = /^(P|DIV|H[1-6]|BLOCKQUOTE|UL|OL|TABLE|PRE|HR|SECTION)$/;
359
+ /** The editor's children → blocks. Loose text and inline nodes between blocks become a paragraph. */
360
+ export function fromDom(root) {
361
+ const out = []; let loose = null;
362
+ const endLoose = () => { if (loose) { const c = trimInline(inlineOf(loose)); if (c.length) out.push({ t: 'p', c }); loose = null; } };
363
+ for (const n of Array.from(root.childNodes || [])) {
364
+ if (n.nodeType === 1 && BLOCK.test(n.tagName)) { endLoose(); out.push(...blockOf(n)); continue; }
365
+ if (n.nodeType === 3 && !n.textContent.trim() && !loose) continue;
366
+ if (!loose) loose = (root.ownerDocument || document).createElement('p');
367
+ loose.appendChild(n.cloneNode(true));
368
+ }
369
+ endLoose();
370
+ return out;
371
+ }
372
+ function blockOf(n) {
373
+ const t = n.tagName;
374
+ if (/^H[1-6]$/.test(t)) return [{ t: 'h', level: +t[1], c: trimInline(inlineOf(n)) }];
375
+ if (t === 'HR') return [{ t: 'hr' }];
376
+ if (t === 'PRE') return [{ t: 'code', lang: n.getAttribute('data-lang') || '', s: n.textContent.replace(/\n$/, '') }];
377
+ if (t === 'BLOCKQUOTE') { const b = fromDom(n); return b.length ? [{ t: 'quote', blocks: b }] : []; }
378
+ if (t === 'UL' || t === 'OL') {
379
+ const items = Array.from(n.children).filter((li) => li.tagName === 'LI').map((li) => {
380
+ const box = li.querySelector(':scope > input[type=checkbox]');
381
+ return { task: box ? !!box.checked : null, c: trimInline(inlineOf(li)) };
382
+ });
383
+ const nested = Array.from(n.querySelectorAll(':scope > li > ul, :scope > li > ol')).flatMap(blockOf);
384
+ return [{ t: 'list', ordered: t === 'OL', items }, ...nested];
385
+ }
386
+ if (t === 'TABLE' || (t === 'DIV' && n.querySelector(':scope > table'))) {
387
+ const tb = t === 'TABLE' ? n : n.querySelector(':scope > table');
388
+ const rows = Array.from(tb.querySelectorAll('tr')).map((r) => Array.from(r.children).map((c) => trimInline(inlineOf(c))));
389
+ if (!rows.length) return [];
390
+ return [{ t: 'table', head: rows[0], rows: rows.slice(1) }];
391
+ }
392
+ if ((t === 'DIV' || t === 'SECTION') && Array.from(n.children).some((c) => BLOCK.test(c.tagName))) return fromDom(n);
393
+ const c = trimInline(inlineOf(n));
394
+ return c.length ? [{ t: 'p', c }] : [];
395
+ }
@@ -1907,6 +1907,171 @@
1907
1907
  .c3 .kp.k-foreground{--c:var(--foreground)}
1908
1908
  .c3 .kp.k-muted-foreground{--c:var(--muted-foreground)}
1909
1909
  .c3 .psess{display:inline-flex;gap:6px;align-items:center}
1910
+
1911
+ /* ── The document sheet (initiative c3-doc-sheet-rich): rendered markdown, the rich-text editor, proposals, the edit log.
1912
+ * Person = --acc, agent = --agent, current = --good. The layout and the look follow the approved mockup. */
1913
+ .c3 .ds{display:grid;gap:var(--sp-3);min-width:0}
1914
+ .c3 .ds-top{position:sticky;top:calc(-1 * var(--sp-4));z-index:4;background:var(--card);padding:var(--sp-3) 0 var(--sp-2);margin-top:calc(-1 * var(--sp-3));display:grid;gap:var(--sp-2);border-bottom:1px solid var(--border);min-width:0}
1915
+ .c3 .ds-top .seg{max-width:100%;overflow-x:auto;scrollbar-width:none}
1916
+ .c3 .ds-top .seg button{white-space:nowrap}
1917
+ .c3 .ds-top .seg .bdg.w{background:var(--warning);color:var(--background)}
1918
+ .c3 .ds-top .seg .bdg.p{background:var(--agent);color:var(--background)}
1919
+ .c3 .ds-meta{flex-wrap:wrap;gap:4px 12px}
1920
+ .c3 .ds-meta .chip2 b{color:var(--foreground);font-weight:500}
1921
+ .c3 .ds-good{color:var(--good)}
1922
+ .c3 .ds-warn{color:var(--warning)}
1923
+ .c3 .ds-bad{color:var(--destructive)}
1924
+ .c3 .ds-ag{color:var(--agent)}
1925
+ .c3 .ds-pe{color:var(--acc)}
1926
+ .c3 .ds-note{font-size:var(--fs-2);color:var(--muted-foreground);margin:0}
1927
+ .c3 .ds-mono{font:var(--fs-2) var(--mono);color:var(--muted-foreground)}
1928
+
1929
+ .c3 .ds-stale{border:1px solid color-mix(in oklab,var(--warning) 45%,transparent);background:color-mix(in oklab,var(--warning) 8%,transparent);border-radius:8px;padding:var(--sp-3);display:grid;gap:6px}
1930
+ .c3 .ds-stale .t{display:flex;gap:8px;align-items:center;font-weight:600;color:var(--warning)}
1931
+ .c3 .ds-stale p{margin:0;font-size:var(--fs-3)}
1932
+ .c3 .ds-stale ul{margin:0;padding-left:18px;font:var(--fs-2) var(--mono);color:var(--muted-foreground);overflow-wrap:anywhere}
1933
+ .c3 .ds-acts{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
1934
+ .c3 .ds-btn{border:1px solid var(--border);background:var(--background);color:var(--foreground);border-radius:6px;padding:5px 12px;font:500 var(--fs-2) var(--mono);display:inline-flex;gap:6px;align-items:center;min-height:30px}
1935
+ .c3 .ds-btn.pri{background:var(--foreground);color:var(--background);border-color:var(--foreground)}
1936
+ .c3 .ds-btn.ag{color:var(--agent);border-color:color-mix(in oklab,var(--agent) 45%,transparent)}
1937
+ .c3 .ds-btn:disabled{opacity:.45;cursor:not-allowed}
1938
+ @media (hover:hover){.c3 .ds-btn:hover{border-color:var(--ring)}.c3 .ds-by:hover{color:var(--foreground)}.c3 .ds-tb button:hover{background:var(--background);color:var(--foreground)}}
1939
+ @media (pointer:coarse){.c3 .ds-btn{min-height:44px}.c3 .ds-tb button{width:44px;height:44px}}
1940
+
1941
+ .c3 .ds-rbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center;justify-content:space-between}
1942
+ .c3 .ds-sel{font:var(--fs-2) var(--mono);color:var(--foreground);background:var(--background);border:1px solid var(--border);border-radius:6px;padding:4px 8px;max-width:100%}
1943
+ .c3 .ds-tg{display:inline-flex;gap:6px;align-items:center;font:var(--fs-2) var(--mono);color:var(--muted-foreground);cursor:pointer}
1944
+ .c3 .ds-tg input{accent-color:var(--acc)}
1945
+ .c3 .ds-legend{display:flex;gap:12px;flex-wrap:wrap;font:var(--fs-1) var(--mono);color:var(--muted-foreground)}
1946
+ .c3 .ds-legend i{display:inline-block;width:10px;height:10px;border-radius:6px;vertical-align:-1px;margin-right:4px}
1947
+ .c3 .ds-legend i.h{background:var(--acc)}
1948
+ .c3 .ds-legend i.a{background:var(--agent)}
1949
+
1950
+ .c3 .ds-props{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:4px 16px;font-size:var(--fs-2);padding:var(--sp-3);border:1px solid var(--border);border-radius:8px;background:color-mix(in oklab,var(--muted) 50%,transparent);margin:0}
1951
+ .c3 .ds-props dt{font-family:var(--mono);color:var(--muted-foreground)}
1952
+ .c3 .ds-props dd{margin:0;min-width:0;overflow-wrap:anywhere}
1953
+ .c3 .ds-props input{font:inherit;font-size:var(--fs-2);color:var(--foreground);background:var(--background);border:1px solid var(--border);border-radius:6px;padding:3px 6px;width:100%}
1954
+ .c3 .ds-props .sub{grid-column:1/-1;flex-wrap:wrap;font:var(--fs-1) var(--mono);color:var(--muted-foreground);display:flex;gap:6px;align-items:center}
1955
+
1956
+ .c3 .ds-md{font:var(--fs-4)/1.65 var(--sans);color:var(--foreground);max-width:72ch;min-width:0;overflow-wrap:anywhere}
1957
+ .c3 .ds-md>*:first-child{margin-top:0}
1958
+ .c3 .ds-md h1{font:600 var(--fs-5)/1.3 var(--sans);margin:4px 0 12px}
1959
+ .c3 .ds-md h2{font:600 var(--fs-5)/1.35 var(--sans);margin:0}
1960
+ .c3 .ds-md h3,.c3 .ds-md h4,.c3 .ds-md h5,.c3 .ds-md h6{font:600 var(--fs-4)/1.4 var(--sans);margin:16px 0 4px}
1961
+ .c3 .ds-md p{margin:8px 0}
1962
+ .c3 .ds-md a{color:var(--acc);text-decoration:underline;text-decoration-color:color-mix(in oklab,var(--acc) 40%,transparent);text-underline-offset:3px}
1963
+ .c3 .ds-md code{font:var(--fs-3) var(--mono);background:var(--muted);padding:1px 5px;border-radius:6px}
1964
+ .c3 .ds-md strong{font-weight:600}
1965
+ .c3 .ds-md blockquote{margin:10px 0;padding:10px 14px;background:var(--muted);border-radius:8px;font-size:var(--fs-3);line-height:1.6}
1966
+ .c3 .ds-md blockquote p{margin:6px 0}
1967
+ .c3 .ds-md ul,.c3 .ds-md ol{margin:8px 0;padding-left:22px}
1968
+ .c3 .ds-md li{margin:3px 0}
1969
+ .c3 .ds-md li.task{list-style:none;margin-left:-20px;display:flex;gap:8px;align-items:baseline}
1970
+ .c3 .ds-md li.task input{accent-color:var(--acc);margin:0;transform:translateY(2px)}
1971
+ .c3 .ds-md li.task.done>span{color:var(--muted-foreground)}
1972
+ .c3 .ds-md hr{border:0;border-top:1px solid var(--border);margin:16px 0}
1973
+ .c3 .ds-md .tw{overflow-x:auto;margin:10px 0;border:1px solid var(--border);border-radius:8px}
1974
+ .c3 .ds-md table{border-collapse:collapse;width:100%;font-size:var(--fs-3);font-variant-numeric:tabular-nums}
1975
+ .c3 .ds-md th{font:500 var(--fs-1) var(--mono);text-transform:uppercase;letter-spacing:.05em;color:var(--muted-foreground);text-align:left;background:var(--muted)}
1976
+ .c3 .ds-md th,.c3 .ds-md td{padding:7px 10px;border-bottom:1px solid var(--border);vertical-align:top}
1977
+ .c3 .ds-md tr:last-child td{border-bottom:0}
1978
+ .c3 .ds-md pre{font:var(--fs-3)/1.5 var(--mono);background:var(--muted);padding:10px 12px;border-radius:8px;overflow-x:auto;white-space:pre-wrap}
1979
+ .c3 .ds-md ins{background:color-mix(in oklab,var(--good) 20%,transparent);text-decoration:none;border-radius:6px;padding:0 1px}
1980
+ .c3 .ds-md del{background:color-mix(in oklab,var(--destructive) 14%,transparent);color:var(--muted-foreground);border-radius:6px;padding:0 1px}
1981
+
1982
+ .c3 .ds-sec{position:relative;padding:var(--sp-2) 0 var(--sp-2) 14px;border-left:3px solid transparent;margin-left:-17px;border-radius:0;scroll-margin-top:120px;min-width:0}
1983
+ .c3 .ds-sec+.ds-sec{margin-top:var(--sp-1)}
1984
+ .c3 .ds-sec-h{display:flex;justify-content:space-between;align-items:baseline;gap:4px 12px;flex-wrap:wrap}
1985
+ .c3 .ds-sec-h h2,.c3 .ds-sec-h h3{font:600 var(--fs-5)/1.35 var(--sans);margin:0}
1986
+ .c3 .ds-sec-h h3{font-size:var(--fs-4)}
1987
+ .c3 .ds-by{display:inline-flex;gap:5px;align-items:center;font:var(--fs-1) var(--mono);color:var(--muted-foreground);border:0;background:transparent;padding:2px 0;white-space:nowrap}
1988
+ .c3 .ds-by .ds-dot{width:16px;height:16px;border-radius:50%;display:inline-grid;place-items:center;font:600 var(--fs-1) var(--mono);color:var(--background);background:var(--acc)}
1989
+ .c3 .ds-by .ds-dot.a{background:var(--agent)}
1990
+ .c3 .ds-by .ds-dot .ic{width:10px;height:10px;stroke-width:2.4}
1991
+ .c3 .ds.who .ds-sec.h{border-left-color:var(--acc);background:color-mix(in oklab,var(--acc) 5%,transparent)}
1992
+ .c3 .ds.who .ds-sec.a{border-left-color:var(--agent);background:color-mix(in oklab,var(--agent) 6%,transparent)}
1993
+ .c3 .ds-sec.flash{animation:dsflash 1.6s ease-out}
1994
+ @keyframes dsflash{from{background:color-mix(in oklab,var(--acc) 18%,transparent)}to{background:transparent}}
1995
+ @media (prefers-reduced-motion:reduce){.c3 .ds-sec.flash{animation:none}}
1996
+ .c3 .ds-pin{display:flex;gap:8px;flex-wrap:wrap;align-items:center;justify-content:space-between;border:1px dashed color-mix(in oklab,var(--agent) 55%,transparent);border-radius:8px;padding:6px 10px;margin:6px 0;font:var(--fs-2) var(--mono);color:var(--agent);background:color-mix(in oklab,var(--agent) 5%,transparent)}
1997
+ .c3 .ds-pin>span{display:inline-flex;gap:6px;align-items:center}
1998
+ .c3 .ds-sec.prev .ds-body{border:1px solid color-mix(in oklab,var(--agent) 35%,transparent);border-radius:8px;padding:2px 12px}
1999
+
2000
+ .c3 .ds-tb{display:flex;gap:2px;align-items:center;flex-wrap:wrap;border:1px solid var(--border);background:var(--muted);border-radius:8px;padding:2px;max-width:100%}
2001
+ .c3 .ds-tb button{width:30px;height:30px;border-radius:6px;display:inline-grid;place-items:center;color:var(--muted-foreground);border:0;background:transparent;flex:none;padding:0}
2002
+ .c3 .ds-tb button[aria-pressed="true"]{background:var(--background);color:var(--foreground);box-shadow:0 0 0 1px var(--border)}
2003
+ .c3 .ds-tb select{font:var(--fs-2) var(--mono);background:var(--background);color:var(--foreground);border:1px solid var(--border);border-radius:6px;height:30px;padding:0 6px;flex:none}
2004
+ .c3 .ds-tb .sep{width:1px;height:20px;background:var(--border);margin:0 4px;flex:none}
2005
+ .c3 .ds-tb .sp{flex:1 0 8px}
2006
+ .c3 .ds-tb .seg{flex:none}
2007
+ .c3 .ds-tb .seg button{width:auto;padding:3px 8px;white-space:nowrap;display:flex}
2008
+ .c3 .ds-ed{outline:none;border:1px solid var(--border);border-radius:8px;padding:14px 18px 14px 26px;min-height:320px;background:var(--background);max-width:none}
2009
+ .c3 .ds-ed:focus{border-color:var(--ring);box-shadow:0 0 0 3px var(--accw)}
2010
+ .c3 .ds-ed h2{margin-top:22px;position:relative}
2011
+ .c3 .ds-ed h2:first-child{margin-top:4px}
2012
+ .c3 .ds-ed h2.chg::before{content:"";position:absolute;left:-17px;top:4px;bottom:4px;width:4px;border-radius:6px;background:var(--acc)}
2013
+ .c3 .ds-ed h2.chg::after{content:"Edited";font:500 var(--fs-1) var(--mono);color:var(--acc);margin-left:10px;vertical-align:middle}
2014
+ .c3 .ds-ed h2.clash::after{content:"Edited · also in a proposal";color:var(--warning)}
2015
+ .c3 .ds-src{width:100%;min-height:420px;font:var(--fs-3)/1.6 var(--mono);border:1px solid var(--border);border-radius:8px;padding:12px 14px;background:var(--background);color:var(--foreground);resize:vertical}
2016
+ .c3 .ds-save{position:sticky;bottom:calc(-1 * var(--sp-4));z-index:3;background:var(--card);border-top:1px solid var(--border);padding:var(--sp-3) 0 var(--sp-4);margin-bottom:calc(-1 * var(--sp-4));display:grid;gap:8px}
2017
+ .c3 .ds-save input{width:100%;font:inherit;font-size:var(--fs-3);padding:8px 10px;border:1px solid var(--ring);border-radius:6px;background:var(--background);color:var(--foreground)}
2018
+ .c3 .ds-save input.bad{border-color:var(--destructive)}
2019
+ .c3 .ds-save .row{display:flex;gap:8px;flex-wrap:wrap;align-items:center;justify-content:space-between}
2020
+ .c3 .ds-save .sum{font:var(--fs-2) var(--mono);color:var(--muted-foreground);display:flex;gap:6px;align-items:center;flex-wrap:wrap}
2021
+ .c3 .ds-pop{display:flex;gap:6px;align-items:center;flex-wrap:wrap;padding:6px;border:1px solid var(--ring);border-radius:8px;background:var(--card)}
2022
+ .c3 .ds-pop input{flex:1 1 200px;font:var(--fs-2) var(--mono);padding:5px 8px;border:1px solid var(--border);border-radius:6px;background:var(--background);color:var(--foreground)}
2023
+
2024
+ .c3 .ds-ask{display:grid;gap:8px;padding:var(--sp-3);border:1px solid var(--border);border-radius:8px}
2025
+ .c3 .ds-ask textarea{width:100%;min-height:64px;font:inherit;font-size:var(--fs-3);padding:8px 10px;border:1px solid var(--ring);border-radius:6px;background:var(--background);color:var(--foreground);resize:vertical}
2026
+ .c3 .ds-h{margin:6px 0 0;font:500 var(--fs-2) var(--mono);color:var(--muted-foreground);text-transform:uppercase;letter-spacing:.06em;display:flex;gap:8px;align-items:center}
2027
+ .c3 .ds-prop{border:1px solid var(--border);border-radius:8px;display:grid;overflow:hidden;min-width:0}
2028
+ .c3 .ds-prop>.hd{padding:12px 14px;display:grid;grid-template-columns:auto minmax(0,1fr) auto;gap:10px;align-items:start;border-bottom:1px solid var(--border)}
2029
+ .c3 .ds-prop .ti{font-weight:600;font-size:var(--fs-3);overflow-wrap:anywhere}
2030
+ .c3 .ds-prop .sub{font:var(--fs-1) var(--mono);color:var(--muted-foreground);display:flex;gap:8px;flex-wrap:wrap;margin-top:2px}
2031
+ .c3 .ds-prop .pb{padding:12px 14px;display:grid;gap:10px;min-width:0}
2032
+ .c3 .ds-chg{border:1px solid var(--border);border-radius:8px;overflow:hidden;min-width:0}
2033
+ .c3 .ds-chg>header{display:flex;gap:8px;align-items:center;justify-content:space-between;flex-wrap:wrap;padding:6px 10px;background:var(--muted);font:var(--fs-2) var(--mono)}
2034
+ .c3 .ds-chg>header label{display:inline-flex;gap:8px;align-items:center;cursor:pointer;color:var(--foreground)}
2035
+ .c3 .ds-chg>header input{accent-color:var(--acc)}
2036
+ .c3 .ds-chg .ds-md{padding:4px 12px;font-size:var(--fs-3)}
2037
+ .c3 .ds-chg.off .ds-md{opacity:.45}
2038
+ .c3 .ds-why{font-size:var(--fs-3);margin:0;display:flex;gap:6px;align-items:baseline}
2039
+ .c3 .ds-closed{display:grid;gap:6px}
2040
+
2041
+ .c3 .ds-log{list-style:none;margin:0;padding:0;display:grid}
2042
+ .c3 .ds-log>li{display:grid;grid-template-columns:28px minmax(0,1fr);gap:10px;position:relative;padding-bottom:14px}
2043
+ .c3 .ds-log>li::before{content:"";position:absolute;left:13.5px;top:30px;bottom:0;width:1px;background:var(--border)}
2044
+ .c3 .ds-log>li:last-child::before{display:none}
2045
+ .c3 .ds-log>li.flash{background:color-mix(in oklab,var(--acc) 10%,transparent);border-radius:8px}
2046
+ .c3 .ds-av{width:28px;height:28px;border-radius:6px;display:grid;place-items:center;font:600 var(--fs-1) var(--mono);border:1px solid var(--border);background:var(--background);color:var(--muted-foreground)}
2047
+ .c3 .ds-av.h{background:var(--acc);border-color:var(--acc);color:var(--background)}
2048
+ .c3 .ds-av.a{background:var(--agent);border-color:var(--agent);color:var(--background)}
2049
+ .c3 .ds-av.w{color:var(--warning);border-color:color-mix(in oklab,var(--warning) 50%,transparent)}
2050
+ .c3 .ds-le{display:grid;gap:4px;min-width:0}
2051
+ .c3 .ds-le .l1{display:flex;gap:4px 10px;flex-wrap:wrap;align-items:baseline;justify-content:space-between}
2052
+ .c3 .ds-le .l1 b{font-size:var(--fs-3);overflow-wrap:anywhere}
2053
+ .c3 .ds-le .when{font:var(--fs-1) var(--mono);color:var(--muted-foreground);white-space:nowrap}
2054
+ .c3 .ds-le .who{font:var(--fs-2) var(--mono);color:var(--muted-foreground);display:flex;gap:4px 10px;flex-wrap:wrap;overflow-wrap:anywhere}
2055
+ .c3 .ds-le .why{font-size:var(--fs-3);margin:0;overflow-wrap:anywhere}
2056
+ .c3 .ds-le .why.none{color:var(--warning);font:var(--fs-2) var(--mono)}
2057
+ .c3 .ds-le .tags{display:flex;gap:4px;flex-wrap:wrap;align-items:center}
2058
+ .c3 .ds-le .tag.add{color:var(--good)}
2059
+ .c3 .ds-le .tag.del{color:var(--destructive)}
2060
+ .c3 .ds-cmp{border:1px solid var(--border);border-radius:8px;padding:4px 12px;margin-top:4px;min-width:0}
2061
+ .c3 .ds-cmp .ds-md{font-size:var(--fs-3)}
2062
+ .c3 .ds-cmp h4{margin:8px 0 0;font:500 var(--fs-1) var(--mono);color:var(--muted-foreground);text-transform:uppercase;letter-spacing:.05em}
2063
+ .c3 .ds-whyadd{display:flex;gap:6px;flex-wrap:wrap}
2064
+ .c3 .ds-whyadd input{flex:1 1 220px;min-width:0;font:var(--fs-2) var(--mono);padding:5px 8px;border:1px solid var(--warning);border-radius:6px;background:var(--background);color:var(--foreground)}
2065
+
2066
+ @media (max-width:640px){
2067
+ .c3 .ds-sec{margin-left:-12px;padding-left:9px}
2068
+ .c3 .ds-ed{padding:12px 12px 12px 18px}
2069
+ .c3 .ds-by{white-space:normal;text-align:left}
2070
+ .c3 .ds-top .seg button:not([aria-pressed="true"]) .lbl{display:none}
2071
+ .c3 .ds-tb{flex-wrap:nowrap;overflow-x:auto}
2072
+ }
2073
+ .c3 .ds input[type=checkbox]{width:auto;min-height:0;padding:0;margin:0;flex:none;border-radius:0}
2074
+
1910
2075
  /* the card before a meeting (initiative c3-meeting-card) */
1911
2076
  .c3 .cal3-ctx .cal3-p{margin:0;font-size:var(--fs-2);line-height:1.5;color:var(--foreground)}
1912
2077
  .c3 .cal3-ctx.cal3-gist{padding:10px 12px;border:1px solid var(--border);border-radius:8px;background:var(--muted)}
@@ -42,7 +42,7 @@ import { VIEWS, REVIEW_DAYS, workPlace, arrives, buildWork, addIso, daysBetween,
42
42
 
43
43
  const SVG_NS = 'http://www.w3.org/2000/svg';
44
44
  /* The mockup's icon sprite (console3.html lines 970 to 1026, ids renamed c3i-*, plus the c3 layer's edit and trash). */
45
- const SPRITE = '<symbol id="c3i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></symbol><symbol id="c3i-star" viewBox="0 0 24 24"><path d="M12 3l2.6 5.6 6.1.7-4.5 4.2 1.2 6L12 16.5 6.6 19.5l1.2-6L3.3 9.3l6.1-.7z"/></symbol><symbol id="c3i-moon" viewBox="0 0 24 24"><path d="M20 14a8 8 0 11-10-10 6 6 0 0010 10z"/></symbol><symbol id="c3i-cal" viewBox="0 0 24 24"><rect x="4" y="5" width="16" height="15" rx="2"/><path d="M4 10h16M8 3v4M16 3v4"/></symbol><symbol id="c3i-inbox" viewBox="0 0 24 24"><path d="M4 13l2-8h12l2 8M4 13v6h16v-6M4 13h5l1 2h4l1-2h5"/></symbol><symbol id="c3i-todo" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="3"/><path d="M8 12l3 3 5-6"/></symbol><symbol id="c3i-init" viewBox="0 0 24 24"><path d="M12 3l9 5-9 5-9-5 9-5zM3 13l9 5 9-5"/></symbol><symbol id="c3i-report" viewBox="0 0 24 24"><path d="M5 20V10M12 20V4M19 20v-7"/></symbol><symbol id="c3i-notes" viewBox="0 0 24 24"><path d="M6 3h9l4 4v14H6zM14 3v5h5M9 13h7M9 17h5"/></symbol><symbol id="c3i-user" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 4-6 8-6s8 2 8 6"/></symbol><symbol id="c3i-doc" viewBox="0 0 24 24"><path d="M6 3h9l4 4v14H6zM14 3v5h5"/></symbol><symbol id="c3i-sheet" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M4 10h16M4 15h16M10 4v16"/></symbol><symbol id="c3i-img" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 16l-5-5-9 9"/></symbol><symbol id="c3i-pdf" viewBox="0 0 24 24"><path d="M6 3h9l4 4v14H6zM14 3v5h5M9 15h6M9 18h4"/></symbol><symbol id="c3i-term" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9l3 3-3 3M13 15h4"/></symbol><symbol id="c3i-branch" viewBox="0 0 24 24"><circle cx="6" cy="5" r="2"/><circle cx="6" cy="19" r="2"/><circle cx="18" cy="9" r="2"/><path d="M6 7v10M18 11c0 4-6 3-12 6"/></symbol><symbol id="c3i-repo" viewBox="0 0 24 24"><path d="M5 4h11a3 3 0 013 3v13H8a3 3 0 01-3-3zM5 17a3 3 0 013-3h11"/></symbol><symbol id="c3i-flag" viewBox="0 0 24 24"><path d="M6 21V4M6 5h11l-2 4 2 4H6"/></symbol><symbol id="c3i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></symbol><symbol id="c3i-alert" viewBox="0 0 24 24"><path d="M12 3l10 18H2zM12 10v5M12 18v.5"/></symbol><symbol id="c3i-check" viewBox="0 0 24 24"><path d="M5 12l5 5 9-10"/></symbol><symbol id="c3i-x" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6L6 18"/></symbol><symbol id="c3i-eye" viewBox="0 0 24 24"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></symbol><symbol id="c3i-undo" viewBox="0 0 24 24"><path d="M4 12a8 8 0 108-8M4 4v6h6"/></symbol><symbol id="c3i-out" viewBox="0 0 24 24"><path d="M5 12h14M13 6l6 6-6 6"/></symbol><symbol id="c3i-shield" viewBox="0 0 24 24"><path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6z"/></symbol><symbol id="c3i-mic" viewBox="0 0 24 24"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0014 0M12 18v3"/></symbol><symbol id="c3i-clip" viewBox="0 0 24 24"><rect x="6" y="4" width="12" height="17" rx="2"/><path d="M9 4h6v3H9zM9 12h6M9 16h4"/></symbol><symbol id="c3i-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5"/></symbol><symbol id="c3i-phone" viewBox="0 0 24 24"><rect x="7" y="2" width="10" height="20" rx="2"/><path d="M11 18h2"/></symbol><symbol id="c3i-cloud" viewBox="0 0 24 24"><path d="M7 18a4 4 0 010-8 5 5 0 019.6-1A4.5 4.5 0 0117 18z"/></symbol><symbol id="c3i-bolt" viewBox="0 0 24 24"><path d="M13 2L4 14h7l-1 8 9-12h-7z"/></symbol><symbol id="c3i-plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></symbol><symbol id="c3i-scale" viewBox="0 0 24 24"><path d="M12 3v18M5 7h14M5 7l-3 7a3 3 0 006 0zM19 7l-3 7a3 3 0 006 0z"/></symbol><symbol id="c3i-reply" viewBox="0 0 24 24"><path d="M9 7L4 12l5 5M4 12h10a6 6 0 016 6"/></symbol><symbol id="c3i-info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 11v6M12 7.5v.5"/></symbol><symbol id="c3i-wait" viewBox="0 0 24 24"><path d="M7 3h10M7 21h10M8 3c0 5 8 5 8 9s-8 4-8 9"/></symbol><symbol id="c3i-up" viewBox="0 0 24 24"><path d="M7 11v9H4v-9zM7 11l4-8c2 0 3 1 3 3v3h5a2 2 0 012 2l-1 6a2 2 0 01-2 2H7"/></symbol><symbol id="c3i-cols" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M9 4v16M15 4v16"/></symbol><symbol id="c3i-fold" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M15 4v16"/></symbol><symbol id="c3i-spark" viewBox="0 0 24 24"><path d="M12 3l2 5 5 2-5 2-2 5-2-5-5-2 5-2z"/></symbol><symbol id="c3i-target" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1"/></symbol><symbol id="c3i-filter" viewBox="0 0 24 24"><path d="M3 5h18l-7 8v6l-4-2v-4z"/></symbol><symbol id="c3i-mail" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></symbol><symbol id="c3i-lock" viewBox="0 0 24 24"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V8a4 4 0 018 0v3"/></symbol><symbol id="c3i-play" viewBox="0 0 24 24"><path d="M7 4l13 8-13 8z"/></symbol><symbol id="c3i-stop" viewBox="0 0 24 24"><rect x="6" y="6" width="12" height="12" rx="1"/></symbol><symbol id="i-pause" viewBox="0 0 24 24"><path d="M8 5v14M16 5v14"/></symbol><symbol id="c3i-prev" viewBox="0 0 24 24"><path d="M15 6l-6 6 6 6"/></symbol><symbol id="c3i-next" viewBox="0 0 24 24"><path d="M9 6l6 6-6 6"/></symbol><symbol id="c3i-tree" viewBox="0 0 24 24"><path d="M5 4h7v4H5zM13 10h7v4h-7zM13 17h7v4h-7zM8 8v11h5M8 12h5"/></symbol><symbol id="c3i-org" viewBox="0 0 24 24"><path d="M9 3h6v5H9zM3 16h6v5H3zM15 16h6v5h-6zM12 8v4M6 16v-4h12v4"/></symbol><symbol id="c3i-drop" viewBox="0 0 24 24"><path d="M12 3c4 5 6 8 6 11a6 6 0 01-12 0c0-3 2-6 6-11z"/></symbol><symbol id="c3i-more" viewBox="0 0 24 24"><path d="M5 12h.01M12 12h.01M19 12h.01" stroke-width="3"/></symbol><symbol id="c3i-zin" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5M8 11h6M11 8v6"/></symbol><symbol id="c3i-zout" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5M8 11h6"/></symbol><symbol id="c3i-bell" viewBox="0 0 24 24"><path d="M6 17v-6a6 6 0 0112 0v6l2 2H4zM10 21h4"/></symbol><symbol id="c3i-vol" viewBox="0 0 24 24"><path d="M4 9v6h4l5 4V5L8 9zM16.5 8.5a5 5 0 010 7M19 6a8.5 8.5 0 010 12"/></symbol><symbol id="c3i-menu" viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h16"/></symbol><symbol id="c3i-edit" viewBox="0 0 24 24"><path d="M4 20h4L19 9l-4-4L4 16v4zM13.5 6.5l4 4"/></symbol><symbol id="c3i-trash" viewBox="0 0 24 24"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></symbol>';
45
+ const SPRITE = '<symbol id="c3i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></symbol><symbol id="c3i-star" viewBox="0 0 24 24"><path d="M12 3l2.6 5.6 6.1.7-4.5 4.2 1.2 6L12 16.5 6.6 19.5l1.2-6L3.3 9.3l6.1-.7z"/></symbol><symbol id="c3i-moon" viewBox="0 0 24 24"><path d="M20 14a8 8 0 11-10-10 6 6 0 0010 10z"/></symbol><symbol id="c3i-cal" viewBox="0 0 24 24"><rect x="4" y="5" width="16" height="15" rx="2"/><path d="M4 10h16M8 3v4M16 3v4"/></symbol><symbol id="c3i-inbox" viewBox="0 0 24 24"><path d="M4 13l2-8h12l2 8M4 13v6h16v-6M4 13h5l1 2h4l1-2h5"/></symbol><symbol id="c3i-todo" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="3"/><path d="M8 12l3 3 5-6"/></symbol><symbol id="c3i-init" viewBox="0 0 24 24"><path d="M12 3l9 5-9 5-9-5 9-5zM3 13l9 5 9-5"/></symbol><symbol id="c3i-report" viewBox="0 0 24 24"><path d="M5 20V10M12 20V4M19 20v-7"/></symbol><symbol id="c3i-notes" viewBox="0 0 24 24"><path d="M6 3h9l4 4v14H6zM14 3v5h5M9 13h7M9 17h5"/></symbol><symbol id="c3i-user" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 4-6 8-6s8 2 8 6"/></symbol><symbol id="c3i-doc" viewBox="0 0 24 24"><path d="M6 3h9l4 4v14H6zM14 3v5h5"/></symbol><symbol id="c3i-sheet" viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M4 10h16M4 15h16M10 4v16"/></symbol><symbol id="c3i-img" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="10" r="2"/><path d="M21 16l-5-5-9 9"/></symbol><symbol id="c3i-pdf" viewBox="0 0 24 24"><path d="M6 3h9l4 4v14H6zM14 3v5h5M9 15h6M9 18h4"/></symbol><symbol id="c3i-term" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 9l3 3-3 3M13 15h4"/></symbol><symbol id="c3i-branch" viewBox="0 0 24 24"><circle cx="6" cy="5" r="2"/><circle cx="6" cy="19" r="2"/><circle cx="18" cy="9" r="2"/><path d="M6 7v10M18 11c0 4-6 3-12 6"/></symbol><symbol id="c3i-repo" viewBox="0 0 24 24"><path d="M5 4h11a3 3 0 013 3v13H8a3 3 0 01-3-3zM5 17a3 3 0 013-3h11"/></symbol><symbol id="c3i-flag" viewBox="0 0 24 24"><path d="M6 21V4M6 5h11l-2 4 2 4H6"/></symbol><symbol id="c3i-clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></symbol><symbol id="c3i-alert" viewBox="0 0 24 24"><path d="M12 3l10 18H2zM12 10v5M12 18v.5"/></symbol><symbol id="c3i-check" viewBox="0 0 24 24"><path d="M5 12l5 5 9-10"/></symbol><symbol id="c3i-x" viewBox="0 0 24 24"><path d="M6 6l12 12M18 6L6 18"/></symbol><symbol id="c3i-eye" viewBox="0 0 24 24"><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></symbol><symbol id="c3i-undo" viewBox="0 0 24 24"><path d="M4 12a8 8 0 108-8M4 4v6h6"/></symbol><symbol id="c3i-out" viewBox="0 0 24 24"><path d="M5 12h14M13 6l6 6-6 6"/></symbol><symbol id="c3i-shield" viewBox="0 0 24 24"><path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6z"/></symbol><symbol id="c3i-mic" viewBox="0 0 24 24"><rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5 11a7 7 0 0014 0M12 18v3"/></symbol><symbol id="c3i-clip" viewBox="0 0 24 24"><rect x="6" y="4" width="12" height="17" rx="2"/><path d="M9 4h6v3H9zM9 12h6M9 16h4"/></symbol><symbol id="c3i-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5"/></symbol><symbol id="c3i-phone" viewBox="0 0 24 24"><rect x="7" y="2" width="10" height="20" rx="2"/><path d="M11 18h2"/></symbol><symbol id="c3i-cloud" viewBox="0 0 24 24"><path d="M7 18a4 4 0 010-8 5 5 0 019.6-1A4.5 4.5 0 0117 18z"/></symbol><symbol id="c3i-bolt" viewBox="0 0 24 24"><path d="M13 2L4 14h7l-1 8 9-12h-7z"/></symbol><symbol id="c3i-plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></symbol><symbol id="c3i-scale" viewBox="0 0 24 24"><path d="M12 3v18M5 7h14M5 7l-3 7a3 3 0 006 0zM19 7l-3 7a3 3 0 006 0z"/></symbol><symbol id="c3i-reply" viewBox="0 0 24 24"><path d="M9 7L4 12l5 5M4 12h10a6 6 0 016 6"/></symbol><symbol id="c3i-info" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 11v6M12 7.5v.5"/></symbol><symbol id="c3i-wait" viewBox="0 0 24 24"><path d="M7 3h10M7 21h10M8 3c0 5 8 5 8 9s-8 4-8 9"/></symbol><symbol id="c3i-up" viewBox="0 0 24 24"><path d="M7 11v9H4v-9zM7 11l4-8c2 0 3 1 3 3v3h5a2 2 0 012 2l-1 6a2 2 0 01-2 2H7"/></symbol><symbol id="c3i-cols" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M9 4v16M15 4v16"/></symbol><symbol id="c3i-fold" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M15 4v16"/></symbol><symbol id="c3i-spark" viewBox="0 0 24 24"><path d="M12 3l2 5 5 2-5 2-2 5-2-5-5-2 5-2z"/></symbol><symbol id="c3i-target" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="5"/><circle cx="12" cy="12" r="1"/></symbol><symbol id="c3i-filter" viewBox="0 0 24 24"><path d="M3 5h18l-7 8v6l-4-2v-4z"/></symbol><symbol id="c3i-mail" viewBox="0 0 24 24"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3 7l9 6 9-6"/></symbol><symbol id="c3i-lock" viewBox="0 0 24 24"><rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V8a4 4 0 018 0v3"/></symbol><symbol id="c3i-play" viewBox="0 0 24 24"><path d="M7 4l13 8-13 8z"/></symbol><symbol id="c3i-stop" viewBox="0 0 24 24"><rect x="6" y="6" width="12" height="12" rx="1"/></symbol><symbol id="i-pause" viewBox="0 0 24 24"><path d="M8 5v14M16 5v14"/></symbol><symbol id="c3i-prev" viewBox="0 0 24 24"><path d="M15 6l-6 6 6 6"/></symbol><symbol id="c3i-next" viewBox="0 0 24 24"><path d="M9 6l6 6-6 6"/></symbol><symbol id="c3i-tree" viewBox="0 0 24 24"><path d="M5 4h7v4H5zM13 10h7v4h-7zM13 17h7v4h-7zM8 8v11h5M8 12h5"/></symbol><symbol id="c3i-org" viewBox="0 0 24 24"><path d="M9 3h6v5H9zM3 16h6v5H3zM15 16h6v5h-6zM12 8v4M6 16v-4h12v4"/></symbol><symbol id="c3i-drop" viewBox="0 0 24 24"><path d="M12 3c4 5 6 8 6 11a6 6 0 01-12 0c0-3 2-6 6-11z"/></symbol><symbol id="c3i-more" viewBox="0 0 24 24"><path d="M5 12h.01M12 12h.01M19 12h.01" stroke-width="3"/></symbol><symbol id="c3i-zin" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5M8 11h6M11 8v6"/></symbol><symbol id="c3i-zout" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5-5M8 11h6"/></symbol><symbol id="c3i-bell" viewBox="0 0 24 24"><path d="M6 17v-6a6 6 0 0112 0v6l2 2H4zM10 21h4"/></symbol><symbol id="c3i-vol" viewBox="0 0 24 24"><path d="M4 9v6h4l5 4V5L8 9zM16.5 8.5a5 5 0 010 7M19 6a8.5 8.5 0 010 12"/></symbol><symbol id="c3i-menu" viewBox="0 0 24 24"><path d="M4 7h16M4 12h16M4 17h16"/></symbol><symbol id="c3i-edit" viewBox="0 0 24 24"><path d="M4 20h4L19 9l-4-4L4 16v4zM13.5 6.5l4 4"/></symbol><symbol id="c3i-trash" viewBox="0 0 24 24"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13"/></symbol><symbol id="c3i-bold" viewBox="0 0 24 24"><path d="M7 4h6a4 4 0 010 8H7zM7 12h7a4 4 0 010 8H7z"/></symbol><symbol id="c3i-italic" viewBox="0 0 24 24"><path d="M10 4h8M6 20h8M14 4l-4 16"/></symbol><symbol id="c3i-code" viewBox="0 0 24 24"><path d="M8 7l-5 5 5 5M16 7l5 5-5 5"/></symbol><symbol id="c3i-link" viewBox="0 0 24 24"><path d="M10 14a4 4 0 006 0l3-3a4 4 0 00-6-6l-1 1M14 10a4 4 0 00-6 0l-3 3a4 4 0 006 6l1-1"/></symbol><symbol id="c3i-ul" viewBox="0 0 24 24"><path d="M9 6h11M9 12h11M9 18h11M4.5 6h.01M4.5 12h.01M4.5 18h.01" /></symbol><symbol id="c3i-ol" viewBox="0 0 24 24"><path d="M10 6h10M10 12h10M10 18h10M4 4h1v4M4 8h2M4 14.5c0-1 2-1 2 0s-2 2-2 3.5h2"/></symbol><symbol id="c3i-task" viewBox="0 0 24 24"><rect x="3" y="4" width="6" height="6" rx="1"/><path d="M4.5 7l1 1 2-2M12 7h9M3 14h6v6H3zM12 17h9"/></symbol><symbol id="c3i-quote" viewBox="0 0 24 24"><path d="M4 6h16M8 12h12M8 18h12M4 11v8"/></symbol><symbol id="c3i-table" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 10h18M3 15h18M10 4v16"/></symbol><symbol id="c3i-hr" viewBox="0 0 24 24"><path d="M3 12h18M7 6h10M7 18h10"/></symbol><symbol id="c3i-redo" viewBox="0 0 24 24"><path d="M20 12a8 8 0 11-8-8M20 4v6h-6"/></symbol><symbol id="c3i-git" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M3 12h6M15 12h6"/></symbol><symbol id="c3i-md" viewBox="0 0 24 24"><rect x="2" y="5" width="20" height="14" rx="2"/><path d="M6 15V9l3 3 3-3v6M16 9v6M14 13l2 2 2-2"/></symbol><symbol id="c3i-copy" viewBox="0 0 24 24"><rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V5a1 1 0 00-1-1H5a1 1 0 00-1 1v10a1 1 0 001 1h3"/></symbol>';
46
46
 
47
47
  /** Link console3.css once, from this module's own folder (the same way primitives.js links components.css). */
48
48
  export function ensureC3Css() {