@gotcos/glasses-server 6.98.1 → 6.99.12
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/CHANGELOG.md +149 -0
- package/package.json +2 -1
- package/server/index.ts +147 -9
- package/server/lib/api-auth.ts +31 -14
- package/server/lib/claude-bridge.ts +21 -2
- package/server/lib/codex-bridge.ts +21 -2
- package/server/lib/display-bus.ts +3 -0
- package/server/lib/drop-destinations.ts +134 -0
- package/server/lib/drop-history-search.ts +97 -0
- package/server/lib/drop-p2-boot.ts +60 -0
- package/server/lib/drop-p2-runtime.ts +237 -0
- package/server/lib/drop-p2-settings.ts +174 -0
- package/server/lib/drop-resolution.ts +168 -0
- package/server/lib/drop-router.ts +325 -0
- package/server/lib/drop-service.ts +739 -0
- package/server/lib/drop-settings.ts +45 -0
- package/server/lib/drop-store.ts +428 -0
- package/server/lib/drop-when.ts +265 -0
- package/server/lib/glasses-pairing.ts +15 -0
- package/server/lib/learning-needs-you.ts +111 -0
- package/server/lib/live-cues-v2-settings.ts +2 -1
- package/server/lib/live-cues-v2.ts +6 -4
- package/server/lib/meeting-consolidation-run.ts +169 -0
- package/server/lib/meeting-store.ts +61 -0
- package/server/lib/meeting-title-steer.ts +200 -0
- package/server/lib/network-policy.ts +84 -6
- package/server/lib/permission-broker.ts +11 -0
- package/server/lib/ping-budget.ts +149 -0
- package/server/lib/ping-engine.ts +245 -0
- package/server/lib/ping-feed-sink.ts +33 -0
- package/server/lib/ping-sources.ts +192 -0
- package/server/lib/query-job-coordinator.ts +5 -2
- package/server/lib/query-job-runtime.ts +45 -4
- package/server/lib/query-job-types.ts +10 -1
- package/server/lib/reminder-scheduler.ts +153 -0
- package/server/lib/tailnet-self-name.ts +103 -0
- package/server/lib/tailscale-nudge.ts +40 -2
- package/server/lib/task-store.ts +32 -0
- package/server/lib/web-ask-admission.ts +56 -0
- package/server/lib/web-live-chunks.ts +273 -0
- package/server/lib/web-memories-bridge.ts +323 -0
- package/server/lib/web-session-auth.ts +492 -0
- package/server/lib/web-usage-bridge.ts +154 -0
- package/server/lib/work-search.ts +14 -1
- package/server/lib/your-move-count.ts +58 -0
- package/server/routes/display.ts +11 -1
- package/server/routes/drops.ts +260 -0
- package/server/routes/health.ts +16 -0
- package/server/routes/live-cues.ts +5 -4
- package/server/routes/meeting.ts +61 -5
- package/server/routes/permission-broker.ts +23 -3
- package/server/routes/transcribe-stream.ts +34 -3
- package/server/routes/web-control.ts +569 -0
- package/server/routes/welcome-context.ts +2 -1
- package/server/web-control/app.css +519 -0
- package/server/web-control/app.js +3643 -0
- package/server/web-control/control.css +312 -0
- package/server/web-control/fonts/DMSans.ttf +0 -0
- package/server/web-control/fonts/Fraunces.ttf +0 -0
- package/server/web-control/fonts/JetBrainsMono.ttf +0 -0
- package/server/web-control/index.html +275 -0
- package/server/web-control/mark.svg +1 -0
- package/server/web-control/memories/d3.min.js +2 -0
- package/server/web-control/memories/graph-explorer.css +200 -0
- package/server/web-control/memories/graph-explorer.js +748 -0
- package/server/web-control/memories/memories-app.js +1561 -0
- package/server/web-control/memories/memories-theme.css +73 -0
- package/server/web-control/memories/memories.html +72 -0
- package/server/web-control/memories/memory-stewardship.js +63 -0
- package/server/web-control/memories/memory-workspace.js +216 -0
- package/server/web-control/panes/cos-chat.css +157 -0
- package/server/web-control/panes/cos-chat.js +973 -0
- package/server/web-control/panes/dictation.js +173 -0
- package/server/web-control/panes/meetings.css +257 -0
- package/server/web-control/panes/meetings.js +2264 -0
- package/server/web-control/panes/messages.js +711 -0
- package/server/web-control/panes/questions.css +32 -0
- package/server/web-control/panes/questions.js +275 -0
- package/server/web-control/panes/sessions.css +452 -0
- package/server/web-control/panes/settings.js +292 -0
- package/server/web-control/panes/speakers.css +269 -0
- package/server/web-control/panes/speakers.js +2755 -0
- package/server/web-control/panes/threads.js +200 -0
- package/server/web-control/panes/work.css +261 -0
- package/server/web-control/panes/work.js +1667 -0
- package/server/web-control/shell.js +294 -0
- package/server/web-control/theme.js +25 -0
- package/server/web-control/vendor/qrcode-encoder.js +10 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/* COS Control Web: Dictation, mirroring Control's DictationActivityPane and Dictionary pane
|
|
2
|
+
(MacDictation.swift 5338-5555 and 6205+, MacDictationCore.swift 1380).
|
|
3
|
+
The dictionary is the server's (GET /api/transcription-glossary and /recent; POST correction,
|
|
4
|
+
correction/undo, correction/remove, vocabulary/add, vocabulary/remove), so it works here in full.
|
|
5
|
+
Dictations and the fixes Learn from my edits suggests are files Control keeps on the Mac; the
|
|
6
|
+
server has no route for them, so this pane says where they are instead of showing empty lists.
|
|
7
|
+
Routes: #/dictation, #/dictation/dictionary. */
|
|
8
|
+
(function () {
|
|
9
|
+
'use strict';
|
|
10
|
+
var W = window.COSWeb;
|
|
11
|
+
var $ = W.$, esc = W.esc, api = W.api;
|
|
12
|
+
var pane = $('pane-dictation');
|
|
13
|
+
|
|
14
|
+
var S = { glossary: null, recent: null, recentError: '', state: 'idle', error: '', q: '', busy: false };
|
|
15
|
+
|
|
16
|
+
function toDate(v) { var d = v ? new Date(v) : null; return d && !isNaN(d.getTime()) ? d : null; }
|
|
17
|
+
// Control's "when": "now" under a minute, "h:mm a" today, otherwise "MMM d".
|
|
18
|
+
function when(v) {
|
|
19
|
+
var d = toDate(v); if (!d) return '';
|
|
20
|
+
var now = new Date();
|
|
21
|
+
if (now - d < 60000) return 'now';
|
|
22
|
+
var same = d.getFullYear() === now.getFullYear() && d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
|
|
23
|
+
return same ? d.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' }) : d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
|
|
24
|
+
}
|
|
25
|
+
function sourceLabel(s) { return s === 'g2' ? 'G2' : s === 'manual' ? 'added' : s === 'dictation' || s === 'meeting' ? s : 'COS'; }
|
|
26
|
+
function pair(heard, written) {
|
|
27
|
+
return '<span class="cx-pair"><span class="heard">' + esc(heard) + '</span><svg class="icon" aria-hidden="true"><use href="#i-chev"/></svg><span class="written">' + esc(written) + '</span></span>';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function load(force) {
|
|
31
|
+
if (S.state === 'loading' || (S.glossary && !force)) return;
|
|
32
|
+
S.state = 'loading';
|
|
33
|
+
render();
|
|
34
|
+
Promise.all([
|
|
35
|
+
api('/api/transcription-glossary'),
|
|
36
|
+
api('/api/transcription-glossary/recent').then(function (d) { return d; }, function (err) { return { error: err }; })
|
|
37
|
+
]).then(function (res) {
|
|
38
|
+
S.glossary = res[0] || { vocabulary: [], corrections: {} };
|
|
39
|
+
S.recent = res[1] && res[1].entries ? res[1].entries : null;
|
|
40
|
+
S.recentError = res[1] && res[1].error ? (res[1].error.status === 404 ? 'Update the COS server to see recent corrections here.' : (res[1].error.message || 'Recent corrections could not be read.')) : '';
|
|
41
|
+
S.state = 'ready';
|
|
42
|
+
render();
|
|
43
|
+
}, function (err) {
|
|
44
|
+
S.state = 'error';
|
|
45
|
+
S.error = err.message || 'The dictionary could not be read.';
|
|
46
|
+
render();
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function hero(title, detail, actions) {
|
|
51
|
+
return '<header class="cx-hero" data-tint="dictation"><span class="cx-glyph"><svg class="icon"><use href="#i-dictation"/></svg></span>' +
|
|
52
|
+
'<div class="cx-hero-text"><h1 class="cx-title">' + esc(title) + '</h1>' + (detail ? '<p class="cx-detail-line">' + esc(detail) + '</p>' : '') + '</div>' +
|
|
53
|
+
'<div class="cx-actions">' + actions + '</div></header>';
|
|
54
|
+
}
|
|
55
|
+
function recentRows(list, limit) {
|
|
56
|
+
if (S.recentError) return '<div class="cx-notice">' + esc(S.recentError) + '</div>';
|
|
57
|
+
if (!list) return '<div class="cx-list">' + W.skeletonRows(3) + '</div>';
|
|
58
|
+
var rows = limit ? list.slice(0, limit) : list;
|
|
59
|
+
if (!rows.length) return '<div class="cx-list"><p class="cx-empty">Nothing learned yet. Use Fix a word on a dictation, or add a correction below.</p></div>';
|
|
60
|
+
return '<div class="cx-list">' + rows.map(function (e) {
|
|
61
|
+
return '<div class="cx-row cx-row-static"><span class="cx-row-glyph"><svg class="icon"><use href="#i-dictation"/></svg></span><span class="cx-row-body">' +
|
|
62
|
+
'<span class="cx-row-title">' + pair(e.heard, e.written) + '</span>' +
|
|
63
|
+
'<span class="cx-meta" style="margin-top:3px"><span class="cx-time">' + esc(sourceLabel(e.source) + ' · ' + when(e.at)) + '</span></span></span>' +
|
|
64
|
+
'<button type="button" class="cx-btn quiet" data-dict="undo" data-id="' + esc(e.id) + '">Undo</button></div>';
|
|
65
|
+
}).join('') + '</div>';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function renderMain() {
|
|
69
|
+
var body;
|
|
70
|
+
if (S.state === 'error') body = '<div class="cx-notice warn">' + esc(S.error) + '</div>';
|
|
71
|
+
else body =
|
|
72
|
+
'<p class="cx-strip-label" style="margin-top:16px">RECENT CORRECTIONS</p>' + recentRows(S.recent, 10) +
|
|
73
|
+
'<div class="cx-notice" style="margin-top:16px"><svg class="icon" aria-hidden="true"><use href="#i-dictation"/></svg><span>Your dictations and the fixes Learn from my edits suggests are kept on your Mac, so they show in COS Control there. The dictionary is shared, so a word you add here applies to G2 meetings, Mac recordings and dictation from the next sentence.</span></div>';
|
|
74
|
+
pane.innerHTML = '<div class="cx" data-tint="dictation">' +
|
|
75
|
+
hero('Dictation', 'Your dictations, the corrections COS learned from your edits, and the dictionary.',
|
|
76
|
+
'<button type="button" class="cx-btn quiet" data-dict="dictionary">Dictionary</button>' +
|
|
77
|
+
'<button type="button" class="cx-btn quiet" data-dict="refresh"><svg class="icon"><use href="#i-refresh"/></svg>Refresh</button>') +
|
|
78
|
+
body + '</div>';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function renderDictionary() {
|
|
82
|
+
W.setCrumb('Dictionary');
|
|
83
|
+
var g = S.glossary || { vocabulary: [], corrections: {} };
|
|
84
|
+
var q = S.q.trim().toLowerCase();
|
|
85
|
+
var vocab = (g.vocabulary || []).filter(function (t) { return !q || String(t).toLowerCase().indexOf(q) >= 0; });
|
|
86
|
+
var corr = Object.keys(g.corrections || {}).map(function (h) { return [h, g.corrections[h]]; })
|
|
87
|
+
.filter(function (p) { return !q || p[0].toLowerCase().indexOf(q) >= 0 || String(p[1]).toLowerCase().indexOf(q) >= 0; });
|
|
88
|
+
var recent = (S.recent || []).filter(function (e) { return !q || String(e.heard).toLowerCase().indexOf(q) >= 0 || String(e.written).toLowerCase().indexOf(q) >= 0; });
|
|
89
|
+
var shownVocab = vocab.slice(0, 60);
|
|
90
|
+
pane.innerHTML = '<div class="cx" data-tint="dictation">' +
|
|
91
|
+
hero('Dictionary', 'One dictionary for G2 meetings, Mac recordings and dictation. A word you add or fix applies from the next sentence.',
|
|
92
|
+
'<button type="button" class="cx-btn quiet" data-dict="refresh"><svg class="icon"><use href="#i-refresh"/></svg>Refresh</button>') +
|
|
93
|
+
'<div class="cx-controls"><div class="cx-search" style="max-width:420px"><input class="field" type="search" id="dict-q" placeholder="Search words and corrections" value="' + esc(S.q) + '" autocomplete="off" aria-label="Search words and corrections">' +
|
|
94
|
+
(S.q ? '<button type="button" class="cx-clear" data-dict="clear">Clear</button>' : '') + '</div></div>' +
|
|
95
|
+
(S.state !== 'ready' ? (S.state === 'error' ? '<div class="cx-notice warn">' + esc(S.error) + '</div>' : '<div class="cx-list">' + W.skeletonRows(4) + '</div>') :
|
|
96
|
+
'<div class="cx-dict-cols"><section><h2 class="cx-col-title">Recent corrections</h2>' + (S.recent && !recent.length && q ? '<div class="cx-list"><p class="cx-empty">No recent correction matches.</p></div>' : recentRows(S.recent ? recent : null, 0)) + '</section>' +
|
|
97
|
+
'<section><h2 class="cx-col-title">Vocabulary · ' + (g.vocabulary || []).length + '</h2>' +
|
|
98
|
+
'<form class="cx-inline-form" data-dict-form="vocab"><input class="field" id="dict-term" type="text" placeholder="Add a word or name" maxlength="100" autocomplete="off" aria-label="Add a word or name"><button type="submit" class="cx-btn">Add</button></form>' +
|
|
99
|
+
'<div class="cx-chips">' + shownVocab.map(function (t) {
|
|
100
|
+
return '<span class="cx-chip">' + esc(t) + '<button type="button" data-dict="vocab-remove" data-term="' + esc(t) + '" aria-label="Remove ' + esc(t) + '">×</button></span>';
|
|
101
|
+
}).join('') + (vocab.length > 60 ? '<span class="cx-chip more">+' + (vocab.length - 60) + '</span>' : '') + '</div></section></div>' +
|
|
102
|
+
'<section style="margin-top:22px"><h2 class="cx-col-title">Corrections · ' + Object.keys(g.corrections || {}).length + '</h2>' +
|
|
103
|
+
'<p class="cx-note" style="margin:0 2px 10px">When COS hears the word on the left, it writes the one on the right.</p>' +
|
|
104
|
+
'<form class="cx-inline-form" data-dict-form="correction"><input class="field" id="dict-heard" type="text" placeholder="Heard" maxlength="100" autocomplete="off" aria-label="Heard">' +
|
|
105
|
+
'<input class="field" id="dict-written" type="text" placeholder="Written" maxlength="100" autocomplete="off" aria-label="Written"><button type="submit" class="cx-btn">Add</button></form>' +
|
|
106
|
+
'<div class="cx-list">' + (corr.length ? corr.map(function (p) {
|
|
107
|
+
return '<div class="cx-row cx-row-static"><span class="cx-row-glyph"><svg class="icon"><use href="#i-dictation"/></svg></span><span class="cx-row-body"><span class="cx-row-title">' + pair(p[0], p[1]) + '</span></span>' +
|
|
108
|
+
'<button type="button" class="cx-x" data-dict="corr-remove" data-heard="' + esc(p[0]) + '" aria-label="Remove ' + esc(p[0]) + '">×</button></div>';
|
|
109
|
+
}).join('') : '<p class="cx-empty">No corrections yet.</p>') + '</div></section>') +
|
|
110
|
+
'<p class="cx-note" id="dict-status" role="status"></p></div>';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function render() {
|
|
114
|
+
var r = W.parseRoute();
|
|
115
|
+
if (r.pane !== 'dictation') return;
|
|
116
|
+
var focus = document.activeElement && document.activeElement.id === 'dict-q';
|
|
117
|
+
var caret = focus ? document.activeElement.selectionStart : null;
|
|
118
|
+
if (r.args[0] === 'dictionary') renderDictionary(); else renderMain();
|
|
119
|
+
if (focus) { var input = $('dict-q'); if (input) { input.focus(); try { input.setSelectionRange(caret, caret); } catch (_e) {} } }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function write(path, body, done) {
|
|
123
|
+
if (S.busy) return;
|
|
124
|
+
S.busy = true;
|
|
125
|
+
api(path, { method: 'POST', body: body }).then(function () {
|
|
126
|
+
S.busy = false;
|
|
127
|
+
if (done) W.toast(done);
|
|
128
|
+
S.glossary = null; S.state = 'idle'; load(true);
|
|
129
|
+
}, function (err) {
|
|
130
|
+
S.busy = false;
|
|
131
|
+
var msg = err && err.message ? err.message : 'That change was not saved.';
|
|
132
|
+
var status = $('dict-status');
|
|
133
|
+
if (status) { status.textContent = msg; status.style.color = 'var(--amber)'; } else W.toast(msg);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
pane.addEventListener('click', function (e) {
|
|
138
|
+
var t = e.target.closest('[data-dict]');
|
|
139
|
+
if (!t) return;
|
|
140
|
+
var act = t.getAttribute('data-dict');
|
|
141
|
+
if (act === 'dictionary') return W.go('dictation', ['dictionary']);
|
|
142
|
+
if (act === 'refresh') { S.glossary = null; S.state = 'idle'; return load(true); }
|
|
143
|
+
if (act === 'clear') { S.q = ''; return render(); }
|
|
144
|
+
if (act === 'undo') return write('/api/transcription-glossary/correction/undo', { id: t.getAttribute('data-id') }, 'Undone');
|
|
145
|
+
if (act === 'vocab-remove') return write('/api/transcription-glossary/vocabulary/remove', { term: t.getAttribute('data-term') }, 'Removed');
|
|
146
|
+
if (act === 'corr-remove') return write('/api/transcription-glossary/correction/remove', { heard: t.getAttribute('data-heard') }, 'Removed');
|
|
147
|
+
});
|
|
148
|
+
pane.addEventListener('input', function (e) {
|
|
149
|
+
if (e.target.id !== 'dict-q') return;
|
|
150
|
+
S.q = e.target.value;
|
|
151
|
+
clearTimeout(pane._t); pane._t = setTimeout(render, 120);
|
|
152
|
+
});
|
|
153
|
+
pane.addEventListener('submit', function (e) {
|
|
154
|
+
var f = e.target.getAttribute('data-dict-form');
|
|
155
|
+
if (!f) return;
|
|
156
|
+
e.preventDefault();
|
|
157
|
+
if (f === 'vocab') {
|
|
158
|
+
var term = ($('dict-term').value || '').trim();
|
|
159
|
+
if (!term) return;
|
|
160
|
+
if (((S.glossary && S.glossary.vocabulary) || []).some(function (v) { return String(v).toLowerCase() === term.toLowerCase(); })) { W.toast('Already in the dictionary.'); return; }
|
|
161
|
+
write('/api/transcription-glossary/vocabulary/add', { term: term }, 'Added');
|
|
162
|
+
} else if (f === 'correction') {
|
|
163
|
+
var heard = ($('dict-heard').value || '').trim(), written = ($('dict-written').value || '').trim();
|
|
164
|
+
if (!heard || !written) return;
|
|
165
|
+
write('/api/transcription-glossary/correction', { heard: heard, written: written, source: 'manual' }, 'Added');
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
W.registerPane('dictation', {
|
|
170
|
+
load: function () { load(false); render(); },
|
|
171
|
+
up: function () { return null; }
|
|
172
|
+
});
|
|
173
|
+
})();
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/* COS Control Web: meetings pane styles, on top of control.css (Control 0.5.366 ActivityMeetings.swift).
|
|
2
|
+
Phone first (closed iPhone Duo, 382 wide): one column, 36pt+ targets, nothing fixed to the bottom.
|
|
3
|
+
From 1100 the live pane and a saved meeting put the Ask panel beside the transcript, as Control's live pane does. */
|
|
4
|
+
|
|
5
|
+
.mt { --mt-live: var(--rec); }
|
|
6
|
+
.mt [hidden] { display: none !important; }
|
|
7
|
+
.mt-amber { color: var(--amber); }
|
|
8
|
+
.mt-muted { color: var(--sub); font-size: 13px; line-height: 1.45; margin: 0; }
|
|
9
|
+
.mt-amber-line { color: var(--amber); font-size: 13px; line-height: 1.45; margin: 6px 0 0; }
|
|
10
|
+
.mt-notice { font-size: 13px; color: var(--sub); margin: 8px 2px 0; }
|
|
11
|
+
.mt-side-note { font-size: 12.5px; color: var(--tertiary); white-space: nowrap; }
|
|
12
|
+
.mt-wait { margin: 14px 2px 10px; }
|
|
13
|
+
|
|
14
|
+
/* ── month calendar ───────────────────────────────────── */
|
|
15
|
+
.mt-cal { padding: 12px 0 12px; border-bottom: 1px solid var(--hairline); max-width: 520px; }
|
|
16
|
+
.mt-cal-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
|
|
17
|
+
.mt-cal-title { font: 500 17px/1.2 var(--display); color: var(--ink); }
|
|
18
|
+
.mt-nav { width: 36px; height: 36px; border-radius: 9px; display: grid; place-items: center; color: var(--ink); border: 1px solid var(--panel-border); background: var(--btn-bg); }
|
|
19
|
+
.mt-nav .icon { width: 16px; height: 16px; stroke-width: 2; }
|
|
20
|
+
.mt-grid { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 4px; }
|
|
21
|
+
.mt-wd { text-align: center; font: 600 10px/1 var(--mono); letter-spacing: 0.04em; color: var(--tertiary); padding: 2px 0 4px; }
|
|
22
|
+
.mt-day, .mt-blank { min-height: 42px; }
|
|
23
|
+
.mt-day { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; border-radius: 8px; color: var(--ink); font: 400 14px/1 var(--body); }
|
|
24
|
+
.mt-day:disabled { opacity: 0.38; }
|
|
25
|
+
.mt-day:not(:disabled):hover { background: var(--qopen); }
|
|
26
|
+
.mt-day[aria-pressed='true'] { background: color-mix(in srgb, var(--tint) 18%, transparent); font-weight: 600; }
|
|
27
|
+
.mt-day:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
|
|
28
|
+
.mt-dot { width: 5px; height: 5px; border-radius: 50%; background: transparent; }
|
|
29
|
+
.mt-dot.on { background: var(--tint); }
|
|
30
|
+
.mt-all { margin-top: 8px; }
|
|
31
|
+
@media (min-width: 600px) {
|
|
32
|
+
.mt-day, .mt-blank { min-height: 34px; }
|
|
33
|
+
.mt-day { font-size: 12.5px; }
|
|
34
|
+
.mt-nav { width: 30px; height: 30px; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* ── toolbar ──────────────────────────────────────────── */
|
|
38
|
+
.mt-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; margin: 12px 0 8px; }
|
|
39
|
+
.mt-search { flex: 1 1 220px; max-width: 320px; }
|
|
40
|
+
.mt-slot:empty { display: none; }
|
|
41
|
+
.mt-select {
|
|
42
|
+
appearance: none; -webkit-appearance: none; height: 36px; padding: 0 30px 0 11px; border-radius: 9px;
|
|
43
|
+
border: 1px solid var(--panel-border); color: var(--ink); font: 500 13.5px/1 var(--body);
|
|
44
|
+
background-color: var(--btn-bg);
|
|
45
|
+
background-image: linear-gradient(45deg, transparent 50%, var(--sub) 50%), linear-gradient(135deg, var(--sub) 50%, transparent 50%);
|
|
46
|
+
background-position: calc(100% - 17px) 15px, calc(100% - 12px) 15px; background-size: 5px 5px; background-repeat: no-repeat;
|
|
47
|
+
}
|
|
48
|
+
.mt-select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
49
|
+
.mt-count { margin-left: auto; font-size: 12.5px; color: var(--sub); white-space: nowrap; }
|
|
50
|
+
.mt-note { font-size: 12.5px; color: var(--sub); margin: -2px 2px 10px; }
|
|
51
|
+
@media (max-width: 599px) {
|
|
52
|
+
.mt-search { max-width: none; flex-basis: 100%; order: 0; }
|
|
53
|
+
.mt-select { height: 40px; font-size: 15px; background-position: calc(100% - 17px) 17px, calc(100% - 12px) 17px; }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ── rows ─────────────────────────────────────────────── */
|
|
57
|
+
.mt-row { grid-template-columns: 32px minmax(0, 1fr) auto 12px; }
|
|
58
|
+
.mt-sub { display: block; margin-top: 3px; font-size: 13px; line-height: 1.4; color: var(--sub); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
59
|
+
.mt-sub.dim { color: var(--tertiary); }
|
|
60
|
+
.mt-snippet { color: var(--sub); }
|
|
61
|
+
.mt-side { display: flex; flex-wrap: wrap; justify-content: flex-end; align-items: center; gap: 6px; align-self: center; }
|
|
62
|
+
.mt-side:empty { display: none; }
|
|
63
|
+
.mt-match { display: inline-flex; align-items: center; height: 19px; padding: 0 8px; border-radius: 10px; font: 600 9.5px/1 var(--mono); letter-spacing: 0.02em; color: var(--ink); background: color-mix(in srgb, var(--tint) 16%, transparent); white-space: nowrap; }
|
|
64
|
+
.mt-row .chev { align-self: center; margin-top: 0; }
|
|
65
|
+
.mt-empty { text-align: center; }
|
|
66
|
+
.mt-capnote { font-size: 12.5px; color: var(--sub); margin: 10px 2px 0; line-height: 1.45; }
|
|
67
|
+
@media (max-width: 599px) {
|
|
68
|
+
.mt-row { grid-template-columns: 32px minmax(0, 1fr) 12px; }
|
|
69
|
+
.mt-row .mt-side { grid-column: 2; grid-row: 2; justify-content: flex-start; margin-top: 2px; }
|
|
70
|
+
.mt-row .chev { grid-column: 3; grid-row: 1; margin-top: 12px; align-self: start; }
|
|
71
|
+
}
|
|
72
|
+
@media (min-width: 1100px) { .mt-sub { font-size: 12px; } }
|
|
73
|
+
|
|
74
|
+
/* ── live now ─────────────────────────────────────────── */
|
|
75
|
+
.mt-livebox { margin: 12px 0 0; padding-bottom: 12px; border-bottom: 1px solid var(--hairline); display: grid; gap: 8px; }
|
|
76
|
+
.mt-liverow { display: flex; align-items: center; gap: 8px; background: var(--panel); border: 1px solid var(--panel-border); border-radius: 14px; overflow: hidden; }
|
|
77
|
+
.mt-liverow .cx-row { flex: 1; min-width: 0; }
|
|
78
|
+
.mt-liveact { flex: none; padding-right: 12px; }
|
|
79
|
+
.mt-liveact:empty { display: none; }
|
|
80
|
+
.mt-dotwrap { background: color-mix(in srgb, var(--mt-live) 10%, transparent); }
|
|
81
|
+
.mt-livedot { width: 8px; height: 8px; border-radius: 50%; background: color-mix(in srgb, var(--sub) 60%, transparent); flex: none; display: inline-block; }
|
|
82
|
+
.mt-livedot.on { background: var(--mt-live); animation: rec 1.6s ease-in-out infinite; }
|
|
83
|
+
.mt-livedot.big { width: 10px; height: 10px; }
|
|
84
|
+
.mt-stopline { font-size: 12.5px; color: var(--sub); margin: 0 4px; }
|
|
85
|
+
.mt-stopline.bad { color: var(--hot); font-weight: 600; }
|
|
86
|
+
@media (max-width: 599px) {
|
|
87
|
+
.mt-liverow { flex-wrap: wrap; gap: 0; }
|
|
88
|
+
.mt-liverow .cx-row { flex-basis: 100%; }
|
|
89
|
+
.mt-liverow .mt-sub { white-space: normal; }
|
|
90
|
+
.mt-liveact { padding: 0 14px 12px 58px; }
|
|
91
|
+
}
|
|
92
|
+
@media (prefers-reduced-motion: reduce) { .mt-livedot.on { animation: none; } }
|
|
93
|
+
|
|
94
|
+
/* Inline confirmation (COSConfirm: never a sheet). */
|
|
95
|
+
.mt-confirm { border: 1px solid var(--panel-border); background: var(--panel); border-radius: 12px; padding: 12px 14px; margin: 4px 0; }
|
|
96
|
+
.mt-confirm-title { font-weight: 600; font-size: 14.5px; margin: 0 0 4px; color: var(--ink); }
|
|
97
|
+
.mt-confirm-line { font-size: 13px; color: var(--sub); margin: 0 0 10px; line-height: 1.45; }
|
|
98
|
+
.mt-confirm-actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
99
|
+
|
|
100
|
+
/* ── unsaved recordings ───────────────────────────────── */
|
|
101
|
+
.mt-orphans { margin: 0 calc(-1 * var(--gutter)); padding: 10px var(--gutter) 12px; background: color-mix(in srgb, var(--amber) 8%, transparent); border-bottom: 1px solid var(--hairline); }
|
|
102
|
+
.mt-orphans-title { font-weight: 600; font-size: 13.5px; color: var(--amber); margin: 0 0 2px; }
|
|
103
|
+
.mt-orphans-line { font-size: 13px; color: var(--sub); margin: 0 0 8px; }
|
|
104
|
+
.mt-orow { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 4px 0; }
|
|
105
|
+
.mt-olabel { font-size: 13px; color: var(--ink); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
106
|
+
.mt-olabel.dim { color: var(--tertiary); }
|
|
107
|
+
@media (max-width: 599px) {
|
|
108
|
+
.mt-olabel { white-space: normal; overflow-wrap: anywhere; }
|
|
109
|
+
.mt-orow { align-items: flex-start; }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* ── detail ───────────────────────────────────────────── */
|
|
113
|
+
.mt-dhead { padding: 14px 0 10px; }
|
|
114
|
+
.mt-dtitle { font: 500 22px/1.2 var(--display); color: var(--ink); margin: 0; overflow-wrap: anywhere; letter-spacing: -0.005em; }
|
|
115
|
+
.mt-dsub { margin: 5px 0 0; font-size: 13px; color: var(--sub); }
|
|
116
|
+
.mt-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 10px 0; border-top: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline); }
|
|
117
|
+
.mt-bar-gap { flex: 1; }
|
|
118
|
+
.mt-bar .cx-pill { margin-left: 2px; }
|
|
119
|
+
.cx-btn.featured { border-color: var(--amber); color: var(--amber); }
|
|
120
|
+
.mt-copynote { font-size: 12.5px; color: var(--sub); margin: 6px 2px 0; }
|
|
121
|
+
.mt-menu { position: relative; min-width: 0; max-width: 100%; }
|
|
122
|
+
.mt-menu > summary { list-style: none; cursor: pointer; }
|
|
123
|
+
.mt-menu > summary::-webkit-details-marker { display: none; }
|
|
124
|
+
.mt-menu-list { display: grid; margin-top: 6px; border: 1px solid var(--panel-border); border-radius: 10px; background: var(--panel); overflow: hidden; }
|
|
125
|
+
.mt-menu-list button { text-align: left; padding: 9px 12px; font: 400 12px/1.3 var(--mono); color: var(--ink); min-height: 36px; white-space: normal; overflow-wrap: anywhere; min-width: 0; }
|
|
126
|
+
.mt-menu-list button + button { border-top: 1px solid var(--hairline); }
|
|
127
|
+
.mt-dbody { display: grid; gap: 14px; padding-top: 14px; }
|
|
128
|
+
.mt-note-line { font-size: 13px; color: var(--sub); margin: 0; }
|
|
129
|
+
.mt-doc { min-width: 0; max-width: 760px; }
|
|
130
|
+
.mt-none { color: var(--sub); font-size: 14.5px; margin: 18px 0 12px; }
|
|
131
|
+
.mt-section + .mt-section { margin-top: 18px; }
|
|
132
|
+
.mt-label { font: 600 10px/1 var(--mono); letter-spacing: 0.08em; color: var(--sub); margin-bottom: 8px; }
|
|
133
|
+
.mt-tlabel { font-weight: 600; font-size: 13px; margin-bottom: 10px; }
|
|
134
|
+
/* Two columns from 860 px, so an unfolded phone (iPhone Duo, about 1000 px) reads like the Mac: text left, Ask right. */
|
|
135
|
+
@media (min-width: 860px) {
|
|
136
|
+
.mt-detail .mt-dbody { grid-template-columns: minmax(0, 1fr) minmax(280px, min(380px, 38%)); align-items: start; column-gap: 24px; }
|
|
137
|
+
.mt-detail .mt-dbody > .mt-note-line, .mt-detail .mt-dbody > .mt-side-panel { grid-column: 2; }
|
|
138
|
+
.mt-detail .mt-dbody > .mt-doc { grid-column: 1; grid-row: 1 / span 20; }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* ── Markdown body (COSMarkdownView) ──────────────────── */
|
|
142
|
+
.mt-md { font-size: 15px; line-height: 1.55; color: var(--ink); overflow-wrap: anywhere; }
|
|
143
|
+
.mt-md p { margin: 0 0 0.75em; }
|
|
144
|
+
.mt-md p:last-child { margin-bottom: 0; }
|
|
145
|
+
.mt-md a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
|
|
146
|
+
.mt-md code { font: 400 13px/1.4 var(--mono); background: var(--qopen); padding: 0 4px; border-radius: 4px; }
|
|
147
|
+
.mt-md strong { font-weight: 600; }
|
|
148
|
+
.md-h { color: var(--ink); }
|
|
149
|
+
.md-h1 { font: 500 20px/1.25 var(--display); margin: 8px 0 10px; }
|
|
150
|
+
.md-h2 { font: 500 18px/1.25 var(--display); margin: 22px 0 10px; padding-bottom: 6px; border-bottom: 1px solid var(--hairline); }
|
|
151
|
+
.md-h3 { font: 600 14px/1.3 var(--body); color: var(--sub); margin: 16px 0 6px; }
|
|
152
|
+
.md-h4 { font: 600 13.5px/1.3 var(--body); margin: 12px 0 4px; }
|
|
153
|
+
.mt-md > .md-h:first-child { margin-top: 0; }
|
|
154
|
+
.md-list { margin: 0 0 0.85em; padding-left: 1.35em; }
|
|
155
|
+
.md-list li { margin: 0.2em 0; }
|
|
156
|
+
.md-list .md-list { margin: 0.2em 0; }
|
|
157
|
+
.md-list li.md-task { list-style: none; margin-left: -1.1em; }
|
|
158
|
+
.md-check { display: inline-block; width: 12px; height: 12px; border: 1.5px solid var(--sub); border-radius: 3px; margin-right: 7px; vertical-align: -1px; }
|
|
159
|
+
.md-check.on { background: var(--green); border-color: var(--green); }
|
|
160
|
+
.md-table-wrap { overflow-x: auto; margin: 0 0 1em; -webkit-overflow-scrolling: touch; }
|
|
161
|
+
.md-table { border-collapse: collapse; font-size: 14px; border: 1px solid var(--panel-border); border-radius: 6px; min-width: 60%; }
|
|
162
|
+
.md-table th, .md-table td { padding: 6px 12px; text-align: left; vertical-align: top; border-bottom: 1px solid var(--hairline); }
|
|
163
|
+
.md-table th { font-weight: 600; background: var(--qopen); }
|
|
164
|
+
.md-table tr:last-child td { border-bottom: 0; }
|
|
165
|
+
.md-table .md-key { color: var(--sub); white-space: nowrap; }
|
|
166
|
+
.md-code { overflow-x: auto; font: 400 12.5px/1.45 var(--mono); background: var(--qopen); border: 1px solid var(--panel-border); padding: 10px; border-radius: 8px; margin: 0 0 1em; }
|
|
167
|
+
.md-quote { margin: 0 0 1em; padding-left: 12px; border-left: 2px solid color-mix(in srgb, var(--gold) 70%, transparent); color: var(--sub); }
|
|
168
|
+
.md-hr { border: 0; border-top: 1px solid var(--hairline); margin: 18px 0; }
|
|
169
|
+
.md-details { border: 1px solid var(--panel-border); border-radius: 9px; background: var(--panel); margin: 0 0 1em; }
|
|
170
|
+
.md-details > summary { cursor: pointer; padding: 10px 12px; font-weight: 600; font-size: 14px; display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline; min-height: 40px; }
|
|
171
|
+
.md-note { font-weight: 400; font-size: 12px; color: var(--sub); }
|
|
172
|
+
.md-details-body { padding: 4px 14px 14px; }
|
|
173
|
+
.md-speakers { display: grid; gap: 22px; margin: 6px 0 1em; }
|
|
174
|
+
.md-who { font-weight: 600; font-size: 12.5px; color: var(--accent); margin-bottom: 6px; }
|
|
175
|
+
.md-who.unnamed { color: var(--sub); }
|
|
176
|
+
.md-turn p { margin: 0 0 0.6em; line-height: 1.6; }
|
|
177
|
+
.md-turn p:last-child { margin-bottom: 0; }
|
|
178
|
+
.mt-cite { font: 600 11px/1 var(--mono); color: var(--accent); }
|
|
179
|
+
|
|
180
|
+
/* ── side panel: Meeting Ask and Cues ─────────────────── */
|
|
181
|
+
.mt-side-panel { min-width: 0; }
|
|
182
|
+
.mt-panel { background: var(--panel); border: 1px solid var(--panel-border); border-radius: 14px; padding: 12px 14px 14px; }
|
|
183
|
+
.mt-pswitch { margin: 0 0 8px; }
|
|
184
|
+
.mt-ask-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
|
185
|
+
.mt-ask-title { font: 500 19px/1.2 var(--display); margin: 0; color: var(--ink); }
|
|
186
|
+
.mt-iconbtn { width: 36px; height: 36px; display: grid; place-items: center; border-radius: 9px; color: var(--accent); }
|
|
187
|
+
.mt-iconbtn .icon { width: 16px; height: 16px; }
|
|
188
|
+
.mt-iconbtn:hover { background: var(--qopen); }
|
|
189
|
+
.mt-ask-line { font-size: 13px; color: var(--sub); margin: 2px 0 8px; }
|
|
190
|
+
.mt-ask-sources { font-size: 13px; color: var(--sub); margin: 0 0 8px; }
|
|
191
|
+
.mt-ask-sources > summary { cursor: pointer; color: var(--ink); min-height: 32px; display: flex; align-items: center; }
|
|
192
|
+
.mt-ask-sources p { margin: 4px 0 0; line-height: 1.45; }
|
|
193
|
+
.mt-ask-acts { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0; }
|
|
194
|
+
.mt-history { display: grid; gap: 12px; margin: 6px 0 10px; }
|
|
195
|
+
.mt-answer { background: var(--qopen); border-radius: 9px; padding: 10px 12px; font-size: 14px; }
|
|
196
|
+
.mt-q { font-weight: 600; font-size: 13.5px; margin: 0 0 6px; overflow-wrap: anywhere; }
|
|
197
|
+
.mt-working { font-size: 13px; color: var(--sub); margin: 0; }
|
|
198
|
+
.mt-arows { display: grid; gap: 8px; }
|
|
199
|
+
.mt-arow p { margin: 0; line-height: 1.5; overflow-wrap: anywhere; }
|
|
200
|
+
.mt-answer .mt-md { font-size: 14px; }
|
|
201
|
+
.mt-source { font-size: 12.5px; margin-top: 6px; }
|
|
202
|
+
.mt-source > summary { cursor: pointer; color: var(--sub); min-height: 28px; display: flex; align-items: center; overflow-wrap: anywhere; }
|
|
203
|
+
.mt-source-body p { margin: 4px 0 0; white-space: pre-wrap; line-height: 1.45; }
|
|
204
|
+
.mt-source-body a { color: var(--accent); }
|
|
205
|
+
.mt-afoot { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 12px; margin-top: 8px; font-size: 12px; }
|
|
206
|
+
.mt-afoot .mt-muted { font-size: 12px; }
|
|
207
|
+
.mt-afoot-acts { display: flex; flex-wrap: wrap; gap: 4px 12px; }
|
|
208
|
+
.mt-link { color: var(--accent); font: 600 13px/1 var(--body); min-height: 32px; padding: 0 2px; }
|
|
209
|
+
.mt-link:disabled { opacity: 0.45; }
|
|
210
|
+
.mt-follow { margin-top: 10px; }
|
|
211
|
+
.mt-follow .field { min-height: 90px; font-size: 15px; margin-bottom: 8px; }
|
|
212
|
+
.mt-composer { display: flex; align-items: flex-end; gap: 8px; margin-top: 8px; }
|
|
213
|
+
.mt-composer .field { min-height: 40px; padding: 9px 12px; font-size: 16px; line-height: 1.35; border-radius: 10px; flex: 1; min-width: 0; resize: none; }
|
|
214
|
+
.mt-composer .cx-btn { height: 40px; }
|
|
215
|
+
.mt-ask-more { display: flex; flex-wrap: wrap; gap: 4px 16px; margin-top: 6px; }
|
|
216
|
+
.mt-model { margin-top: 8px; font-size: 12px; }
|
|
217
|
+
.mt-model-pick { display: flex; align-items: center; gap: 10px; margin-top: 12px; }
|
|
218
|
+
.mt-model-select { flex: 1; min-width: 0; max-width: 320px; height: 36px; font-size: 14px; }
|
|
219
|
+
.mt-cue { border-top: 1px solid var(--hairline); padding: 10px 0; }
|
|
220
|
+
.mt-cue:first-of-type { border-top: 0; }
|
|
221
|
+
.mt-cue p { margin: 0 0 4px; }
|
|
222
|
+
.mt-cue-line { font: 600 11px/1.3 var(--mono); color: var(--tertiary); }
|
|
223
|
+
.mt-cue-head { font-weight: 600; font-size: 14px; }
|
|
224
|
+
.mt-cue-detail { font-size: 14px; line-height: 1.5; }
|
|
225
|
+
|
|
226
|
+
/* ── live transcript view ─────────────────────────────── */
|
|
227
|
+
.mt-lhead { display: grid; gap: 4px; }
|
|
228
|
+
.mt-ltitle { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
229
|
+
.mt-lstatus { font-weight: 500; font-size: 13.5px; color: var(--ink); margin: 4px 0 0; }
|
|
230
|
+
.mt-lsub { font-size: 13px; color: var(--tertiary); margin: 0; }
|
|
231
|
+
.mt-handoff { font-size: 13px; margin: 2px 0 0; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
|
|
232
|
+
.mt-lhead .mt-bar { border-bottom: 0; margin-top: 6px; }
|
|
233
|
+
/* Control's LiveMeetingFilesRow: one collapsed "Files · N" row; open, the names and the note. */
|
|
234
|
+
.mt-files { border: 1px solid var(--hairline); border-radius: 9px; margin: 6px 0 4px; max-width: 520px; min-width: 0; }
|
|
235
|
+
.mt-files-head { display: flex; align-items: center; gap: 8px; width: 100%; min-height: 36px; padding: 0 12px; font-size: 13px; font-weight: 600; color: var(--ink); text-align: left; }
|
|
236
|
+
.mt-files-head > span { flex: 1; min-width: 0; }
|
|
237
|
+
.mt-files-head .icon { width: 14px; height: 14px; color: var(--sub); flex: none; }
|
|
238
|
+
.mt-files-head .icon:last-child { transform: rotate(90deg); transition: transform 0.15s ease; }
|
|
239
|
+
.mt-files-head[aria-expanded="true"] .icon:last-child { transform: rotate(-90deg); }
|
|
240
|
+
.mt-files-list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--hairline); }
|
|
241
|
+
.mt-files-list li { display: grid; gap: 2px; padding: 8px 12px; border-bottom: 1px solid var(--hairline); min-width: 0; }
|
|
242
|
+
.mt-files-name { font-size: 13px; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
243
|
+
.mt-files-note { padding: 8px 12px 10px; font-size: 12px; }
|
|
244
|
+
@media (prefers-reduced-motion: reduce) { .mt-files-head .icon:last-child { transition: none; } }
|
|
245
|
+
.mt-lgrid { display: grid; gap: 16px; }
|
|
246
|
+
.mt-lmain { min-width: 0; }
|
|
247
|
+
.mt-turns { display: grid; gap: 22px; padding: 16px 0 14px; border-top: 1px solid var(--hairline); max-width: 700px; }
|
|
248
|
+
.mt-lwho { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; }
|
|
249
|
+
.mt-lname { font-weight: 600; font-size: 12.5px; color: var(--sub); }
|
|
250
|
+
.mt-lstamp { font: 400 11px/1 var(--mono); color: var(--tertiary); }
|
|
251
|
+
.mt-lturn p { margin: 0 0 0.6em; font-size: 15px; line-height: 1.6; color: var(--ink); overflow-wrap: anywhere; }
|
|
252
|
+
.mt-lturn p:last-child { margin-bottom: 0; }
|
|
253
|
+
.mt-lempty { color: var(--sub); text-align: center; font-size: 14px; padding: 26px 8px; margin: 0; }
|
|
254
|
+
@media (min-width: 860px) {
|
|
255
|
+
.mt-lgrid { grid-template-columns: minmax(0, 1fr) minmax(280px, min(380px, 38%)); align-items: start; column-gap: 24px; }
|
|
256
|
+
.mt-lgrid > .mt-side-panel { position: sticky; top: calc(62px + var(--safe-top, 0px)); max-height: calc(100vh - 80px - var(--safe-top, 0px)); overflow: auto; }
|
|
257
|
+
}
|