symbiot 0.47.1 → 0.49.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.
- package/CHANGELOG.md +14 -0
- package/adapt.mjs +23 -2
- package/fonts/Geist-OFL.txt +92 -0
- package/fonts/Geist-Variable.woff2 +0 -0
- package/package.json +3 -2
- package/server.mjs +6 -0
- package/ui.mjs +261 -66
- package/watch.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
What each Symbiot release brought, newest first. When Approve bumps the version, it writes that release here from the tasks it approved, worded as release notes by your connected AI (or in the tasks' own words without one). The app shows the releases newer than yours under "What's new" when it offers an update, and once more after you update.
|
|
4
4
|
|
|
5
|
+
## 0.49.0
|
|
6
|
+
|
|
7
|
+
- **Home by P.A.R.A.** The liquid groups everything Symbiot holds four ways: **Projects** (work with an end: Tasks, Agents, Todo, and what's asking you), **Areas** (what you keep up with: the Dashboard, your feeds, Week, Standup), **Resources** (to look things up in: Map, Drift, Settings) and the **Archive** (what's done). Each group has its own part of the screen and its name on the liquid; tap a name to open it. Projects move the most, the Archive is still, and Agents has spheres orbiting it while one works.
|
|
8
|
+
- **The Dashboard as a stream.** Time runs left to right, one current per feed, "now" on the right. Each message is a bead placed when it arrived, amber when it's waiting for your reply. Point at one to read it and Draft a reply or Open it; each feed's Seen, Talk and Check now sit under its name. The cards are still there under "All of it as a list".
|
|
9
|
+
- **Amber means it needs you, everywhere.** The liquid can tint a single droplet, so what's asking you glows amber at home and in the stream, in every look.
|
|
10
|
+
|
|
11
|
+
## 0.48.0
|
|
12
|
+
|
|
13
|
+
- **Three looks, one switch.** Ferrofluid (glossy black liquid metal, lit like a studio), Glass (clear droplets that bend the colours behind them) and Pearl (silver lit like a product photo). Switch at the top left, next to the name; Ferrofluid is first. Panels follow the look.
|
|
14
|
+
- **Geist, in the package.** The app's type is Geist, shipped with Symbiot, so it looks the same offline.
|
|
15
|
+
- **Names without pills.** Each droplet's name sits under it as plain type; tap the droplet or its name and it opens into a small card with Open.
|
|
16
|
+
- **A chat without boxes.** Symbiot's replies are plain text, yours sit in a soft capsule, and the steps fold into one line you can open. No more sideways scrolling.
|
|
17
|
+
- **Nearest neighbours.** Parts you go between sit together (k-nearest neighbours on how you move between them), so home grows into clusters with gaps between, not a ring. Before you've used it, the work (Tasks, Agents, Todo), your time (Week, Standup) and your repos (Dashboard, Map, Drift) start together. In the Tasks scene, waiting tasks gather round the agent working in their repo.
|
|
18
|
+
|
|
5
19
|
## 0.47.1
|
|
6
20
|
|
|
7
21
|
- **Sharp, not blurry.** The liquid renders at your screen's full pixel density (it was capped at 1.5x, so 2x screens were stretched). Sphere edges are crisp at any size, each sphere casts a tight shadow below it instead of a fuzzy halo round it, and tags sit on whole pixels with almost no frosting, so their text is sharp.
|
package/adapt.mjs
CHANGED
|
@@ -204,12 +204,33 @@ function userStyleLine(file = MIND_FILE) {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
// ---- the app's view ------------------------------------------------------------------
|
|
207
|
+
// Nearest neighbours: two parts are near when you go from one to the other. Their
|
|
208
|
+
// affinity is the symmetric next-step probability, Dirichlet-smoothed as in predict,
|
|
209
|
+
// plus a small prior (KIN) for parts that belong together before you've used them,
|
|
210
|
+
// by P.A.R.A.: Projects (tasks, agents, todo: work with an end), Areas (dashboard,
|
|
211
|
+
// week, standup: what you keep up with) and Resources (map, drift, settings: what
|
|
212
|
+
// you look things up in). The Archive is its own droplet in the liquid. Each part keeps its k nearest (k-NN), counting only links above
|
|
213
|
+
// chance (1/K), and the liquid sets it beside them, so the layout has clusters
|
|
214
|
+
// and gaps, structure without looking ruled.
|
|
215
|
+
const KIN_GROUPS = [["tasks", "agents", "todo"], ["board", "week", "standup"], ["map", "drift", "settings"]];
|
|
216
|
+
const KIN = KIN_GROUPS.flatMap((g) => g.flatMap((a, i) => g.slice(i + 1).map((b) => [a, b])));
|
|
217
|
+
const KIN_W = 0.08;
|
|
218
|
+
function affinity(d, a, b, ids = SHAPES) {
|
|
219
|
+
const K = ids.length, p = (x, y) => { const row = d.trans[x] || {}, N = ids.reduce((s, id) => s + (row[id] || 0), 0); return ((row[y] || 0) + ALPHA) / (N + ALPHA * K); };
|
|
220
|
+
return (p(a, b) + p(b, a)) / 2 + (KIN.some(([x, y]) => (x === a && y === b) || (x === b && y === a)) ? KIN_W : 0);
|
|
221
|
+
}
|
|
222
|
+
function neighbours(d, ids = SHAPES, k = 2) {
|
|
223
|
+
const out = {};
|
|
224
|
+
const chance = 1 / ids.length + 0.02;
|
|
225
|
+
for (const a of ids) out[a] = ids.filter((b) => b !== a).map((b) => [b, affinity(d, a, b, ids)]).filter(([, w]) => w > chance).sort((x, y) => y[1] - x[1] || (x[0] < y[0] ? -1 : 1)).slice(0, k).map(([id, w]) => ({ id, w: +w.toFixed(3) }));
|
|
226
|
+
return out;
|
|
227
|
+
}
|
|
207
228
|
function adaptState({ from = "", commit = false, touch, now = Date.now() } = {}) {
|
|
208
229
|
const d = loadAdapt(), m = modes(d, now);
|
|
209
230
|
const layout = layoutFor(d, SHAPES, { from, now, commit, touch: touch ?? m.touch });
|
|
210
231
|
if (commit || layout.changed) saveAdapt(d);
|
|
211
232
|
let style = null; try { const md = JSON.parse(readFileSync(MIND_FILE, "utf8")); style = styleOf((md.log || []).filter((l) => l.role === "user").map((l) => l.text)); } catch {}
|
|
212
|
-
return { layout, modes: m, style, styleLine: styleLine(style), events: d.events || 0 };
|
|
233
|
+
return { layout, near: neighbours(d), modes: m, style, styleLine: styleLine(style), events: d.events || 0 };
|
|
213
234
|
}
|
|
214
235
|
function noteUse(body = {}) {
|
|
215
236
|
const shape = String(body.shape || ""), via = String(body.via || "click"), from = String(body.from || "");
|
|
@@ -219,4 +240,4 @@ function noteUse(body = {}) {
|
|
|
219
240
|
return { ok: true, events: d.events };
|
|
220
241
|
}
|
|
221
242
|
|
|
222
|
-
export { ADAPT_FILE, SHAPES, HALF_LIFE, HYSTERESIS, decayed, bump, recordUse, predict, entropy, adaptivity, fittsTime, allocate, hick, expectedTime, layoutFor, modes, styleOf, styleLine, userStyleLine, adaptState, noteUse, loadAdapt, saveAdapt };
|
|
243
|
+
export { ADAPT_FILE, SHAPES, HALF_LIFE, HYSTERESIS, decayed, bump, recordUse, predict, entropy, adaptivity, fittsTime, allocate, hick, expectedTime, layoutFor, modes, styleOf, styleLine, userStyleLine, adaptState, noteUse, loadAdapt, saveAdapt, affinity, neighbours, KIN };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Copyright (c) 2023 Vercel, in collaboration with basement.studio
|
|
2
|
+
|
|
3
|
+
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
|
4
|
+
This license is copied below, and is also available with a FAQ at:
|
|
5
|
+
http://scripts.sil.org/OFL
|
|
6
|
+
|
|
7
|
+
-----------------------------------------------------------
|
|
8
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
9
|
+
-----------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
PREAMBLE
|
|
12
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
13
|
+
development of collaborative font projects, to support the font creation
|
|
14
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
15
|
+
open framework in which fonts may be shared and improved in partnership
|
|
16
|
+
with others.
|
|
17
|
+
|
|
18
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
19
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
20
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
21
|
+
redistributed and/or sold with any software provided that any reserved
|
|
22
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
23
|
+
however, cannot be released under any other type of license. The
|
|
24
|
+
requirement for fonts to remain under this license does not apply
|
|
25
|
+
to any document created using the fonts or their derivatives.
|
|
26
|
+
|
|
27
|
+
DEFINITIONS
|
|
28
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
29
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
30
|
+
include source files, build scripts and documentation.
|
|
31
|
+
|
|
32
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
33
|
+
copyright statement(s).
|
|
34
|
+
|
|
35
|
+
"Original Version" refers to the collection of Font Software components as
|
|
36
|
+
distributed by the Copyright Holder(s).
|
|
37
|
+
|
|
38
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
39
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
40
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
41
|
+
new environment.
|
|
42
|
+
|
|
43
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
44
|
+
writer or other person who contributed to the Font Software.
|
|
45
|
+
|
|
46
|
+
PERMISSION AND CONDITIONS
|
|
47
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
48
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
49
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
50
|
+
Software, subject to the following conditions:
|
|
51
|
+
|
|
52
|
+
1) Neither the Font Software nor any of its individual components,
|
|
53
|
+
in Original or Modified Versions, may be sold by itself.
|
|
54
|
+
|
|
55
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
56
|
+
redistributed and/or sold with any software, provided that each copy
|
|
57
|
+
contains the above copyright notice and this license. These can be
|
|
58
|
+
included either as stand-alone text files, human-readable headers or
|
|
59
|
+
in the appropriate machine-readable metadata fields within text or
|
|
60
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
61
|
+
|
|
62
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
63
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
64
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
65
|
+
presented to the users.
|
|
66
|
+
|
|
67
|
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
68
|
+
Software shall not be used to promote, endorse or advertise any
|
|
69
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
70
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
71
|
+
permission.
|
|
72
|
+
|
|
73
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
74
|
+
must be distributed entirely under this license, and must not be
|
|
75
|
+
distributed under any other license. The requirement for fonts to
|
|
76
|
+
remain under this license does not apply to any document created
|
|
77
|
+
using the Font Software.
|
|
78
|
+
|
|
79
|
+
TERMINATION
|
|
80
|
+
This license becomes null and void if any of the above conditions are
|
|
81
|
+
not met.
|
|
82
|
+
|
|
83
|
+
DISCLAIMER
|
|
84
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
85
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
86
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
87
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
88
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
89
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
90
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
91
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
92
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "symbiot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.0",
|
|
4
4
|
"description": "Your week, written from your real work. Reads your local git and writes it up with the AI of your choice — Claude, OpenAI, Gemini, or a local model.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"desktop.mjs",
|
|
34
34
|
"README.md",
|
|
35
35
|
"CHANGELOG.md",
|
|
36
|
-
"LICENSE"
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"fonts"
|
|
37
38
|
],
|
|
38
39
|
"engines": {
|
|
39
40
|
"node": ">=18"
|
package/server.mjs
CHANGED
|
@@ -182,6 +182,12 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
|
|
|
182
182
|
const server = createServer(async (req, res) => {
|
|
183
183
|
const u = new URL(req.url, "http://127.0.0.1");
|
|
184
184
|
if (req.method === "GET" && u.pathname === "/") { res.writeHead(200, { "content-type": "text/html; charset=utf-8" }); res.end(EMBEDDED_UI); return; }
|
|
185
|
+
// the app's typeface ships in the package (fonts/), so it's there offline
|
|
186
|
+
if (req.method === "GET" && u.pathname === "/fonts/Geist-Variable.woff2") {
|
|
187
|
+
try { const b = readFileSync(fileURLToPath(new URL("./fonts/Geist-Variable.woff2", import.meta.url))); res.writeHead(200, { "content-type": "font/woff2", "cache-control": "public, max-age=31536000, immutable" }); res.end(b); }
|
|
188
|
+
catch { res.writeHead(404); res.end(); }
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
185
191
|
if (u.pathname.startsWith("/api/")) {
|
|
186
192
|
const tok = req.headers["x-symbiot-token"] || u.searchParams.get("t");
|
|
187
193
|
if (tok !== TOKEN) { res.writeHead(403); res.end("forbidden"); return; }
|
package/ui.mjs
CHANGED
|
@@ -7,33 +7,39 @@
|
|
|
7
7
|
export const EMBEDDED_UI = `<!doctype html><html><head><meta charset="utf8">
|
|
8
8
|
<meta name="viewport" content="width=device-width,initial-scale=1"><title>Symbiot</title>
|
|
9
9
|
<style>
|
|
10
|
-
|
|
10
|
+
@font-face{font-family:'Geist';src:url('/fonts/Geist-Variable.woff2') format('woff2');font-weight:100 900;font-style:normal;font-display:swap}
|
|
11
|
+
:root{--ink:#0E1A1F;--ink2:#15262C;--ink3:#1D333A;--line:#24404A;--bone:#F4F1EA;--text:#B7C9C4;--faint:#7E9690;--green:#3DDC97;--amber:#F2A541;--green-dim:#16322D;--sans:'Geist',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif}
|
|
11
12
|
/* ---- the liquid: the app's home (adapt.mjs shapes it, home.mjs fills it) ---- */
|
|
12
13
|
/* Your colours, from the system, live: light/dark, contrast, transparency, forced colours. */
|
|
13
14
|
body.lq-light{--ink:#F4F6F9;--ink2:#FFFFFF;--ink3:#EDF0F4;--line:#D3D9E2;--bone:#0F1720;--text:#2B3644;--faint:#5C6878;--green:#0B7A55;--amber:#9A5200;--green-dim:#DDF2E9}
|
|
15
|
+
body.lq-look-ferro{--ink:#0B0C0E;--ink2:#131518;--ink3:#1B1E22;--line:#2A2E34;--bone:#F3F0EA;--text:#C9CDD3;--faint:#8A919B;--green-dim:#17241F}
|
|
14
16
|
body.lq-contrast{--line:#9FB3BB;--faint:#C9D6D2}
|
|
15
17
|
body.lq-light.lq-contrast{--line:#4A5565;--faint:#2B3644;--text:#10151C}
|
|
16
18
|
#liquid{display:none}
|
|
17
|
-
body.lq-liquid #liquid{display:block;position:fixed;inset:0;z-index:0;overflow:hidden;background:#
|
|
18
|
-
body.lq-liquid.lq-light #liquid{background:#
|
|
19
|
+
body.lq-liquid #liquid{display:block;position:fixed;inset:0;z-index:0;overflow:hidden;background:#08090B}
|
|
20
|
+
body.lq-liquid.lq-light #liquid{background:#EBEEF0}
|
|
21
|
+
body.lq-liquid.lq-look-pearl #liquid{background:#EDECE9}
|
|
19
22
|
#lq{position:absolute;inset:0;width:100%;height:100%;display:block}
|
|
20
23
|
body.lq-forced #lq{display:none}
|
|
21
24
|
#lqdrops{position:absolute;inset:0;pointer-events:none}
|
|
22
|
-
.lqd{position:absolute;left:0;top:0;display:flex;flex-direction:column;align-items:center;border-radius:
|
|
23
|
-
.lqd .lt{
|
|
25
|
+
.lqd{position:absolute;left:0;top:0;display:flex;flex-direction:column;align-items:center;border-radius:14px;border:1px solid transparent;background:transparent;color:#ECE9E4;font:600 13.5px/1.25 var(--sans);letter-spacing:-.005em;pointer-events:auto;transition:opacity .5s ease,background-color .2s ease,border-color .2s ease;will-change:transform;text-shadow:0 1px 10px rgba(0,0,0,.6)}
|
|
26
|
+
.lqd .lt .lm{display:block;font-weight:500;font-size:11.5px;color:#8A919B;margin-top:1px}
|
|
27
|
+
.lqd .lt{all:unset;box-sizing:border-box;display:flex;align-items:center;justify-content:center;gap:7px;min-height:36px;padding:6px 10px;cursor:pointer;text-align:center;max-width:220px}
|
|
28
|
+
.lqd.lq-you .lt::before{content:'';flex:none;width:7px;height:7px;border-radius:50%;background:#F2A541;box-shadow:0 0 0 3px rgba(242,165,65,.2)}
|
|
24
29
|
.lqd .lt span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
25
30
|
.lqd .lgo{display:none;margin:4px 10px 10px;min-height:38px;padding:0 16px;border-radius:999px;border:1px solid #FFFFFF;background:linear-gradient(180deg,#FFFFFF 0%,#D2D8E1 44%,#8E97A6 56%,#E6EAF0 100%);color:#06080B;font:700 12.5px var(--sans);cursor:pointer}
|
|
26
|
-
.lqd.open{z-index:5;border-radius:20px}.lqd.open .lt span{white-space:normal}.lqd.open .lt{max-width:280px}.lqd.open .lgo{display:block}
|
|
31
|
+
.lqd.open{z-index:5;border-radius:18px;background:rgba(16,18,21,.92);border-color:rgba(255,255,255,.12);box-shadow:0 24px 50px -20px rgba(0,0,0,.8);text-shadow:none;backdrop-filter:blur(12px)}.lqd.open .lt span{white-space:normal}.lqd.open .lt{max-width:280px}.lqd.open .lgo{display:block}
|
|
27
32
|
.lqd .lt:focus-visible,.lqd .lgo:focus-visible{outline:2px solid currentColor;outline-offset:2px;border-radius:999px}
|
|
28
33
|
.lqmb{min-height:44px;padding:8px 14px;border-radius:999px;border:1px solid rgba(220,228,240,.35);background:rgba(4,5,7,.72);backdrop-filter:blur(10px);color:#EEF2F8;font:600 13.5px var(--sans);cursor:pointer}
|
|
29
34
|
body.lq-light .lqmb{background:rgba(255,255,255,.86);color:#10151C;border-color:rgba(20,30,45,.25)}
|
|
30
|
-
.lqd small{display:none;font-weight:500;font-size:
|
|
31
|
-
.lqd.
|
|
32
|
-
.lqd:hover{border-color:rgba(255,255,255,.95)}
|
|
35
|
+
.lqd small{display:none;font-weight:500;font-size:12.5px;line-height:1.4;color:#A1A8B1;max-width:260px;padding:0 15px 2px;text-align:center}.lqd.open small{display:block}
|
|
36
|
+
.lqd:hover .lt span{text-decoration:underline;text-decoration-color:rgba(255,255,255,.35);text-underline-offset:4px}
|
|
33
37
|
.lqd:focus-visible,#lqcore:focus-visible,#lqask:focus-visible{outline:2px solid currentColor;outline-offset:3px}
|
|
34
|
-
body.lq-light .lqd{
|
|
38
|
+
body.lq-light .lqd{color:#151A21;text-shadow:0 1px 8px rgba(255,255,255,.7)}
|
|
39
|
+
body.lq-light .lqd .lt .lm{color:#5A6470}
|
|
40
|
+
body.lq-light .lqd.open{background:rgba(255,255,255,.94);border-color:rgba(20,30,45,.1);box-shadow:0 24px 50px -24px rgba(30,45,60,.45)}
|
|
35
41
|
body.lq-light .lqd small{color:#4A5565}
|
|
36
|
-
body.lq-light .lqd.
|
|
42
|
+
body.lq-light .lqd:hover .lt span{text-decoration-color:rgba(21,26,33,.35)}
|
|
37
43
|
body.lq-contrast .lqd{background:#000;color:#FFF;border:2px solid #FFF}
|
|
38
44
|
body.lq-contrast .lqd small{color:#FFF}
|
|
39
45
|
body.lq-light.lq-contrast .lqd{background:#FFF;color:#000;border:2px solid #000}
|
|
@@ -42,21 +48,39 @@ body.lq-solid .lqd,body.lq-solid .lqglass{backdrop-filter:none;background:#05070
|
|
|
42
48
|
body.lq-light.lq-solid .lqd,body.lq-light.lq-solid .lqglass{background:#FFFFFF}
|
|
43
49
|
body.lq-touch .lqd .lt{min-height:48px}
|
|
44
50
|
body.lq-keys .lqd:focus{outline:2px solid currentColor;outline-offset:3px}
|
|
45
|
-
#lqcore{position:absolute;left:0;top:0;
|
|
46
|
-
body.lq-light #lqcore{color:#
|
|
51
|
+
#lqcore{position:absolute;left:0;top:0;padding:4px 8px;border:0;border-radius:12px;background:transparent;color:#F3F0EA;font:600 16px/1.25 var(--sans);letter-spacing:-.01em;text-align:center;cursor:pointer;transition:opacity .6s ease;white-space:normal;text-wrap:balance}
|
|
52
|
+
body.lq-light #lqcore{color:#151A21}
|
|
47
53
|
.lqglass{background:rgba(4,5,8,.78);border:1px solid rgba(220,228,240,.32);backdrop-filter:blur(14px)}
|
|
48
54
|
body.lq-light .lqglass{background:rgba(255,255,255,.82);border-color:rgba(20,30,45,.2)}
|
|
49
|
-
#lqform{position:absolute;left:50%;bottom:20px;transform:translateX(-50%);width:min(640px,calc(100vw - 32px));display:flex;gap:8px;padding:6px;border-radius:
|
|
50
|
-
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
.
|
|
56
|
-
|
|
55
|
+
#lqform{position:absolute;left:50%;bottom:20px;transform:translateX(-50%);width:min(640px,calc(100vw - 32px));display:flex;align-items:center;gap:8px;padding:5px 5px 5px 6px;border-radius:20px;z-index:3;color:#ECE9E4}
|
|
56
|
+
#lqform.lqglass{background:rgba(255,255,255,.055);border:1px solid rgba(255,255,255,.12);backdrop-filter:blur(16px)}
|
|
57
|
+
body.lq-light #lqform{color:#151A21}
|
|
58
|
+
body.lq-light #lqform.lqglass{background:rgba(255,255,255,.62);border-color:rgba(255,255,255,.9);box-shadow:0 20px 50px -30px rgba(30,45,60,.5)}
|
|
59
|
+
#lqask{flex:1;min-width:0;min-height:44px;padding:0 12px;border:0;border-radius:14px;background:transparent;color:inherit;font:400 15.5px var(--sans);outline:none}
|
|
60
|
+
#lqask::placeholder{color:#7A818B}
|
|
61
|
+
body.lq-light #lqask::placeholder{color:#6A7480}
|
|
62
|
+
#lqsend{flex:none;width:40px;height:40px;padding:0;display:grid;place-items:center;border-radius:50%;border:0;background:#F3F0EA;color:#0A0B0D;cursor:pointer}
|
|
63
|
+
body.lq-light #lqsend{background:#151A21;color:#FFFFFF}
|
|
64
|
+
body.lq-look-pearl #lqsend{background:linear-gradient(180deg,#FFFFFF 0%,#D9DDE2 45%,#9AA1AA 55%,#E8EAED 100%);color:#1B1E23;box-shadow:0 2px 6px rgba(0,0,0,.18),inset 0 0 0 1px rgba(0,0,0,.08)}
|
|
65
|
+
#lqtalk{position:absolute;left:50%;bottom:88px;transform:translateX(-50%);width:min(640px,calc(100vw - 32px));display:flex;flex-direction:column;gap:16px;z-index:3;max-height:46vh;overflow-x:hidden;overflow-y:auto;scrollbar-width:none;padding-top:36px;-webkit-mask-image:linear-gradient(to bottom,transparent 0,#000 40px);mask-image:linear-gradient(to bottom,transparent 0,#000 40px)}
|
|
66
|
+
#lqtalk::-webkit-scrollbar{display:none}
|
|
67
|
+
.lqmsg{align-self:flex-start;max-width:92%;padding:0;border:0;background:none;color:#ECE9E4;font-size:15px;line-height:1.55;white-space:pre-wrap;overflow-wrap:anywhere;text-shadow:0 1px 12px rgba(0,0,0,.7)}
|
|
68
|
+
.lqmsg.me{align-self:flex-end;max-width:80%;padding:8px 14px;border-radius:18px;background:rgba(255,255,255,.09);color:#C9CDD3;font-size:14.5px;text-shadow:none}
|
|
69
|
+
body.lq-light .lqmsg{color:#151A21;text-shadow:0 1px 10px rgba(255,255,255,.8)}
|
|
70
|
+
body.lq-light .lqmsg.me{background:rgba(21,26,33,.07);color:#151A21;text-shadow:none}
|
|
71
|
+
body.lq-look-pearl .lqmsg.me{background:#1B1E23;color:#F4F4F2}
|
|
57
72
|
#lqmore{position:absolute;right:16px;bottom:92px;display:flex;flex-direction:column;align-items:flex-end;gap:6px;z-index:3}
|
|
58
73
|
body.lq-liquid header{position:fixed;top:0;left:0;right:0;z-index:4;background:transparent;pointer-events:none}
|
|
59
|
-
body.lq-liquid header .status,body.lq-liquid header .ver{pointer-events:auto}
|
|
74
|
+
body.lq-liquid header .status,body.lq-liquid header .ver,body.lq-liquid #lqlook{pointer-events:auto}
|
|
75
|
+
#lqlook{display:none}
|
|
76
|
+
body.lq-liquid #lqlook{display:inline-flex;gap:2px;margin-left:10px;padding:3px;border-radius:999px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1)}
|
|
77
|
+
#lqlook button{min-height:28px;padding:0 12px;border:0;border-radius:999px;background:transparent;color:#8A919B;font:500 12.5px var(--sans);cursor:pointer}
|
|
78
|
+
#lqlook button[aria-checked='true']{background:#F3F0EA;color:#0A0B0D}
|
|
79
|
+
#lqlook button:focus-visible{outline:2px solid currentColor;outline-offset:2px}
|
|
80
|
+
body.lq-light #lqlook{background:rgba(21,26,33,.05);border-color:rgba(21,26,33,.1)}
|
|
81
|
+
body.lq-light #lqlook button{color:#5A6470}
|
|
82
|
+
body.lq-light #lqlook button[aria-checked='true']{background:#151A21;color:#FFFFFF}
|
|
83
|
+
body.lq-touch #lqlook button{min-height:40px}
|
|
60
84
|
body.lq-liquid header .brand{letter-spacing:.38em;font-weight:500;text-transform:uppercase}
|
|
61
85
|
body.lq-liquid .tabs{display:none}
|
|
62
86
|
body.lq-liquid main{display:none}
|
|
@@ -87,6 +111,14 @@ body.lq-contrast.lq-liquid .task,body.lq-contrast.lq-liquid .agent{border:2px so
|
|
|
87
111
|
|
|
88
112
|
/* the work scene: tasks and agents in the liquid, with spheres revolving round what's at work */
|
|
89
113
|
#lqorbits{position:absolute;inset:0;pointer-events:none}
|
|
114
|
+
#lqgroups{position:absolute;inset:0;pointer-events:none}
|
|
115
|
+
.lqg{position:absolute;left:0;top:0;display:flex;flex-direction:column;align-items:center;gap:1px;padding:4px 10px;border:0;border-radius:12px;background:transparent;color:#ECE9E4;font:600 17px/1.15 var(--sans);letter-spacing:-.01em;cursor:pointer;transition:opacity .5s ease;will-change:transform;text-shadow:0 1px 12px rgba(0,0,0,.6)}
|
|
116
|
+
.lqg small{font:400 12px var(--sans);color:#8A919B;letter-spacing:0}
|
|
117
|
+
.lqg:hover span{text-decoration:underline;text-underline-offset:5px;text-decoration-color:rgba(255,255,255,.3)}
|
|
118
|
+
.lqg:focus-visible{outline:2px solid currentColor;outline-offset:2px}
|
|
119
|
+
body.lq-light .lqg{color:#151A21;text-shadow:0 1px 10px rgba(255,255,255,.8)}
|
|
120
|
+
body.lq-light .lqg small{color:#5A6470}
|
|
121
|
+
body.lq-pooled #lqgroups{display:none}
|
|
90
122
|
.lqorbit{position:absolute;left:0;top:0;pointer-events:none;transition:opacity .5s ease}
|
|
91
123
|
.lqorbit span{position:absolute;inset:0;animation:lqorbit 3.4s linear infinite}
|
|
92
124
|
.lqorbit span:nth-child(2){animation-duration:4.6s;animation-delay:-1.6s}
|
|
@@ -99,7 +131,7 @@ body.lq-contrast.lq-liquid .task,body.lq-contrast.lq-liquid .agent{border:2px so
|
|
|
99
131
|
body.lq-work #lqgo.on{display:inline-flex;position:absolute;left:50%;bottom:86px;transform:translateX(-50%);z-index:4;align-items:center;gap:8px;min-height:52px;padding:0 26px;border-radius:999px;border:1px solid #FFFFFF;background:linear-gradient(180deg,#FFFFFF 0%,#D2D8E1 44%,#8E97A6 56%,#E6EAF0 100%);color:#06080B;font:700 16px var(--sans);letter-spacing:.02em;cursor:pointer;box-shadow:0 16px 40px -16px rgba(0,0,0,.75)}
|
|
100
132
|
body.lq-work #lqback{display:inline-flex;position:absolute;left:16px;top:52px;z-index:4;align-items:center;min-height:44px;padding:0 16px;border-radius:999px;border:1px solid rgba(220,228,240,.3);background:rgba(4,5,7,.6);backdrop-filter:blur(8px);color:#EEF2F8;font:600 14px var(--sans);cursor:pointer}
|
|
101
133
|
body.lq-light.lq-work #lqback{background:rgba(255,255,255,.8);color:#10151C;border-color:rgba(20,30,45,.2)}
|
|
102
|
-
body.lq-pooled #lqgo,body.lq-pooled #lqback,body.lq-pooled #lqorbits{display:none}
|
|
134
|
+
body.lq-pooled #lqgo,body.lq-pooled #lqback,body.lq-pooled #lqorbits,body.lq-work #lqgroups{display:none}
|
|
103
135
|
.lqd.lq-ready{border-color:#F2A541}
|
|
104
136
|
/* the live work of an agent: what it's doing, its to-dos, its steps, its files, its pace */
|
|
105
137
|
.wk{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(260px,100%),1fr));gap:12px;margin-top:10px}
|
|
@@ -136,8 +168,14 @@ body.lq-light .wkfile .fb i{background:linear-gradient(90deg,#6F7887,#0F1720)}
|
|
|
136
168
|
.orb{flex:none;display:inline-block;width:14px;height:14px;border-radius:50%;background:radial-gradient(circle at 35% 30%,#FFFFFF 0%,#C9CFD9 30%,#6F7887 70%,#2A2F38 100%);box-shadow:0 0 0 1px rgba(255,255,255,.25)}
|
|
137
169
|
.orb.run{animation:lqpulse 1.6s ease-in-out infinite}.orb.fail{background:radial-gradient(circle at 35% 30%,#FFE1DA 0%,#FF8A75 45%,#7A2A1C 100%)}
|
|
138
170
|
@keyframes lqpulse{0%,100%{transform:scale(1);box-shadow:0 0 0 1px rgba(255,255,255,.25)}50%{transform:scale(1.18);box-shadow:0 0 14px 2px rgba(255,255,255,.35)}}
|
|
139
|
-
.
|
|
140
|
-
.steps
|
|
171
|
+
details.steps{margin-top:6px;white-space:normal;font-size:12.5px;color:var(--faint);text-shadow:none}
|
|
172
|
+
details.steps summary{cursor:pointer;list-style:none;display:inline-flex;align-items:center;gap:6px}
|
|
173
|
+
details.steps summary::-webkit-details-marker{display:none}
|
|
174
|
+
details.steps summary::before{content:'';width:5px;height:5px;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(-45deg);transition:transform .2s ease}
|
|
175
|
+
details.steps[open] summary::before{transform:rotate(45deg)}
|
|
176
|
+
details.steps ul{margin:6px 0 0;padding:0 0 0 12px;list-style:none;border-left:1px solid var(--line);display:flex;flex-direction:column;gap:3px}
|
|
177
|
+
.lqmsg details.steps{color:#8A919B}
|
|
178
|
+
body.lq-light .lqmsg details.steps{color:#5A6470}
|
|
141
179
|
.thinking{background:linear-gradient(90deg,#8C96A6 0%,#FFFFFF 40%,#8C96A6 60%,#8C96A6 100%);background-size:220% 100%;-webkit-background-clip:text;background-clip:text;color:transparent !important;animation:lqshim 2.2s linear infinite}
|
|
142
180
|
body.lq-light .thinking{background-image:linear-gradient(90deg,#4A5565 0%,#0F1720 40%,#4A5565 60%,#4A5565 100%)}
|
|
143
181
|
@media (prefers-reduced-motion: reduce){.wkdoing,.thinking,.orb.run,.wktodo.in_progress i{animation:none}}
|
|
@@ -280,6 +318,47 @@ label.check input{width:auto}
|
|
|
280
318
|
.scrbox span{position:absolute;left:0;top:0;font-size:11px;background:var(--green);color:var(--ink);padding:1px 6px;white-space:nowrap}
|
|
281
319
|
.scrbox.draw{border:2px dashed var(--amber);background:rgba(242,165,65,.14)}
|
|
282
320
|
.board{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;margin-top:12px}
|
|
321
|
+
/* the Dashboard as a stream: time runs left to right, a current per feed, now on the right */
|
|
322
|
+
.bstream{position:relative;margin-top:10px;border-radius:18px;overflow:hidden;isolation:isolate;background:#08090B;box-shadow:0 0 0 1px rgba(255,255,255,.08)}
|
|
323
|
+
body.lq-light .bstream{background:#EBEEF0;box-shadow:0 0 0 1px rgba(21,26,33,.08)}
|
|
324
|
+
body.lq-look-pearl .bstream{background:#EDECE9}
|
|
325
|
+
.bstream.none{display:none}
|
|
326
|
+
#bsc{position:absolute;inset:0;width:100%;height:100%;display:block}
|
|
327
|
+
#bsov{position:absolute;inset:0}
|
|
328
|
+
body.lq-liquid .bcard.blane{position:absolute;left:16px;width:150px;padding:0;border:0;background:transparent;box-shadow:none;transform:translateY(-50%)}
|
|
329
|
+
.blane .bln{font:600 14px/1.2 var(--sans);color:var(--bone);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
330
|
+
.blane .blc{font-size:12px;color:var(--faint);margin-top:1px}
|
|
331
|
+
.blane .blc b{color:var(--amber);font-weight:600}
|
|
332
|
+
.blane .bla{display:flex;flex-wrap:wrap;gap:0 2px;margin:3px 0 0 -6px}
|
|
333
|
+
.blane .bla button,.btip .bta button.quiet{border:0;background:transparent;color:var(--faint);font:500 12px var(--sans);padding:4px 6px;border-radius:8px;cursor:pointer}
|
|
334
|
+
.blane .bla button:hover{color:var(--bone)}
|
|
335
|
+
.blane .ble{font-size:11.5px;color:#E5484D;margin-top:2px}
|
|
336
|
+
.blb{position:absolute;right:16px;width:230px;text-align:right;font-size:12.5px;line-height:1.4;color:var(--faint);transform:translateY(-50%);pointer-events:none}
|
|
337
|
+
.bline{position:absolute;height:1px;background:linear-gradient(90deg,transparent,rgba(255,255,255,.1) 6%,rgba(255,255,255,.1) 94%,transparent);pointer-events:none}
|
|
338
|
+
body.lq-light .bline{background:linear-gradient(90deg,transparent,rgba(21,26,33,.1) 6%,rgba(21,26,33,.1) 94%,transparent)}
|
|
339
|
+
.btick{position:absolute;bottom:12px;font-size:11px;color:var(--faint);transform:translateX(-50%);pointer-events:none}
|
|
340
|
+
.bnow{position:absolute;top:18px;bottom:30px;width:1px;background:linear-gradient(180deg,transparent,rgba(255,255,255,.25),transparent);pointer-events:none}
|
|
341
|
+
body.lq-light .bnow{background:linear-gradient(180deg,transparent,rgba(21,26,33,.25),transparent)}
|
|
342
|
+
.bbead{position:absolute;left:0;top:0;border:0;padding:0;border-radius:50%;background:transparent;cursor:pointer;will-change:transform}
|
|
343
|
+
.bbead:focus-visible{outline:2px solid var(--bone);outline-offset:2px}
|
|
344
|
+
.btip{position:absolute;left:0;top:0;width:300px;max-width:calc(100% - 24px);z-index:4;opacity:0;pointer-events:none;transition:opacity .18s ease}
|
|
345
|
+
.btip.on{opacity:1;pointer-events:auto}
|
|
346
|
+
body.lq-liquid .btip .bcard{padding:12px 14px;border-radius:16px;background:rgba(16,18,21,.95);border:1px solid rgba(255,255,255,.1);box-shadow:0 24px 50px -20px rgba(0,0,0,.8)}
|
|
347
|
+
body.lq-liquid.lq-light .btip .bcard{background:rgba(255,255,255,.96);border-color:rgba(21,26,33,.1);box-shadow:0 24px 50px -24px rgba(30,45,60,.45)}
|
|
348
|
+
.btip .bi{display:block !important;border:0 !important;padding:0 !important}
|
|
349
|
+
.btip .btf{font:600 13px var(--sans);color:var(--bone)}
|
|
350
|
+
.btip .btf span{font-weight:400;color:var(--faint)}
|
|
351
|
+
.btip .btx{margin:3px 0 10px;font-size:14px;color:var(--text);overflow-wrap:anywhere}
|
|
352
|
+
.btip .bta{display:flex;flex-wrap:wrap;gap:4px;align-items:center}
|
|
353
|
+
.btip .bta button:not(.quiet){border:0;border-radius:999px;padding:7px 13px;font:600 12.5px var(--sans);background:#F3F0EA;color:#0A0B0D;cursor:pointer}
|
|
354
|
+
body.lq-light .btip .bta button:not(.quiet){background:#151A21;color:#FFFFFF}
|
|
355
|
+
.blist{margin-top:12px}
|
|
356
|
+
.blist summary{cursor:pointer;color:var(--faint);font-size:13px;list-style:none}
|
|
357
|
+
.blist summary::-webkit-details-marker{display:none}
|
|
358
|
+
.blist summary::before{content:'';display:inline-block;width:5px;height:5px;margin-right:8px;border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(-45deg);vertical-align:2px;transition:transform .2s ease}
|
|
359
|
+
.blist[open] summary::before{transform:rotate(45deg)}
|
|
360
|
+
#boardtalk .bcard{margin-top:12px}
|
|
361
|
+
@media (max-width:720px){.blb{display:none}body.lq-liquid .bcard.blane{width:96px}.blane .bla{display:none}}
|
|
283
362
|
.bcard{border:1px solid var(--line);border-radius:12px;padding:12px 14px;background:var(--ink2);min-width:0}
|
|
284
363
|
.bcard.has{border-color:#2a6b52}
|
|
285
364
|
.bcard .bh{display:flex;gap:8px;align-items:center}.bcard .bn{color:var(--bone);font-weight:600;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
@@ -295,19 +374,20 @@ label.check input{width:auto}
|
|
|
295
374
|
<div id="liquid" aria-label="Symbiot">
|
|
296
375
|
<canvas id="lq" aria-hidden="true"></canvas>
|
|
297
376
|
<div id="lqorbits"></div>
|
|
377
|
+
<div id="lqgroups"></div>
|
|
298
378
|
<div id="lqdrops"></div>
|
|
299
379
|
<button type="button" id="lqback">‹ Home</button>
|
|
300
380
|
<button type="button" id="lqgo"></button>
|
|
301
381
|
<button type="button" id="lqcore" title="talk to Symbiot"></button>
|
|
302
382
|
<div id="lqmore"></div>
|
|
303
383
|
<div id="lqtalk" aria-live="polite"></div>
|
|
304
|
-
<form id="lqform" class="lqglass"><label for="lqask" style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)">Talk to Symbiot</label><input id="lqask" autocomplete="off" placeholder="Talk to Symbiot, or tell it what to do"><button type="submit" id="lqsend"
|
|
384
|
+
<form id="lqform" class="lqglass"><label for="lqask" style="position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)">Talk to Symbiot</label><input id="lqask" autocomplete="off" placeholder="Talk to Symbiot, or tell it what to do"><button type="submit" id="lqsend" aria-label="Send"><svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M8 13V3M3.5 7.5 8 3l4.5 4.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></button></form>
|
|
305
385
|
</div>
|
|
306
386
|
|
|
307
387
|
<div id="updatebar" class="updatebar"></div>
|
|
308
388
|
<div id="appbar" class="updatebar"></div>
|
|
309
389
|
<div id="whatsnew" class="whatsnew"></div>
|
|
310
|
-
<header><span class="dot"></span><span class="brand">Symbiot</span><span class="ver" id="ver"></span><span class="status" id="status">...</span></header>
|
|
390
|
+
<header><span class="dot"></span><span class="brand">Symbiot</span><span class="ver" id="ver"></span><span id="lqlook" role="radiogroup" aria-label="Look"><button type="button" role="radio" data-look="glass" aria-checked="false">Glass</button><button type="button" role="radio" data-look="ferro" aria-checked="true">Ferrofluid</button><button type="button" role="radio" data-look="pearl" aria-checked="false">Pearl</button></span><span class="status" id="status">...</span></header>
|
|
311
391
|
<div class="tabs">
|
|
312
392
|
<button class="tab active" data-tab="map">Map</button>
|
|
313
393
|
<button class="tab" data-tab="board" id="boardtab">Dashboard</button>
|
|
@@ -354,8 +434,10 @@ label.check input{width:auto}
|
|
|
354
434
|
<section id="panel-board" class="hidden">
|
|
355
435
|
<div class="row"><span class="muted" id="boardsum" style="flex:1">Everything you watch, side by side.</span><select id="boardhours" title="how far back" style="flex:0 0 auto;width:auto"><option value="24">last 24 hours</option><option value="72">last 3 days</option><option value="168">last 7 days</option></select><button class="ghost" id="boardrefresh">Refresh</button></div>
|
|
356
436
|
<div id="boardmsg"></div>
|
|
437
|
+
<div id="bstream" class="bstream"><canvas id="bsc" aria-hidden="true"></canvas><div id="bsov"></div></div>
|
|
438
|
+
<div id="boardtalk"></div>
|
|
439
|
+
<details id="blist" class="blist"><summary>All of it as a list</summary><div id="board" class="board"></div></details>
|
|
357
440
|
<div id="boardlinks"></div>
|
|
358
|
-
<div id="board" class="board"></div>
|
|
359
441
|
</section>
|
|
360
442
|
<section id="panel-run" class="hidden">
|
|
361
443
|
<div class="row"><button class="act" id="write">Write my <span id="what">week</span></button>
|
|
@@ -588,7 +670,7 @@ else if(rm)rm.addEventListener('click',function(){api('/api/tasks/remove',{id:id
|
|
|
588
670
|
// connected model, grounded in the task's repo (and its pending changes).
|
|
589
671
|
function askBtn(t){var n=(t.chat||[]).length;return "<button class='ask"+(n?" has":"")+"' title='"+(n?"questions & answers about this task":"ask a question about this task")+"'>💬"+(n?" "+Math.ceil(n/2):"")+"</button>";}
|
|
590
672
|
function taskById(id){var all=PENDTASKS.concat(ALLTASKS);for(var i=0;i<all.length;i++)if(all[i].id===id)return all[i];return null;}
|
|
591
|
-
function stepsHtml(st){return st&&st.length?"<
|
|
673
|
+
function stepsHtml(st){return st&&st.length?"<details class='steps'><summary>"+st.length+(st.length>1?' steps':' step')+" · "+esc(String(st[0]).charAt(0).toLowerCase()+String(st[0]).slice(1))+"</summary><ul>"+st.map(function(x){return "<li>"+esc(x)+"</li>";}).join('')+"</ul></details>":"";}
|
|
592
674
|
function chatMsgs(chat){return (chat||[]).map(function(m){return "<div class='msg "+(m.role==='user'?'q':'a')+"'>"+esc(m.text)+(m.role==='user'?'':stepsHtml(m.steps))+"</div>";}).join("");}
|
|
593
675
|
var CHATHINT="<div class='muted' style='font-size:12px'>Ask anything about this task — what it means, how to approach it, or what the agent changed. Answers draw on the repo's commits, README and rules.</div>";
|
|
594
676
|
function wireAsk(row,id){var b=row.querySelector('.ask');if(b)b.addEventListener('click',function(){toggleChat(row,id);});}
|
|
@@ -1085,8 +1167,51 @@ var TALKHINT="<div class='muted' style='font-size:12px'>Go over what's new here
|
|
|
1085
1167
|
function talking(){var f=document.activeElement;return BTALKBUSY>0||!!(f&&f.closest&&f.closest('.btalk'));}
|
|
1086
1168
|
function loadBoard(){api('/api/watch/board?hours='+(+$('boardhours').value||24)).then(function(b){if(b&&b.cards){BOARD=b;if(!talking())renderBoard();}});}
|
|
1087
1169
|
function boardMsg(cls,html){$('boardmsg').innerHTML="<div class='note "+cls+"'>"+html+"</div>";}
|
|
1170
|
+
|
|
1171
|
+
// ---- the Dashboard as a stream ----------------------------------------------------
|
|
1172
|
+
// Time runs left to right, one current per feed, "now" on the right. Each message
|
|
1173
|
+
// is a bead placed when it arrived: amber and bigger when it needs you, small when
|
|
1174
|
+
// read. A bead opens a popover with that message's own actions (the same buttons as
|
|
1175
|
+
// the list below, wired by renderBoard's wire); a feed's name carries its Seen,
|
|
1176
|
+
// Talk and Check now. Drawn in the look's liquid, with its own canvas.
|
|
1177
|
+
var BS={G:null,beads:[],hot:null,on:false,b:null},BWIRE=null;
|
|
1178
|
+
function bsGeom(){var el=$('bstream'),w=(el&&el.clientWidth)||900,n=((BS.b&&BS.b.cards)||[]).length,narrow=w<720;
|
|
1179
|
+
return {w:w,h:Math.max(280,70+n*96+40),x0:narrow?120:200,x1:w-(narrow?28:270),narrow:narrow,n:n};}
|
|
1180
|
+
function bsBuild(b){BS.b=b;var ov=$('bsov'),el=$('bstream');if(!ov||!el)return;var cs=b.cards||[];if(!cs.length){ov.innerHTML='';return;}
|
|
1181
|
+
var g=bsGeom();if(el.style)el.style.height=g.h+'px';var now=Date.now(),H=(b.hours||24)*3600000,laneY=function(i){return 60+(g.h-110)*(i+0.5)/Math.max(1,g.n);};
|
|
1182
|
+
var html='';cs.forEach(function(c,i){var y=laneY(i);
|
|
1183
|
+
html+="<div class='bline' style='left:"+(g.x0-24)+"px;width:"+(g.x1-g.x0+48)+"px;top:"+Math.round(y)+"px'></div>";
|
|
1184
|
+
html+="<div class='bcard blane' data-i='"+i+"' style='top:"+Math.round(y)+"px'><div class='bln' title='"+esc(c.url||'')+"'>"+esc(c.name)+"</div><div class='blc'>"+(c.count?"<b>"+esc(c.label)+"</b>":"nothing new")+"</div><div class='bla'>"+(c.count?"<button class='bseen' title='set this feed back to 0: only what comes in after counts'>Seen</button>":"")+"<button class='btalkbtn' title='talk what's new here over with your AI before you draft a reply'>Talk"+((c.chat||[]).length?" "+Math.ceil(c.chat.length/2):"")+"</button><button class='bcheck' title='read it now'>Check now</button></div>"+(c.error?"<div class='ble'>"+esc(c.error)+"</div>":"")+"</div>";
|
|
1185
|
+
if(c.brief&&!g.narrow)html+="<div class='blb' style='top:"+Math.round(y)+"px'>"+esc(c.brief.text.length>150?c.brief.text.slice(0,149)+'…':c.brief.text)+"</div>";});
|
|
1186
|
+
var marks=b.hours===24?[18,12,6]:b.hours===72?[48,24]:[120,72,24];
|
|
1187
|
+
marks.forEach(function(m){html+="<span class='btick' style='left:"+Math.round(g.x0+(1-m/(b.hours||24))*(g.x1-g.x0))+"px'>"+(m<48?m+'h ago':Math.round(m/24)+' days ago')+"</span>";});
|
|
1188
|
+
html+="<span class='btick' style='left:"+g.x1+"px'>now</span><div class='bnow' style='left:"+g.x1+"px'></div>";
|
|
1189
|
+
var old={};BS.beads.forEach(function(x){old[x.key]=x;});BS.beads=[];
|
|
1190
|
+
cs.forEach(function(c,i){(c.items||[]).forEach(function(n,j){var age=Math.max(0,now-(n.ts||now));if(age>H)return;var key=c.id+':'+(n.id||j),o=old[key];
|
|
1191
|
+
BS.beads.push({key:key,i:i,j:j,n:n,need:!!n.need&&!!(n.mail||n.chat),tx:g.x0+(1-age/H)*(g.x1-g.x0),ty:laneY(i),tr:n.need&&(n.mail||n.chat)?13:n.read?6:9,x:o?o.x:g.x1,y:o?o.y:laneY(i),r:o?o.r:0});});});
|
|
1192
|
+
html+=BS.beads.map(function(x,k){return "<button type='button' class='bbead' data-k='"+k+"' aria-label='"+esc(x.n.text||'')+"'></button>";}).join('');
|
|
1193
|
+
html+="<div class='btip' role='dialog' aria-live='polite'></div>";ov.innerHTML=html;
|
|
1194
|
+
BS.btns=[];ov.querySelectorAll('.bbead').forEach(function(bt){BS.btns.push(bt);var k=+bt.getAttribute('data-k');bt.addEventListener('pointerenter',function(){bsShow(k);});bt.addEventListener('focus',function(){bsShow(k);});bt.addEventListener('click',function(){bsShow(k);});});
|
|
1195
|
+
if(el.addEventListener&&!BS.wired){BS.wired=true;el.addEventListener('pointerleave',function(){bsShow(null);});el.addEventListener('keydown',function(e){if(e&&e.key==='Escape')bsShow(null);});}
|
|
1196
|
+
BS.hot=null;bsStart();}
|
|
1197
|
+
function bsShow(k){var tip=document.querySelector('#bsov .btip');if(!tip)return;BS.hot=k;if(k==null||!BS.beads[k]){tip.className='btip';return;}
|
|
1198
|
+
var x=BS.beads[k],c=BS.b.cards[x.i],n=x.n;
|
|
1199
|
+
tip.innerHTML="<div class='bcard' data-i='"+x.i+"'><div class='bi' data-j='"+x.j+"'><div class='btf'>"+esc(n.name||c.name)+" <span>· "+esc(c.name)+" · "+agoTxt(n.ts)+(n.from&&CHATFROM[n.from]?" · "+CHATFROM[n.from]:"")+(n.read?" · read":"")+"</span></div><div class='btx'>"+esc(n.text||'')+"</div><div class='bta'>"+(n.mail||n.chat?"<button class='bdraft'>"+(n.drafted?"Drafted · again":"Draft a reply")+"</button>":"")+(n.href?"<button class='bopen"+(n.mail||n.chat?" quiet":"")+"'>Open</button>":"")+(n.chat&&n.drafted?"<button class='bopenwa quiet'>Open in WhatsApp</button>":"")+"</div></div></div>";
|
|
1200
|
+
tip.className='btip on';if(BWIRE)BWIRE(tip);}
|
|
1201
|
+
function bsStart(){if(BS.on)return;var raf=typeof window.requestAnimationFrame==='function'?function(f){return window.requestAnimationFrame(f);}:null;if(!raf)return;BS.on=true;
|
|
1202
|
+
var loop=function(){var p=$('panel-board'),el=$('bstream');if(!p||!el||!p.offsetParent||(el.classList&&el.classList.contains('none'))){BS.on=false;return;}bsFrame();raf(loop);};raf(loop);}
|
|
1203
|
+
function bsFrame(){var el=$('bstream'),cv=$('bsc');if(!el||!cv)return;if(!BS.G)BS.G=lqGL(cv,40);var g=bsGeom(),t=LQ.t||0,still=LQ.theme&&LQ.theme.still;
|
|
1204
|
+
BS.beads.forEach(function(x,k){var hot=BS.hot===k;x.x+=(x.tx-x.x)*0.12;x.y+=((x.ty+(still?0:Math.sin(t*0.8+k*1.3)*2))-x.y)*0.12;x.r+=((x.tr*(hot?1.35:1))-x.r)*0.15;
|
|
1205
|
+
var bt=BS.btns&&BS.btns[k];if(bt&&bt.style){var R=Math.max(14,x.r+6);bt.style.width=bt.style.height=(2*R)+'px';bt.style.transform='translate('+Math.round(x.x-R)+'px,'+Math.round(x.y-R)+'px)';}});
|
|
1206
|
+
var tip=document.querySelector('#bsov .btip');if(tip&&BS.hot!=null&&BS.beads[BS.hot]){var x=BS.beads[BS.hot],tw=tip.offsetWidth||300,th=tip.offsetHeight||120,tx=Math.max(12,Math.min(g.w-tw-12,x.x-tw/2)),ty=x.y-x.r-th-14;if(ty<8)ty=x.y+x.r+14;tip.style.transform='translate('+Math.round(tx)+'px,'+Math.round(ty)+'px)';}
|
|
1207
|
+
var G=BS.G;if(!G)return;var gl=G.gl,d=Math.min(window.devicePixelRatio||1,3),W=Math.max(1,Math.floor(g.w*d)),H=Math.max(1,Math.floor(g.h*d));if(cv.width!==W||cv.height!==H){cv.width=W;cv.height=H;gl.viewport(0,0,W,H);}
|
|
1208
|
+
var o=G.out;for(var i=0;i<o.length;i++)o[i]=0;var k=0,put=function(px,py,r,a){if(k>=40)return;o[k*4]=px/g.w;o[k*4+1]=py/g.h;o[k*4+2]=Math.max(0,r);o[k*4+3]=a;k++;};
|
|
1209
|
+
(BS.b.cards||[]).forEach(function(c,i){put(g.x1+16,60+(g.h-110)*(i+0.5)/Math.max(1,g.n),c.count?9+2*Math.min(c.count,6):5,0);});
|
|
1210
|
+
BS.beads.forEach(function(x){put(x.x,x.y,x.r,x.need?1:0);});
|
|
1211
|
+
var th=LQ.theme||{},U=G.U;gl.uniform2f(U.uRes,W,H);gl.uniform1f(U.uT,t);gl.uniform1f(U.uDpr,d);gl.uniform4fv(U.uB,o);gl.uniform3f(U.uRip,0,0,-10);gl.uniform1f(U.uStyle,th.look==='glass'?0:th.look==='pearl'?2:1);gl.uniform1f(U.uContrast,th.contrast?1:0);gl.uniform1f(U.uExposure,th.night?0.82:1);gl.drawArrays(gl.TRIANGLES,0,3);}
|
|
1088
1212
|
function renderBoard(){var b=BOARD,cs=b.cards,el=$('board');
|
|
1089
1213
|
var tab=$('boardtab');if(tab)tab.textContent='Dashboard'+(b.total?' · '+b.total:'');
|
|
1214
|
+
var bst=$('bstream');if(bst&&bst.classList)bst.classList.toggle('none',!cs.length);var bl=$('blist');if(bl&&!cs.length)bl.open=true;
|
|
1090
1215
|
if(!cs.length){$('boardsum').textContent='Everything you watch, side by side.';
|
|
1091
1216
|
el.innerHTML="<div style='grid-column:1/-1'><div class='note muted' style='margin-top:0'>Nothing watched yet. Map your inbox, a chat (web.whatsapp.com) or any page under <b>Screens</b> on the Map tab and click <b>Watch</b> on it, or click <b>Watch GitHub</b> there. Each one gets a card here with what's new on it.</div><div class='row' style='margin-top:8px'><button class='ghost' id='boardgo'>Go to Screens</button></div></div>";
|
|
1092
1217
|
$('boardgo').addEventListener('click',function(){setTab('map');var w=$('watchbox');if(w&&w.scrollIntoView)w.scrollIntoView({block:'center'});});return;}
|
|
@@ -1100,9 +1225,12 @@ return "<div class='bcard"+(c.count?" has":"")+"' data-i='"+i+"'><div class='bh'
|
|
|
1100
1225
|
+(c.brief?"<div class='bbrief'>"+esc(c.brief.text)+"</div>":"")
|
|
1101
1226
|
+c.items.map(function(n,j){return "<div class='bi' data-j='"+j+"'><span class='t'>"+esc(n.text)+" <span class='muted' style='font-size:11px'>"+agoTxt(n.ts)+(n.from&&CHATFROM[n.from]?" · "+CHATFROM[n.from]:"")+"</span></span>"+(n.href?"<button class='ghost bopen' title='open it in your browser'>Open</button>":"")+(n.mail||n.chat?"<button class='ghost bdraft' title='"+(n.chat?DRAFT_CHAT_TIP:"your coding agent writes a reply and leaves it in Drafts. It never presses Send")+"'>"+(n.drafted?"Drafted · again":"Draft a reply")+"</button>":"")+(n.chat&&n.drafted?"<button class='ghost bopenwa' title='"+OPEN_WA_TIP+"'>Open in WhatsApp</button>":"")+"</div>";}).join('')
|
|
1102
1227
|
+(c.count>c.items.length?"<div class='bl' style='margin-top:6px'>…and "+(c.count-c.items.length)+" more</div>":"")
|
|
1103
|
-
+
|
|
1228
|
+
+"</div>";}).join('');
|
|
1229
|
+
var tk=$('boardtalk');if(tk)tk.innerHTML=cs.map(function(c,i){var nt=Math.ceil(((c.chat||[]).length)/2);return BTALK[c.id]?"<div class='bcard' data-i='"+i+"'><div class='bh'><span class='bn'>Talking over "+esc(c.name)+"</span><button class='ghost btalkbtn' style='padding:4px 9px;font-size:12px'>Close</button></div><div class='tchat btalk'><div class='msgs'>"+(nt?chatMsgs(c.chat):TALKHINT)+"</div><div class='row'><input class='talkq' placeholder='Ask about these, or say how to answer one...' style='flex:1'><button class='ghost talksend'>Ask</button><a class='talkclear' title='forget this conversation'>clear</a></div></div></div>":"";}).join('');
|
|
1230
|
+
bsBuild(b);
|
|
1104
1231
|
function card(btn){return cs[+btn.closest('.bcard').getAttribute('data-i')];}
|
|
1105
|
-
|
|
1232
|
+
function wire(el){if(!el||!el.querySelectorAll)return;
|
|
1233
|
+
el.querySelectorAll('.btalkbtn').forEach(function(btn){btn.addEventListener('click',function(){var c=card(btn);BTALK[c.id]=!BTALK[c.id];renderBoard();var tk2=$('boardtalk'),q=BTALK[c.id]&&tk2&&tk2.querySelector(".bcard[data-i='"+cs.indexOf(c)+"'] .talkq");if(q&&q.focus)q.focus();});});
|
|
1106
1234
|
el.querySelectorAll('.btalk').forEach(function(box){var c=card(box),inp=box.querySelector('.talkq'),send=box.querySelector('.talksend'),msgs=box.querySelector('.msgs');
|
|
1107
1235
|
inp.value=BTALKDRAFT[c.id]||'';inp.addEventListener('input',function(){BTALKDRAFT[c.id]=inp.value;});
|
|
1108
1236
|
function ask(){var q=(inp.value||'').trim();if(!q||send.disabled)return;send.disabled=true;inp.value='';BTALKDRAFT[c.id]='';BTALKBUSY++;
|
|
@@ -1124,6 +1252,7 @@ api('/api/watch/seen',{id:c.id}).then(function(x){if(!x||x.error){btn.disabled=f
|
|
|
1124
1252
|
el.querySelectorAll('.bopen').forEach(function(btn){btn.addEventListener('click',function(){var n=item(btn);if(n&&/^https?:/.test(n.href))window.open(n.href,'_blank','noopener');});});
|
|
1125
1253
|
el.querySelectorAll('.bdraft').forEach(function(btn){btn.addEventListener('click',function(){var n=item(btn);btn.disabled=true;
|
|
1126
1254
|
api('/api/watch/draft',{id:n.id}).then(function(x){btn.disabled=false;if(!x||x.error){boardMsg('err',esc((x&&x.error)||'failed'));return;}boardMsg('ok',x.chat?DRAFTING_CHAT:DRAFTING);loadWatchUI();}).catch(function(e){btn.disabled=false;boardMsg('err',esc(String((e&&e.message)||e)));});});});}
|
|
1255
|
+
wire(el);wire($('boardtalk'));wire($('bsov'));BWIRE=wire;}
|
|
1127
1256
|
$('boardrefresh').addEventListener('click',loadBoard);$('boardhours').addEventListener('change',loadBoard);
|
|
1128
1257
|
// A web page, mapped by itself in the hidden browser (headless.mjs): no capture,
|
|
1129
1258
|
// no dragging. It takes a few seconds, so the button says so meanwhile.
|
|
@@ -1296,11 +1425,12 @@ function lqBuildWork(){var w=LQ.workData||{},S=lqSize(),cx=S.w/2,cy=S.h*0.5,list
|
|
|
1296
1425
|
var vx=Math.max(0.5,Math.min(S.s*1.3,(S.w/2-90)/400)),vy=Math.max(0.35,Math.min(S.s*0.8,(S.h-cy-230)/400,(cy-160)/400)),rs=Math.min(S.s,(vx+vy)/1.5);
|
|
1297
1426
|
var run=(w.running||[]).slice(0,3),ready=(w.ready||[]).slice(0,2),wait=(w.waiting||[]).slice(0,5),extra=(w.waitingCount||0)-wait.length;
|
|
1298
1427
|
run.forEach(function(r,i){var ang=Math.PI+(i+1)*Math.PI/(run.length+1);var pg=r.progress?' · '+r.progress.done+' of '+r.progress.total+' done':'';
|
|
1299
|
-
list.push({id:r.id,kind:'run',shape:'agents',ask:r.waiting,title:r.doing,sub:r.name+pg+(r.waiting?' · has a question for you':''),r:58*rs,tx:cx+Math.cos(ang)*190*vx,ty:cy+Math.sin(ang)*130*vy});});
|
|
1300
|
-
ready.forEach(function(r,i){var ang=-Math.PI/2+(i-(ready.length-1)/2)*0.9;list.push({id:r.id,kind:'ready',shape:'tasks',title:'Ready for your OK',sub:r.repo+(r.count?' · '+r.count+' task'+(r.count>1?'s':'')+' done':''),r:48*rs,tx:cx+Math.cos(ang)*300*vx,ty:cy+Math.sin(ang)*300*vy});});
|
|
1301
|
-
wait.forEach(function(t,i){var ang=Math.PI/2+(i-(wait.length-1)/2)*0.55;list.push({id:t.id,kind:'wait',shape:'tasks',title:t.text,sub:t.repo+(t.busy?' · after the current one':' · waiting'),r:30*rs,tx:cx+Math.cos(ang)*300*vx,ty:cy+Math.sin(ang)*260*vy});});
|
|
1428
|
+
list.push({id:r.id,kind:'run',repo:r.name,shape:'agents',ask:r.waiting,title:r.doing,sub:r.name+pg+(r.waiting?' · has a question for you':''),r:58*rs,tx:cx+Math.cos(ang)*190*vx,ty:cy+Math.sin(ang)*130*vy});});
|
|
1429
|
+
ready.forEach(function(r,i){var ang=-Math.PI/2+(i-(ready.length-1)/2)*0.9;list.push({id:r.id,kind:'ready',repo:r.repo,shape:'tasks',title:'Ready for your OK',sub:r.repo+(r.count?' · '+r.count+' task'+(r.count>1?'s':'')+' done':''),r:48*rs,tx:cx+Math.cos(ang)*300*vx,ty:cy+Math.sin(ang)*300*vy});});
|
|
1430
|
+
wait.forEach(function(t,i){var ang=Math.PI/2+(i-(wait.length-1)/2)*0.55;list.push({id:t.id,kind:'wait',repo:t.repo,shape:'tasks',title:t.text,sub:t.repo+(t.busy?' · after the current one':' · waiting'),r:30*rs,tx:cx+Math.cos(ang)*300*vx,ty:cy+Math.sin(ang)*260*vy});});
|
|
1302
1431
|
if(extra>0)list.push({id:'morewait',kind:'more',shape:'tasks',title:'+'+extra+' more waiting',sub:'see them all',r:24*rs,tx:S.w-Math.max(80,S.w*0.1),ty:S.h-230});
|
|
1303
|
-
|
|
1432
|
+
lqGrow(list,cx,cy,vx,vy);list.forEach(function(d){d.ax=d.tx;d.ay=d.ty;});
|
|
1433
|
+
var wl=[];list.forEach(function(d,i){if(d.kind!=='wait'&&d.kind!=='ready')return;list.forEach(function(e,j){if(e.kind==='run'&&e.repo===d.repo)wl.push([j,i,0.5]);});});lqSeed(list,wl,cx,cy,vx,vy);lqNear(list,wl,cx,cy,60*rs,S);lqRelax(list,S,cx,cy,60*rs);
|
|
1304
1434
|
var old={};LQ.drops.forEach(function(d){old[d.id]=d;});
|
|
1305
1435
|
LQ.drops=list.map(function(d){var o=old[d.id];d.x=o?o.x:cx;d.y=o?o.y:cy;d.vx=o?o.vx:0;d.vy=o?o.vy:0;d.cr=o?o.cr:0;return d;});
|
|
1306
1436
|
var nr=(w.running||[]).length,nw=w.waitingCount||0,nk=(w.ready||[]).length;
|
|
@@ -1322,20 +1452,61 @@ var LQ={drops:[],btns:[],core:{x:0,y:0,cr:0},mode:'aware',last:'',lastAct:Date.n
|
|
|
1322
1452
|
var LQNAMES={board:'Dashboard',map:'Map',tasks:'Tasks',agents:'Agents',week:'Week',standup:'Standup',todo:'Todo',drift:'Drift',settings:'Settings'};
|
|
1323
1453
|
var LQ_REST=60000,LQ_MAX=11;
|
|
1324
1454
|
function lqMM(q){try{return !!(window.matchMedia&&window.matchMedia(q).matches);}catch(e){return false;}}
|
|
1325
|
-
function
|
|
1455
|
+
function lqLookGet(){var l='';try{l=window.localStorage&&window.localStorage.getItem('symbiot-look')||'';}catch(e){}return l==='glass'||l==='pearl'?l:'ferro';}
|
|
1456
|
+
function lqLook(l){try{if(window.localStorage)window.localStorage.setItem('symbiot-look',l);}catch(e){}lqTheme();var g=$('lqlook');if(g&&g.querySelectorAll)g.querySelectorAll('button').forEach(function(b){b.setAttribute('aria-checked',b.getAttribute('data-look')===l?'true':'false');});LQ.ripple=[0.5,0.47,LQ.t||0];}
|
|
1457
|
+
function lqTheme(){var look=lqLookGet();var th={look:look,light:look!=='ferro',contrast:lqMM('(prefers-contrast: more)'),forced:lqMM('(forced-colors: active)'),solid:lqMM('(prefers-reduced-transparency: reduce)'),still:lqMM('(prefers-reduced-motion: reduce)'),wide:lqMM('(color-gamut: p3)'),night:false,accent:null};
|
|
1326
1458
|
var h=new Date().getHours();th.night=h>=22||h<6;
|
|
1327
1459
|
try{if(document.createElement&&typeof getComputedStyle==='function'&&document.body&&document.body.appendChild){var a=document.createElement('span'),b=document.createElement('span');a.style.color='AccentColor';b.style.color='CanvasText';document.body.appendChild(a);document.body.appendChild(b);
|
|
1328
1460
|
if(a.style.color){var ca=getComputedStyle(a).color,cb=getComputedStyle(b).color;if(ca&&ca!==cb){var m=ca.match(/[0-9.]+/g);if(m&&m.length>=3)th.accent=[m[0]/255,m[1]/255,m[2]/255];}}a.remove();b.remove();}}catch(e){}
|
|
1329
|
-
var bd=document.body;if(bd&&bd.classList){bd.classList.toggle('lq-light',th.light);bd.classList.toggle('lq-contrast',th.contrast);bd.classList.toggle('lq-solid',th.solid||th.contrast);bd.classList.toggle('lq-forced',th.forced);}
|
|
1461
|
+
var bd=document.body;if(bd&&bd.classList){bd.classList.toggle('lq-light',th.light);['ferro','glass','pearl'].forEach(function(l){bd.classList.toggle('lq-look-'+l,l===look);});bd.classList.toggle('lq-contrast',th.contrast);bd.classList.toggle('lq-solid',th.solid||th.contrast);bd.classList.toggle('lq-forced',th.forced);}
|
|
1330
1462
|
LQ.theme=th;return th;}
|
|
1331
1463
|
function lqSize(){var el=$('liquid');var w=(el&&el.clientWidth)||window.innerWidth||1280,h=(el&&el.clientHeight)||window.innerHeight||800;return {w:w,h:h,s:Math.max(0.55,Math.min(1.15,Math.min(w,h)/860))};}
|
|
1332
1464
|
function lqLoad(commit){if(LQ.scene==='work'){lqLoadWork();return;}Promise.all([api('/api/adapt?from='+encodeURIComponent(LQ.last)+(commit?'&commit=1':'')+(LQ.touch?'&touch=1':'')),api('/api/home')]).then(function(r){LQ.adapt=r[0]||{};LQ.home=r[1]||{};lqBuild();}).catch(function(){});}
|
|
1465
|
+
// A droplet's own place: a steady nudge in angle and distance from its id, so the
|
|
1466
|
+
// layout reads as grown, not ruled, and stays put from one visit to the next.
|
|
1467
|
+
// P.A.R.A.: home's droplets in four groups, each in its own part of the screen:
|
|
1468
|
+
// Projects (work with an end) up left, Areas (what you keep up with) up right,
|
|
1469
|
+
// Resources (to look things up in) down left, the Archive (what's done) sunk low right.
|
|
1470
|
+
var PARA_OF={tasks:'p',agents:'p',todo:'p',board:'a',week:'a',standup:'a',map:'r',drift:'r',settings:'r'};
|
|
1471
|
+
var PARA_NAME={p:'Projects',a:'Areas',r:'Resources',x:'Archive'},PARA_SUB={p:'work with an end',a:'what you keep up with',r:'to look things up in',x:'what’s done'};
|
|
1472
|
+
var PARA_ANG={p:-2.4,a:-0.65,r:2.45,x:0.8},PARA_LIVE={p:0.45,a:0.3,r:0.12,x:0};
|
|
1473
|
+
function lqPara(d){return d.kind==='archive'?'x':d.kind==='feed'?'a':PARA_OF[d.shape]||'p';}
|
|
1474
|
+
function lqParaGo(g){lqAct();if(g==='p'){lqWork();return;}if(g==='a'){lqUse('board');lqPool('board');return;}if(g==='r'){lqUse('map');lqPool('map');return;}lqArchive();}
|
|
1475
|
+
function lqArchive(){lqPool('tasks');if(typeof loadArchived==='function')loadArchived();}
|
|
1476
|
+
function lqOrg(id,k){var h=k*977;id=String(id);for(var i=0;i<id.length;i++)h=(h*31+id.charCodeAt(i))|0;return ((h>>>0)%1000)/1000;}
|
|
1477
|
+
function lqGrow(list,cx,cy,vx,vy){list.forEach(function(d){var x=(d.tx-cx)/vx,y=(d.ty-cy)/vy,an=Math.atan2(y,x)+(lqOrg(d.id,1)-0.5)*0.7,rr=Math.sqrt(x*x+y*y)*(0.78+0.44*lqOrg(d.id,2));d.tx=cx+Math.cos(an)*rr*vx;d.ty=cy+Math.sin(an)*rr*vy;});}
|
|
1478
|
+
// Nearest neighbours, in the liquid: each droplet is drawn towards the ones it's
|
|
1479
|
+
// linked to (its k nearest, from adapt.mjs, or the part or repo it belongs to),
|
|
1480
|
+
// pushed off the rest, and loosely held at its own distance from the core. A few
|
|
1481
|
+
// hundred fixed steps, so the same links give the same picture every time.
|
|
1482
|
+
// Linked droplets settle just past their separation; unlinked ones keep a wider gap,
|
|
1483
|
+
// so clusters read as groups. Only the distance from the core is held (the likely
|
|
1484
|
+
// nearer); the angle is free, so there's no ring for them to fall into.
|
|
1485
|
+
function lqNear(list,links,cx,cy,cr,S){if(!links.length)return;var gap=36*S.s,lk={};links.forEach(function(l){lk[l[0]+','+l[1]]=1;lk[l[1]+','+l[0]]=1;});
|
|
1486
|
+
for(var it=0;it<320;it++){links.forEach(function(l){var a=list[l[0]],b=list[l[1]],dx=b.tx-a.tx,dy=b.ty-a.ty,d=Math.sqrt(dx*dx+dy*dy)||1,L=lqSep(a,b,dx,dy)+10,f=(d-L)*0.08*Math.max(0.4,Math.min(1,l[2]*4))/d;if(d>L){a.tx+=dx*f;a.ty+=dy*f;b.tx-=dx*f;b.ty-=dy*f;}});
|
|
1487
|
+
for(var i=0;i<list.length;i++){var a=list[i];for(var j=i+1;j<list.length;j++){var b=list[j],dx=b.tx-a.tx,dy=b.ty-a.ty,d=Math.sqrt(dx*dx+dy*dy)||1,mn=lqSep(a,b,dx,dy)+(lk[i+','+j]?0:gap);if(d<mn){var f=(mn-d)*0.2/d;a.tx-=dx*f;a.ty-=dy*f;b.tx+=dx*f;b.ty+=dy*f;}}
|
|
1488
|
+
a.tx+=(cx-a.tx)*0.004;a.ty+=(cy-a.ty)*0.004;var ex=a.tx-cx,ey=a.ty-cy,ed=Math.sqrt(ex*ex+ey*ey)||1,mn2=cr+a.r+60;if(ed<mn2){a.tx=cx+ex/ed*mn2;a.ty=cy+ey/ed*mn2;}}}}
|
|
1489
|
+
// Seed by cluster: the linked groups (union-find) each get their own arc, sized to
|
|
1490
|
+
// the group, in the order of where they'd have been, with a steady offset; members
|
|
1491
|
+
// sit together, alternately nearer and further. lqNear then only polishes.
|
|
1492
|
+
function lqSeed(list,links,cx,cy,vx,vy,keyOf,angOf){var par=list.map(function(_,i){return i;});function root(i){while(par[i]!==i)i=par[i]=par[par[i]];return i;}links.forEach(function(l){par[root(l[0])]=root(l[1]);});
|
|
1493
|
+
var groups={};list.forEach(function(d,i){if(d.kind==='more')return;var r=keyOf?keyOf(d):root(i);(groups[r]=groups[r]||[]).push(i);});
|
|
1494
|
+
var gs=Object.keys(groups).map(function(k){var g=groups[k];return {key:k,m:g,ang:Math.atan2(g.reduce(function(t,i){return t+(list[i].ay-cy)/vy;},0),g.reduce(function(t,i){return t+(list[i].ax-cx)/vx;},0))};});
|
|
1495
|
+
gs.sort(function(a,b){return a.ang-b.ang;});var tot=gs.reduce(function(t,g){return t+g.m.length+0.8;},0),acc=gs.length?gs[0].ang-(gs[0].m.length+0.8)/tot*Math.PI:0;
|
|
1496
|
+
gs.forEach(function(g){var span=(g.m.length+0.8)/tot*2*Math.PI,mid=acc+span/2+(lqOrg(list[g.m[0]].id,3)-0.5)*span*0.25;acc+=span;if(angOf){span=Math.min(1.5,span);mid=angOf(g.key)+(lqOrg(list[g.m[0]].id,3)-0.5)*0.2;}
|
|
1497
|
+
g.m.forEach(function(i,k){var d=list[i],rr=Math.sqrt(Math.pow((d.ax-cx)/vx,2)+Math.pow((d.ay-cy)/vy,2))*(k%2?1.22:0.92),th=mid+(k-(g.m.length-1)/2)*Math.min(0.62,span/Math.max(1,g.m.length)*0.8);d.tx=cx+Math.cos(th)*rr*vx;d.ty=cy+Math.sin(th)*rr*vy;});});}
|
|
1498
|
+
function lqLinks(list,near){var at={},links=[],same=function(l){return lqPara(list[l[0]])===lqPara(list[l[1]]);};list.forEach(function(d,i){if(d.kind==='shape')at[d.shape]=i;});
|
|
1499
|
+
list.forEach(function(d,i){if(d.kind==='shape')((near||{})[d.shape]||[]).forEach(function(n){var j=at[n.id];if(j!=null&&j>i)links.push([i,j,n.w]);else if(j!=null&&j<i&&!links.some(function(l){return l[0]===j&&l[1]===i;}))links.push([j,i,n.w]);});
|
|
1500
|
+
else if((d.kind==='you'||d.kind==='feed')&&at[d.shape]!=null)links.push([at[d.shape],i,0.5]);});return links.filter(same);}
|
|
1333
1501
|
function lqBuild(){var a=LQ.adapt||{},h=LQ.home||{},S=lqSize(),cx=S.w/2,cy=S.h*0.47,list=[];LQ.bw=S.w;LQ.bh=S.h;
|
|
1334
1502
|
var you=(h.you||[]).slice(0,3),feeds=(h.feeds||[]).slice(0,2),lay=(a.layout&&a.layout.items)||[],more=((a.layout&&a.layout.more)||[]).slice();
|
|
1335
1503
|
var vx=Math.max(0.5,Math.min(S.s*1.3,(S.w/2-90)/400)),vy=Math.max(0.35,Math.min(S.s*0.8,(S.h-cy-230)/400,(cy-160)/400));var rs=Math.min(S.s,(vx+vy)/1.5);var room=Math.max(3,LQ_MAX-you.length-feeds.length-1);lay.slice(room).forEach(function(it){more.push(it.id);});
|
|
1336
1504
|
lay.slice(0,room).forEach(function(it){list.push({id:'shape:'+it.id,kind:'shape',shape:it.id,title:LQNAMES[it.id]||it.id,sub:'',r:it.r*rs,tx:cx+Math.cos(it.angle)*it.d*vx,ty:cy+Math.sin(it.angle)*it.d*vy});});
|
|
1337
1505
|
you.forEach(function(y,i){var ang=-Math.PI/2+(i-(you.length-1)/2)*0.6;list.push({id:y.id,kind:'you',shape:y.shape,title:y.title,sub:y.sub,r:50*rs,tx:cx+Math.cos(ang)*230*vx,ty:cy+Math.sin(ang)*230*vy*1.05,tendril:true});});
|
|
1338
1506
|
feeds.forEach(function(f,i){var ang=Math.PI/2+(i-(feeds.length-1)/2)*0.7;list.push({id:f.id,kind:'feed',shape:f.shape,title:f.title,sub:f.sub,r:(28+5*Math.min(f.count||1,5))*rs,tx:cx+Math.cos(ang)*300*vx,ty:cy+Math.sin(ang)*300*vy});});
|
|
1507
|
+
list.push({id:'archive',kind:'archive',shape:'tasks',title:'Archive',sub:'what’s done: archived tasks',r:26*rs,tx:cx+Math.cos(PARA_ANG.x)*330*vx,ty:cy+Math.sin(PARA_ANG.x)*330*vy});
|
|
1508
|
+
list.forEach(function(d){var g=lqPara(d);d.live=PARA_LIVE[g];if(g==='r')d.r*=0.82;if(g==='x')d.r*=0.85;if(d.shape==='agents'&&d.kind==='shape'&&h.working)d.orbit=true;});
|
|
1509
|
+
lqGrow(list,cx,cy,vx,vy);list.forEach(function(d){d.ax=d.tx;d.ay=d.ty;});var lks=lqLinks(list,a.near);lqSeed(list,lks,cx,cy,vx,vy,lqPara,function(g){return PARA_ANG[g];});lqNear(list,lks,cx,cy,92*rs,S);
|
|
1339
1510
|
// Solve it. If this screen can't hold them all clear of each other (lqRelax), the
|
|
1340
1511
|
// least likely part goes under "more" (Hick: fewer, not cramped) and it solves
|
|
1341
1512
|
// again. What only you can do and your feeds always stay out.
|
|
@@ -1375,8 +1546,9 @@ el.innerHTML=LQ.drops.map(function(d,i){return "<div class='lqd lq-"+d.kind+(LQ.
|
|
|
1375
1546
|
LQ.btns=[];LQ.lts=[];el.querySelectorAll('.lqd').forEach(function(b){LQ.btns.push(b);var d=LQ.drops[+b.getAttribute('data-i')],lt=b.querySelector('.lt'),go=b.querySelector('.lgo');LQ.lts.push(lt);
|
|
1376
1547
|
if(lt)lt.addEventListener('click',function(ev){if(d.kind==='more'){lqOpen(d,ev);return;}lqTag(d.id);});
|
|
1377
1548
|
if(go)go.addEventListener('click',function(ev){LQ.openTag=null;lqOpen(d,ev);});});
|
|
1378
|
-
var oe=$('lqorbits');if(oe){oe.innerHTML=LQ.drops.map(function(d,i){return d.kind==='run'?"<div class='lqorbit"+(d.ask?' ask':'')+"' data-i='"+i+"'><span><i></i></span><span><i></i></span><span><i></i></span></div>":'';}).join('');LQ.orbs=[];oe.querySelectorAll('.lqorbit').forEach(function(o){LQ.orbs.push(o);});}
|
|
1549
|
+
var oe=$('lqorbits');if(oe){oe.innerHTML=LQ.drops.map(function(d,i){return d.kind==='run'||d.orbit?"<div class='lqorbit"+(d.ask?' ask':'')+"' data-i='"+i+"'><span><i></i></span><span><i></i></span><span><i></i></span></div>":'';}).join('');LQ.orbs=[];oe.querySelectorAll('.lqorbit').forEach(function(o){LQ.orbs.push(o);});}
|
|
1379
1550
|
LQ.drops.forEach(function(d){if(d.x===d.tx&&d.y===d.ty)return;if(!LQ.frame){d.x=d.tx;d.y=d.ty;d.cr=d.r;}});lqStep();
|
|
1551
|
+
var ge=$('lqgroups');if(ge){var gs={};if(LQ.scene!=='work')LQ.drops.forEach(function(d){if(d.kind!=='more')gs[lqPara(d)]=1;});ge.innerHTML=['p','a','r','x'].filter(function(g){return gs[g];}).map(function(g){return "<button type='button' class='lqg' data-g='"+g+"'><span>"+PARA_NAME[g]+"</span><small>"+PARA_SUB[g]+"</small></button>";}).join('');LQ.gbtns=[];ge.querySelectorAll('.lqg').forEach(function(b){LQ.gbtns.push(b);b.addEventListener('click',function(){lqParaGo(b.getAttribute('data-g'));});});}
|
|
1380
1552
|
var c=$('lqcore');if(c)c.textContent=LQ.coreText||'';}
|
|
1381
1553
|
// Tapping a sphere (or its name) opens its tag; tapping it again, or elsewhere, closes it.
|
|
1382
1554
|
function lqTag(id){LQ.openTag=LQ.openTag===id?null:id;lqAct();LQ.btns.forEach(function(b,i){var d=LQ.drops[+b.getAttribute('data-i')],on=!!d&&d.id===LQ.openTag;if(b.classList)b.classList.toggle('open',on);var lt=LQ.lts&&LQ.lts[i];if(lt&<.setAttribute)lt.setAttribute('aria-expanded',on?'true':'false');});}
|
|
@@ -1384,6 +1556,7 @@ function lqVia(ev){return LQ.talking?'talk':ev&&ev.pointerType==='touch'?'touch'
|
|
|
1384
1556
|
function lqUse(shape,ev,via){api('/api/adapt/use',{shape:shape,from:LQ.last,via:via||lqVia(ev)});LQ.last=shape;}
|
|
1385
1557
|
function lqOpen(d,ev){if(!d)return;lqAct();
|
|
1386
1558
|
if(d.kind==='more'){LQ.moreOpen=!LQ.moreOpen;lqMore(d);return;}
|
|
1559
|
+
if(d.kind==='archive'){lqArchive();return;}
|
|
1387
1560
|
var S=lqSize();LQ.ripple=[d.x/S.w,d.y/S.h,LQ.t];if(LQ.scene==='work'){lqPool(d.shape||'agents');return;}lqUse(d.shape||'board',ev);lqGo(d.shape||'board');}
|
|
1388
1561
|
function lqMore(d){var el=$('lqmore');if(!el)return;if(!LQ.moreOpen||!d){el.innerHTML='';return;}
|
|
1389
1562
|
el.innerHTML=(d.more||[]).map(function(s){return "<button type='button' class='lqmb lqm' data-s='"+s+"'>"+esc(LQNAMES[s]||s)+"</button>";}).join('');
|
|
@@ -1416,53 +1589,74 @@ var ax=k*(tx-d.x)-c*d.vx,ay=k*(ty-d.y)-c*d.vy;
|
|
|
1416
1589
|
if(still){d.x=tx;d.y=ty;d.vx=0;d.vy=0;}else{d.vx+=ax;d.vy+=ay;d.x+=d.vx;d.y+=d.vy;}
|
|
1417
1590
|
if(!rest&&!pool){d.x=Math.max(d.cr+12,Math.min(S.w-d.cr-12,d.x));d.y=Math.max(140+d.cr,Math.min(S.h-150-d.cr-50,d.y));}
|
|
1418
1591
|
d.cr+=(tr-d.cr)*(still?1:0.06);
|
|
1419
|
-
var b=LQ.btns[i];if(b&&b.offsetWidth)d.lw=b.offsetWidth;if(b&&b.style){b.style.transform='translate('+Math.round(d.x-(b.offsetWidth||0)/2)+'px,'+Math.round(d.y
|
|
1592
|
+
var b=LQ.btns[i];if(b&&b.offsetWidth)d.lw=b.offsetWidth;if(b&&b.style){b.style.transform='translate('+Math.round(d.x-(b.offsetWidth||0)/2)+'px,'+Math.round(d.y+d.cr+(d.kind==='run'?28:4))+'px)';var show=!rest&&!pool;b.style.opacity=show?'1':'0';b.style.pointerEvents=show?'auto':'none';var lt=LQ.lts&&LQ.lts[i];if(lt)lt.tabIndex=show?0:-1;}}
|
|
1593
|
+
(LQ.gbtns||[]).forEach(function(gb){var g=gb.getAttribute('data-g'),top=1e9,sx=0,nn=0;D.forEach(function(d){if(d.kind!=='more'&&lqPara(d)===g){top=Math.min(top,d.y-d.cr);sx+=d.x;nn++;}});if(!nn||!gb.style)return;var gw=gb.offsetWidth||0,gh=gb.offsetHeight||0;gb.style.transform='translate('+Math.round(Math.max(12,Math.min(lqSize().w-gw-12,sx/nn-gw/2)))+'px,'+Math.round(Math.max(48,top-gh-30))+'px)';var gshow=!rest&&!pool&&!talk;gb.style.opacity=gshow?'1':'0';gb.style.pointerEvents=gshow?'auto':'none';gb.tabIndex=gshow?0:-1;});
|
|
1420
1594
|
(LQ.orbs||[]).forEach(function(o){var d=D[+o.getAttribute('data-i')];if(!d||!o.style)return;var R=Math.round(d.cr+20);o.style.width=o.style.height=(2*R)+'px';o.style.transform='translate('+Math.round(d.x-R)+'px,'+Math.round(d.y-R)+'px)';o.style.opacity=rest||pool?'0':'1';});
|
|
1421
|
-
var cb=$('lqcore');if(cb&&cb.style){cb.style.transform='translate('+Math.round(cx-
|
|
1595
|
+
var cb=$('lqcore');if(cb&&cb.style){var inC=LQ.theme&&LQ.theme.look!=='pearl'&&core.cr>60;cb.style.maxWidth=inC?Math.round(core.cr*1.5)+'px':'340px';var cw=cb.offsetWidth||0,chh=cb.offsetHeight||0;cb.style.transform='translate('+Math.round(cx-cw/2)+'px,'+(rest||inC?(inC?Math.round(core.y-chh/2):Math.round(core.y+core.cr+14)):56)+'px)';var ct=rest?(LQ.coreText||'All handled'):talk||pool?'':(LQ.coreText||'');cb.textContent=ct;cb.style.opacity=ct?'1':'0';cb.style.pointerEvents=ct?'auto':'none';}}
|
|
1422
1596
|
// The liquid: a metaball surface (Σ r²/d² = 1) shaded as chrome, on the GPU.
|
|
1423
1597
|
var LQ_FS=['precision highp float;',
|
|
1424
|
-
'uniform vec2 uRes;uniform float uT;uniform float uDpr;uniform
|
|
1425
|
-
'float field(vec2 p){float f=0.0;for(int i=0;i<
|
|
1598
|
+
'uniform vec2 uRes;uniform float uT;uniform float uDpr;uniform vec4 uB[NB];uniform vec3 uRip;uniform float uStyle;uniform float uContrast;uniform float uExposure;',
|
|
1599
|
+
'float field(vec2 p){float f=0.0;for(int i=0;i<NB;i++){vec4 b=uB[i];float r=b.z*uDpr;vec2 d=p-b.xy*uRes;f+=r*r/(dot(d,d)+1.0);}vec2 q=p/uDpr;return f*(1.0+0.025*sin(q.x*0.011+uT*0.7)*cos(q.y*0.009-uT*0.55)+0.012*sin(q.x*0.031-q.y*0.027+uT*1.3));}',
|
|
1600
|
+
'float tint(vec2 p){float f=0.0,t=0.0;for(int i=0;i<NB;i++){vec4 b=uB[i];float r=b.z*uDpr;vec2 d=p-b.xy*uRes;float c=r*r/(dot(d,d)+1.0);f+=c;t+=c*b.w;}return t/max(f,0.0001);}',
|
|
1426
1601
|
'float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}',
|
|
1427
|
-
'vec3
|
|
1428
|
-
'
|
|
1429
|
-
'
|
|
1602
|
+
'vec3 glassBg(vec2 p){vec2 q=p/uRes;vec3 c=vec3(0.918,0.927,0.936);',
|
|
1603
|
+
'vec2 a=vec2(0.22+0.05*sin(uT*0.13),0.28+0.06*cos(uT*0.11));vec2 b=vec2(0.8+0.04*cos(uT*0.09),0.22+0.05*sin(uT*0.15));vec2 e=vec2(0.52+0.06*sin(uT*0.07),0.62);',
|
|
1604
|
+
'c=mix(c,vec3(0.56,0.79,0.80),0.6*exp(-dot(q-a,q-a)*6.0));c=mix(c,vec3(0.98,0.79,0.60),0.55*exp(-dot(q-b,q-b)*7.0));c=mix(c,vec3(0.72,0.76,0.93),0.45*exp(-dot(q-e,q-e)*5.0));',
|
|
1605
|
+
'vec2 gp=mod(p,22.0*uDpr)-11.0*uDpr;c-=0.07*smoothstep(1.7*uDpr,0.7*uDpr,length(gp));return c;}',
|
|
1606
|
+
'float box(vec2 d,vec2 c,vec2 s){vec2 k=abs(d-c)/s;return 1.0-smoothstep(0.55,1.0,max(k.x,k.y));}',
|
|
1607
|
+
'vec3 irid(float x){return 0.5+0.5*cos(6.2832*(vec3(0.0,0.33,0.67)+x));}',
|
|
1608
|
+
'void main(){vec2 p=vec2(gl_FragCoord.x,uRes.y-gl_FragCoord.y);float f=field(p);float e=1.5*uDpr;',
|
|
1609
|
+
'vec2 g=vec2(field(p+vec2(e,0.0))-field(p-vec2(e,0.0)),field(p+vec2(0.0,e))-field(p-vec2(0.0,e)))/(2.0*e);float gm=length(g)+0.00001;',
|
|
1610
|
+
'float sd=(f-1.0)/gm;float m=smoothstep(-0.75*uDpr,0.75*uDpr,sd);vec2 gn=g/gm;',
|
|
1430
1611
|
'vec2 rc=uRip.xy*uRes;float rd=distance(p,rc)/uDpr;float age=uT-uRip.z;float ring=0.0;if(age>0.0&&age<4.0){float w=rd-age*300.0;ring=sin(w*0.07)*exp(-abs(w)*0.02)*(1.0-age/4.0);}',
|
|
1431
|
-
'vec2 rdir=normalize(p-rc+0.001);
|
|
1432
|
-
'vec3 n=normalize(vec3(-
|
|
1433
|
-
'
|
|
1434
|
-
'
|
|
1435
|
-
'
|
|
1436
|
-
'vec2
|
|
1437
|
-
'
|
|
1438
|
-
'
|
|
1439
|
-
'
|
|
1440
|
-
'
|
|
1441
|
-
'
|
|
1442
|
-
'vec3
|
|
1612
|
+
'vec2 rdir=normalize(p-rc+0.001);float rho=clamp(1.0/sqrt(max(f,0.0001)),0.0,1.0);',
|
|
1613
|
+
'vec3 n=normalize(vec3(-gn*rho+ring*0.18*rdir,sqrt(max(0.0,1.0-rho*rho))+0.02));',
|
|
1614
|
+
'vec3 r=vec3(2.0*n.z*n.xy,2.0*n.z*n.z-1.0);float fs=field(p-vec2(0.0,12.0*uDpr));float gr=hash(floor(p))-0.5;vec3 col;vec3 ink=vec3(0.0);float tf=tint(p);vec3 amb=vec3(0.95,0.62,0.22);',
|
|
1615
|
+
'if(uStyle<0.5){',
|
|
1616
|
+
'vec3 bg=glassBg(p+rdir*ring*6.0*uDpr);bg-=0.09*smoothstep(0.45,1.0,fs)*(1.0-m);',
|
|
1617
|
+
'vec2 off=n.xy*34.0*uDpr*rho;vec3 rf=vec3(glassBg(p+off*0.9).r,glassBg(p+off).g,glassBg(p+off*1.1).b);',
|
|
1618
|
+
'rf=mix(rf,vec3(dot(rf,vec3(0.33))),0.15)*vec3(0.985,0.995,1.0)+0.03;float fr=pow(1.0-n.z,3.0);rf=mix(rf,vec3(1.0),fr*0.6);rf=mix(rf,rf*vec3(1.08,0.86,0.62),tf*0.55);',
|
|
1619
|
+
'rf+=vec3(1.0)*pow(max(dot(n,normalize(vec3(-0.35,-0.6,1.0))),0.0),140.0)*0.95;rf+=vec3(1.0,0.97,0.92)*pow(max(dot(n,normalize(vec3(0.5,0.65,1.0))),0.0),24.0)*0.12;',
|
|
1620
|
+
'rf-=0.10*smoothstep(2.4*uDpr,0.0,sd);col=mix(bg,rf,m)+gr*0.008;',
|
|
1621
|
+
'}else if(uStyle<1.5){',
|
|
1622
|
+
'vec2 q=p/uRes;vec3 bg=vec3(0.03,0.034,0.04)+0.045*exp(-dot(q-vec2(0.5,0.2),q-vec2(0.5,0.2))*3.0);bg*=1.0-0.35*length(q-0.5);',
|
|
1623
|
+
'bg+=vec3(0.025,0.026,0.03)*smoothstep(0.5,1.0,fs)*(1.0-m)+vec3(0.5,0.55,0.62)*abs(ring)*0.05;',
|
|
1624
|
+
'float R=mix(0.16,1.0,pow(1.0-n.z,3.0));',
|
|
1625
|
+
'vec3 env=vec3(1.0,0.99,0.97)*box(r.xy,vec2(-0.42,-0.58),vec2(0.30,0.15))*1.2+vec3(0.85,0.9,1.0)*box(r.xy,vec2(0.78,-0.05),vec2(0.07,0.55))*0.55+vec3(0.6,0.5,0.4)*box(r.xy,vec2(0.0,0.85),vec2(0.9,0.12))*0.10;',
|
|
1626
|
+
'vec3 c=vec3(0.010,0.011,0.013)+env*R+irid((1.0-n.z)*1.3+0.15)*pow(1.0-n.z,2.2)*0.22;c+=amb*tf*(0.05+0.7*pow(1.0-n.z,2.0));c+=vec3(0.06)*smoothstep(1.8*uDpr,0.0,sd);',
|
|
1627
|
+
'col=mix(bg,c,m)+gr*0.012;ink=vec3(1.0);',
|
|
1628
|
+
'}else{',
|
|
1629
|
+
'vec2 q=p/uRes;vec3 bg=vec3(0.929,0.925,0.917)-0.06*length(q-vec2(0.5,0.35));bg-=vec3(0.13,0.13,0.12)*smoothstep(0.45,1.0,fs)*(1.0-m)+vec3(0.2)*abs(ring)*0.05;',
|
|
1630
|
+
'vec3 env=mix(vec3(0.96,0.965,0.97),vec3(0.36,0.38,0.42),smoothstep(-0.7,0.75,r.y));env=mix(env,vec3(0.62,0.6,0.57),smoothstep(0.55,1.0,r.y)*0.6);',
|
|
1631
|
+
'env+=vec3(1.0)*box(r.xy,vec2(-0.45,-0.5),vec2(0.32,0.2))*0.55+vec3(1.0)*box(r.xy,vec2(0.72,-0.1),vec2(0.08,0.45))*0.35;',
|
|
1632
|
+
'vec3 c=env*mix(0.82,1.0,pow(1.0-n.z,2.0))+irid((1.0-n.z)*0.9+0.55)*pow(1.0-n.z,1.6)*0.10;c=mix(c,c*vec3(1.1,0.86,0.6),tf*0.6);c-=0.08*smoothstep(1.6*uDpr,0.0,sd);',
|
|
1633
|
+
'col=mix(bg,c,m)+gr*0.014;',
|
|
1634
|
+
'}',
|
|
1635
|
+
'float line=(smoothstep(-2.0*uDpr,0.0,sd)-smoothstep(0.0,2.0*uDpr,sd))*uContrast;col=mix(col,ink,line);col*=mix(1.0,uExposure,0.5);',
|
|
1443
1636
|
'gl_FragColor=vec4(col,1.0);}'].join('');
|
|
1444
|
-
function
|
|
1637
|
+
function lqFS(n){return LQ_FS.split('NB').join(String(n));}
|
|
1638
|
+
function lqGL(c,n){c=c||$('lq');n=n||16;if(!c||!c.getContext)return null;var gl=null;try{gl=c.getContext('webgl',{antialias:false,alpha:false});}catch(e){}if(!gl)return null;
|
|
1445
1639
|
function sh(t,src){var o=gl.createShader(t);gl.shaderSource(o,src);gl.compileShader(o);return o;}
|
|
1446
|
-
var pr=gl.createProgram();gl.attachShader(pr,sh(gl.VERTEX_SHADER,'attribute vec2 a;void main(){gl_Position=vec4(a,0.0,1.0);}'));gl.attachShader(pr,sh(gl.FRAGMENT_SHADER,
|
|
1640
|
+
var pr=gl.createProgram();gl.attachShader(pr,sh(gl.VERTEX_SHADER,'attribute vec2 a;void main(){gl_Position=vec4(a,0.0,1.0);}'));gl.attachShader(pr,sh(gl.FRAGMENT_SHADER,lqFS(n)));gl.linkProgram(pr);
|
|
1447
1641
|
if(!gl.getProgramParameter(pr,gl.LINK_STATUS))return null;gl.useProgram(pr);
|
|
1448
1642
|
var bf=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,bf);gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,3,-1,-1,3]),gl.STATIC_DRAW);
|
|
1449
1643
|
var al=gl.getAttribLocation(pr,'a');gl.enableVertexAttribArray(al);gl.vertexAttribPointer(al,2,gl.FLOAT,false,0,0);
|
|
1450
|
-
var U={};['uRes','uT','uDpr','uB','uRip','
|
|
1451
|
-
return {gl:gl,c:c,U:U,out:new Float32Array(
|
|
1644
|
+
var U={};['uRes','uT','uDpr','uB','uRip','uStyle','uContrast','uExposure'].forEach(function(n){U[n]=gl.getUniformLocation(pr,n);});
|
|
1645
|
+
return {gl:gl,c:c,U:U,out:new Float32Array(n*4),t0:(window.performance&&performance.now)?performance.now():Date.now()};}
|
|
1452
1646
|
function lqDraw(G){var gl=G.gl,c=G.c,S=lqSize(),d=Math.min(window.devicePixelRatio||1,3),W=Math.max(1,Math.floor(S.w*d)),H=Math.max(1,Math.floor(S.h*d));
|
|
1453
1647
|
if(c.width!==W||c.height!==H){c.width=W;c.height=H;gl.viewport(0,0,W,H);}
|
|
1454
1648
|
var now=(window.performance&&performance.now)?performance.now():Date.now(),t=(now-G.t0)/1000,th=LQ.theme,still=th.still,o=G.out;LQ.t=t;
|
|
1455
|
-
for(var i=0;i<
|
|
1456
|
-
var put=function(k,x,y,r){if(k>15)return;var w=still?0:(k===0?1:0.3);o[k*
|
|
1649
|
+
for(var i=0;i<64;i++)o[i]=0;
|
|
1650
|
+
var put=function(k,x,y,r,a,lv){if(k>15)return;var w=still?0:(k===0?1:lv!=null?lv:0.3);o[k*4]=x/S.w+Math.sin(t*1.3+k*1.7)*0.004*w;o[k*4+1]=y/S.h+Math.cos(t*1.1+k*2.3)*0.005*w;o[k*4+2]=Math.max(0,r)*(1+0.035*Math.sin(t*2.0+k)*w);o[k*4+3]=a||0;};
|
|
1457
1651
|
put(0,LQ.core.x,LQ.core.y,LQ.core.cr);var k=1,tend=[];
|
|
1458
|
-
LQ.drops.forEach(function(dr){put(k++,dr.x,dr.y,dr.cr);if(dr.tendril&&LQ.mode==='aware'&&!LQ.talking)tend.push(dr);});
|
|
1652
|
+
LQ.drops.forEach(function(dr){put(k++,dr.x,dr.y,dr.cr,dr.kind==='you'||dr.ask?1:0,dr.live);if(dr.tendril&&LQ.mode==='aware'&&!LQ.talking)tend.push(dr);});
|
|
1459
1653
|
// a tendril is a chain of blobs close enough to always bridge (spaced at under 2.8r),
|
|
1460
1654
|
// never one blob floating in the gap: drawn whole or not at all
|
|
1461
1655
|
tend.forEach(function(dr){var dx=dr.x-LQ.core.x,dy=dr.y-LQ.core.y,len=Math.sqrt(dx*dx+dy*dy)||1,gap=len-LQ.core.cr-dr.cr;if(gap<=8)return;var n=Math.max(1,Math.min(3,Math.ceil(gap/40))),sp=gap/(n+1),rr=sp/2;if(k+n>15)return;for(var m=1;m<=n;m++){var at=LQ.core.cr+sp*m;put(k++,LQ.core.x+dx/len*at,LQ.core.y+dy/len*at,rr);}});
|
|
1462
1656
|
var P=LQ.pointer;if(P&&LQ.mode==='aware'&&!LQ.talking){var best=null,bd=1e9;[LQ.core].concat(LQ.drops).forEach(function(g){if(g.cr<18)return;var dd=Math.hypot(P[0]-g.x,P[1]-g.y)-g.cr;if(dd<bd){bd=dd;best=g;}});
|
|
1463
1657
|
if(best&&bd<230){var dx=P[0]-best.x,dy=P[1]-best.y,dl=Math.hypot(dx,dy)||1,reach=Math.min(dl,best.cr+70);put(15,best.x+dx/dl*reach,best.y+dy/dl*reach,8+20*(1-Math.max(0,bd)/230));}}
|
|
1464
|
-
var U=G.U;gl.uniform2f(U.uRes,W,H);gl.uniform1f(U.uT,t);gl.uniform1f(U.uDpr,d);gl.
|
|
1465
|
-
gl.uniform1f(U.
|
|
1658
|
+
var U=G.U;gl.uniform2f(U.uRes,W,H);gl.uniform1f(U.uT,t);gl.uniform1f(U.uDpr,d);gl.uniform4fv(U.uB,o);gl.uniform3f(U.uRip,LQ.ripple[0],LQ.ripple[1],still?-10:LQ.ripple[2]);
|
|
1659
|
+
gl.uniform1f(U.uStyle,th.look==='glass'?0:th.look==='pearl'?2:1);gl.uniform1f(U.uContrast,th.contrast?1:0);gl.uniform1f(U.uExposure,th.night?0.82:1);
|
|
1466
1660
|
gl.drawArrays(gl.TRIANGLES,0,3);}
|
|
1467
1661
|
function lqInit(){var bd=document.body;if(!bd||!bd.classList)return;lqTheme();bd.classList.add('lq-liquid');
|
|
1468
1662
|
['(prefers-color-scheme: light)','(prefers-contrast: more)','(forced-colors: active)','(prefers-reduced-transparency: reduce)','(prefers-reduced-motion: reduce)'].forEach(function(q){try{var mq=window.matchMedia&&window.matchMedia(q);if(mq&&mq.addEventListener)mq.addEventListener('change',lqTheme);else if(mq&&mq.addListener)mq.addListener(lqTheme);}catch(e){}});
|
|
@@ -1472,6 +1666,7 @@ var ia=$('lqask');if(ia&&ia.addEventListener){ia.addEventListener('focus',functi
|
|
|
1472
1666
|
var co=$('lqcore');if(co&&co.addEventListener)co.addEventListener('click',function(){var i=$('lqask');if(i&&i.focus)i.focus();});
|
|
1473
1667
|
var sk=$('lqsink');if(sk&&sk.addEventListener)sk.addEventListener('click',lqSink);
|
|
1474
1668
|
var bk=$('lqback');if(bk&&bk.addEventListener)bk.addEventListener('click',lqHome);
|
|
1669
|
+
var lk=$('lqlook');if(lk&&lk.querySelectorAll){var cur=lqLookGet();lk.querySelectorAll('button').forEach(function(b){b.setAttribute('aria-checked',b.getAttribute('data-look')===cur?'true':'false');b.addEventListener('click',function(){lqLook(b.getAttribute('data-look'));});});}
|
|
1475
1670
|
var gb=$('lqgo');if(gb&&gb.addEventListener)gb.addEventListener('click',lqGoWork);
|
|
1476
1671
|
var mn=document.querySelector('main');if(mn&&mn.addEventListener)mn.addEventListener('keydown',function(ev){if(ev&&ev.key==='Escape')lqSink();});
|
|
1477
1672
|
lqLoad(true);
|
package/watch.mjs
CHANGED
|
@@ -255,7 +255,7 @@ function watchBoard(hours = 24, now = Date.now()) {
|
|
|
255
255
|
const recent = d.news.filter((n) => n.watch === w.id && n.ts >= since && unseen(n, w)), k = kindOf(w.url), count = recent.filter((n) => needsYou(n, w)).length;
|
|
256
256
|
const b = d.briefs.find((x) => x.watch === w.id && x.ts >= since && unseen(x, w));
|
|
257
257
|
return { ...view(w), source: sourceOf(w.url), count, label: k ? `${count} ${k[count === 1 ? 0 : 1]}` : `${count} new`,
|
|
258
|
-
items: markNews(recent.slice(0, 8), [w]), ...(b ? { brief: { text: b.text, ts: b.ts, count: b.count } } : {}), ...(w.chat && w.chat.length ? { chat: w.chat } : {}) };
|
|
258
|
+
items: markNews(recent.slice(0, 8), [w]).map((n) => ({ ...n, need: needsYou(n, w) })), ...(b ? { brief: { text: b.text, ts: b.ts, count: b.count } } : {}), ...(w.chat && w.chat.length ? { chat: w.chat } : {}) };
|
|
259
259
|
});
|
|
260
260
|
return { hours, total: cards.reduce((s, c) => s + c.count, 0), cards, brief: briefOn() };
|
|
261
261
|
}
|