symbiot 0.46.0 → 0.47.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/home.mjs +36 -3
- package/package.json +1 -1
- package/server.mjs +4 -1
- package/ui.mjs +96 -32
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
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.47.1
|
|
6
|
+
|
|
7
|
+
- **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.
|
|
8
|
+
|
|
9
|
+
## 0.47.0
|
|
10
|
+
|
|
11
|
+
- **Tasks and agents in the liquid.** Opening Tasks or Agents re-forms the liquid: each agent at work is a sphere with small spheres revolving round it, named for what it's doing in plain words; work ready for your OK sits at the top; tasks waiting their turn sit below. One **Go** (or just say "go") starts everything waiting.
|
|
12
|
+
- **Tags float on their spheres.** Each sphere carries just its name, floating with a shadow. Tap the sphere to open its tag for a line about it and **Open** for the details.
|
|
13
|
+
- **Less to read.** An agent's block shows what it's doing and its to-do list; steps, files, tokens and cost are under "details".
|
|
14
|
+
- **No stray box.** The moving silver edge round open panels is gone.
|
|
15
|
+
|
|
5
16
|
## 0.46.0 — 2026-10-06
|
|
6
17
|
|
|
7
18
|
- **The Symbiot look on every part.** Whatever you open pools over the liquid as glass, with a slow silver edge: cards are glass, the main buttons chrome, the map's nodes small silver spheres.
|
package/home.mjs
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
// how you use them; this is the live data around them. Home's talk goes to the
|
|
8
8
|
// same Symbiot as every chat (mind.mjs converse), told what home shows.
|
|
9
9
|
import { watchBoard } from "./watch.mjs";
|
|
10
|
-
import { pendingReview } from "./tasks.mjs";
|
|
11
|
-
import { agentsList } from "./agents.mjs";
|
|
10
|
+
import { pendingReview, pushTasks } from "./tasks.mjs";
|
|
11
|
+
import { agentsList, runHandoff } from "./agents.mjs";
|
|
12
|
+
import { loadTasks } from "./core.mjs";
|
|
12
13
|
import { lanesState } from "./lanes.mjs";
|
|
13
14
|
import { converse, actIn, actNow, taskIn } from "./mind.mjs";
|
|
14
15
|
import { repoPathMap } from "./scan.mjs";
|
|
@@ -71,4 +72,36 @@ async function homeAsk(question, { ask, now = Date.now(), state } = {}) {
|
|
|
71
72
|
return { answer: r.reply, ...(r.did ? { did: r.did } : {}), steps: r.steps || [] };
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
// ---- the work scene: the liquid when you open Tasks or Agents ---------------------
|
|
76
|
+
// Plain words for someone who doesn't read diffs: what each agent is doing now,
|
|
77
|
+
// what's waiting its turn, what's done and waiting for your OK, and whether
|
|
78
|
+
// there's anything a Go would start. The details (steps, files, cost) stay one
|
|
79
|
+
// tap away, in the Agents panel.
|
|
80
|
+
const plain = (s, n = 64) => { s = String(s || "").replace(/[\`*_#>]/g, "").replace(/\s+/g, " ").trim(); return s.length > n ? s.slice(0, n - 1).replace(/\s+\S*$/, "") + "…" : s; };
|
|
81
|
+
function workScene({ deps = {} } = {}) {
|
|
82
|
+
const agents = deps.agents || (() => { try { return agentsList(); } catch { return []; } });
|
|
83
|
+
const pending = deps.pending || (() => { try { return pendingReview(); } catch { return []; } });
|
|
84
|
+
const tasks = deps.tasks || (() => { try { return loadTasks(); } catch { return []; } });
|
|
85
|
+
const seen = new Set(), running = [];
|
|
86
|
+
for (const a of agents()) {
|
|
87
|
+
if (a.status !== "running" || seen.has(a.path)) continue; seen.add(a.path);
|
|
88
|
+
const w = a.work || {}, todo = (w.todos || []).find((t) => t.status === "in_progress"), pg = a.progress;
|
|
89
|
+
running.push({ id: "run:" + a.path, path: a.path, name: a.name, doing: plain(todo ? todo.active : w.doing || "Working on it", 56), progress: pg || null, waiting: !!(a.ask && a.ask.questions && a.ask.questions.length) });
|
|
90
|
+
}
|
|
91
|
+
const ready = pending().filter((r) => r.path && !r.running && ((r.tasks || []).length || (r.files || []).length)).map((r) => ({ id: "ready:" + r.repo, repo: r.repo, count: (r.tasks || []).length }));
|
|
92
|
+
const busy = new Set(running.map((r) => r.name)), open = tasks().filter((t) => !t.done && !t.archived && !t.review && t.repo);
|
|
93
|
+
const waiting = open.map((t) => ({ id: "task:" + t.id, text: plain(t.text, 56), repo: t.repo, busy: busy.has(t.repo) }));
|
|
94
|
+
return { running, ready, waiting: waiting.slice(0, 12), waitingCount: waiting.length, canGo: waiting.filter((w) => !w.busy).length };
|
|
95
|
+
}
|
|
96
|
+
// Go: everything waiting goes to its repo's agent, the way Send to repos and an
|
|
97
|
+
// agent per repo would: briefs written, an agent started in each (or queued
|
|
98
|
+
// behind one already there). { started, queued, repos } or { error }.
|
|
99
|
+
function workGo({ push = pushTasks, run = runHandoff } = {}) {
|
|
100
|
+
const r = push({});
|
|
101
|
+
if (r.empty) return { started: 0, queued: 0, repos: [], note: "Nothing waiting to start." };
|
|
102
|
+
let started = 0, queued = 0; const repos = [];
|
|
103
|
+
for (const w of r.written || []) { const e = run(w.path); if (e && e.id && !e.busy && !e.blocked) started++; else queued++; repos.push(w.name); }
|
|
104
|
+
return { started, queued, repos, ...(r.unresolved && r.unresolved.length ? { unresolved: r.unresolved.map((u) => u.name) } : {}) };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export { homeState, homeContext, homeAsk, workScene, workGo };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "symbiot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.47.1",
|
|
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": {
|
package/server.mjs
CHANGED
|
@@ -26,7 +26,7 @@ import { linksState, linkSite, checkLink, unlinkSite } from "./links.mjs";
|
|
|
26
26
|
import { mindState, forget } from "./mind.mjs";
|
|
27
27
|
import { lanesTick, lanesState } from "./lanes.mjs";
|
|
28
28
|
import { adaptState, noteUse } from "./adapt.mjs";
|
|
29
|
-
import { homeState, homeAsk } from "./home.mjs";
|
|
29
|
+
import { homeState, homeAsk, workScene, workGo } from "./home.mjs";
|
|
30
30
|
import { phoneState, setPhoneLink, newCode, unpairPhone, pairComputer, forgetComputer, pollComputer, startPhone } from "./phone.mjs";
|
|
31
31
|
|
|
32
32
|
// The in-app update installs the EXACT newest version (not the `latest` tag, which
|
|
@@ -280,6 +280,9 @@ async function startApp({ bin, since = 7, all = false, c = PLAIN_COLOURS } = {})
|
|
|
280
280
|
// Home (home.mjs): the liquid's live data, and its talk; Adapt (adapt.mjs): its
|
|
281
281
|
// shape from how you use it (commit=1 when it wakes from rest, never mid-gesture).
|
|
282
282
|
if (u.pathname === "/api/home") return json(res, homeState({ fresh: u.searchParams.get("fresh") === "1" }));
|
|
283
|
+
// the work scene: what agents are doing, what's waiting, what's ready; Go starts what's waiting
|
|
284
|
+
if (u.pathname === "/api/work") return json(res, workScene());
|
|
285
|
+
if (u.pathname === "/api/work/go" && req.method === "POST") return json(res, workGo());
|
|
283
286
|
if (u.pathname === "/api/home/ask" && req.method === "POST") { const b = await readBody(req); return json(res, await homeAsk(b.question)); }
|
|
284
287
|
if (u.pathname === "/api/adapt") return json(res, adaptState({ from: String(u.searchParams.get("from") || ""), commit: u.searchParams.get("commit") === "1", ...(u.searchParams.has("touch") ? { touch: u.searchParams.get("touch") === "1" } : {}) }));
|
|
285
288
|
if (u.pathname === "/api/adapt/use" && req.method === "POST") { const b = await readBody(req); return json(res, noteUse(b)); }
|
package/ui.mjs
CHANGED
|
@@ -19,12 +19,19 @@ body.lq-liquid.lq-light #liquid{background:#EDEFF3}
|
|
|
19
19
|
#lq{position:absolute;inset:0;width:100%;height:100%;display:block}
|
|
20
20
|
body.lq-forced #lq{display:none}
|
|
21
21
|
#lqdrops{position:absolute;inset:0;pointer-events:none}
|
|
22
|
-
.lqd{position:absolute;left:0;top:0;display:flex;flex-direction:column;align-items:center;
|
|
23
|
-
.lqd
|
|
22
|
+
.lqd{position:absolute;left:0;top:0;display:flex;flex-direction:column;align-items:center;border-radius:999px;border:1px solid rgba(220,228,240,.35);background:rgba(4,5,7,.9);backdrop-filter:blur(2px);color:#EEF2F8;font:600 13.5px/1.2 var(--sans);pointer-events:auto;transition:opacity .5s ease,border-radius .25s ease;will-change:transform;box-shadow:0 16px 30px -12px rgba(0,0,0,.7),0 3px 8px rgba(0,0,0,.35)}
|
|
23
|
+
.lqd .lt{all:unset;box-sizing:border-box;display:flex;align-items:center;justify-content:center;min-height:44px;padding:8px 15px;cursor:pointer;text-align:center;max-width:240px}
|
|
24
|
+
.lqd .lt span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
25
|
+
.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}
|
|
27
|
+
.lqd .lt:focus-visible,.lqd .lgo:focus-visible{outline:2px solid currentColor;outline-offset:2px;border-radius:999px}
|
|
28
|
+
.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
|
+
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:12px;line-height:1.35;color:#A9B3C2;max-width:260px;padding:0 15px 2px;text-align:center}.lqd.open small{display:block}
|
|
24
31
|
.lqd.lq-you{border-color:rgba(255,255,255,.9)}
|
|
25
32
|
.lqd:hover{border-color:rgba(255,255,255,.95)}
|
|
26
33
|
.lqd:focus-visible,#lqcore:focus-visible,#lqask:focus-visible{outline:2px solid currentColor;outline-offset:3px}
|
|
27
|
-
body.lq-light .lqd{background:rgba(255,255,255,.
|
|
34
|
+
body.lq-light .lqd{background:rgba(255,255,255,.95);color:#10151C;border-color:rgba(20,30,45,.25)}
|
|
28
35
|
body.lq-light .lqd small{color:#4A5565}
|
|
29
36
|
body.lq-light .lqd.lq-you{border-color:#10151C}
|
|
30
37
|
body.lq-contrast .lqd{background:#000;color:#FFF;border:2px solid #FFF}
|
|
@@ -33,7 +40,7 @@ body.lq-light.lq-contrast .lqd{background:#FFF;color:#000;border:2px solid #000}
|
|
|
33
40
|
body.lq-light.lq-contrast .lqd small{color:#000}
|
|
34
41
|
body.lq-solid .lqd,body.lq-solid .lqglass{backdrop-filter:none;background:#05070A}
|
|
35
42
|
body.lq-light.lq-solid .lqd,body.lq-light.lq-solid .lqglass{background:#FFFFFF}
|
|
36
|
-
body.lq-touch .lqd{min-height:48px
|
|
43
|
+
body.lq-touch .lqd .lt{min-height:48px}
|
|
37
44
|
body.lq-keys .lqd:focus{outline:2px solid currentColor;outline-offset:3px}
|
|
38
45
|
#lqcore{position:absolute;left:0;top:0;min-height:44px;padding:0 16px;border:1px solid rgba(220,228,240,.22);border-radius:999px;background:rgba(4,5,7,.6);backdrop-filter:blur(8px);color:#C3CBD7;font:500 13px var(--sans);letter-spacing:.05em;cursor:pointer;transition:opacity .6s ease;white-space:nowrap}
|
|
39
46
|
body.lq-light #lqcore{color:#2B3644;background:rgba(255,255,255,.78);border-color:rgba(20,30,45,.18)}
|
|
@@ -64,9 +71,6 @@ body.lq-talking .lqd{opacity:.55}
|
|
|
64
71
|
body.lq-liquid.lq-pooled main{background:rgba(6,8,11,.86);backdrop-filter:blur(20px) saturate(1.25);border:1px solid transparent;background-clip:padding-box;box-shadow:0 0 0 1px rgba(214,222,234,.22),0 40px 90px -30px rgba(0,0,0,.85),inset 0 1px 0 rgba(255,255,255,.12)}
|
|
65
72
|
body.lq-liquid.lq-light.lq-pooled main{background:rgba(246,248,251,.9);box-shadow:0 0 0 1px rgba(20,30,45,.14),0 40px 90px -40px rgba(20,30,45,.45),inset 0 1px 0 #FFFFFF}
|
|
66
73
|
body.lq-liquid.lq-solid.lq-pooled main{backdrop-filter:none;background:var(--ink)}
|
|
67
|
-
.lqedge{position:fixed;top:64px;left:max(12px,4vw);right:max(12px,4vw);bottom:88px;z-index:6;border-radius:24px;pointer-events:none;padding:1px;background:linear-gradient(115deg,rgba(255,255,255,.0) 0%,rgba(255,255,255,.75) 18%,rgba(140,150,166,.3) 32%,rgba(255,255,255,.0) 48%,rgba(230,236,244,.6) 66%,rgba(255,255,255,0) 82%);background-size:260% 260%;-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask-composite:exclude;animation:lqsheen 9s linear infinite;display:none}
|
|
68
|
-
body.lq-liquid.lq-pooled .lqedge{display:block}
|
|
69
|
-
@keyframes lqsheen{0%{background-position:0% 50%}100%{background-position:260% 50%}}
|
|
70
74
|
body.lq-liquid .task,body.lq-liquid .agent,body.lq-liquid .rcard,body.lq-liquid .bcard,body.lq-liquid .drift,body.lq-liquid .q,body.lq-liquid .out,body.lq-liquid .tchat{background:linear-gradient(180deg,rgba(255,255,255,.075),rgba(255,255,255,.02));border:1px solid rgba(220,228,240,.16);border-radius:16px;box-shadow:inset 0 1px 0 rgba(255,255,255,.09),0 12px 30px -24px rgba(0,0,0,.9)}
|
|
71
75
|
body.lq-liquid.lq-light .task,body.lq-liquid.lq-light .agent,body.lq-liquid.lq-light .rcard,body.lq-liquid.lq-light .bcard,body.lq-liquid.lq-light .drift,body.lq-liquid.lq-light .q,body.lq-liquid.lq-light .out,body.lq-liquid.lq-light .tchat{background:linear-gradient(180deg,#FFFFFF,#F2F4F8);border-color:rgba(20,30,45,.12);box-shadow:inset 0 1px 0 #FFFFFF,0 12px 28px -24px rgba(20,30,45,.5)}
|
|
72
76
|
body.lq-liquid button.act{background:linear-gradient(180deg,#FFFFFF 0%,#D2D8E1 44%,#8E97A6 56%,#E6EAF0 100%);color:#06080B;border:1px solid rgba(255,255,255,.9);border-radius:999px;box-shadow:0 6px 18px -10px rgba(0,0,0,.7)}
|
|
@@ -80,6 +84,23 @@ body.lq-liquid.lq-light .tgroup{color:#3B4656}
|
|
|
80
84
|
body.lq-liquid h2,body.lq-liquid h3{letter-spacing:-.01em}
|
|
81
85
|
body.lq-liquid .fchip.on{background:linear-gradient(180deg,#FFFFFF,#C9D1DC);color:#06080B;border-color:#FFFFFF}
|
|
82
86
|
body.lq-contrast.lq-liquid .task,body.lq-contrast.lq-liquid .agent{border:2px solid currentColor}
|
|
87
|
+
|
|
88
|
+
/* the work scene: tasks and agents in the liquid, with spheres revolving round what's at work */
|
|
89
|
+
#lqorbits{position:absolute;inset:0;pointer-events:none}
|
|
90
|
+
.lqorbit{position:absolute;left:0;top:0;pointer-events:none;transition:opacity .5s ease}
|
|
91
|
+
.lqorbit span{position:absolute;inset:0;animation:lqorbit 3.4s linear infinite}
|
|
92
|
+
.lqorbit span:nth-child(2){animation-duration:4.6s;animation-delay:-1.6s}
|
|
93
|
+
.lqorbit span:nth-child(3){animation-duration:5.8s;animation-delay:-3.9s;animation-direction:reverse}
|
|
94
|
+
.lqorbit i{position:absolute;left:50%;top:0;width:10px;height:10px;margin:-5px 0 0 -5px;border-radius:50%;background:radial-gradient(circle at 35% 30%,#FFFFFF 0%,#C9CFD9 35%,#6F7887 75%,#2A2F38 100%);box-shadow:0 0 8px rgba(255,255,255,.35)}
|
|
95
|
+
.lqorbit.ask i{background:radial-gradient(circle at 35% 30%,#FFF4E2 0%,#F2A541 50%,#7A4A10 100%)}
|
|
96
|
+
@keyframes lqorbit{to{transform:rotate(360deg)}}
|
|
97
|
+
@media (prefers-reduced-motion: reduce){.lqorbit span{animation:none}.lqorbit span:nth-child(2){transform:rotate(120deg)}.lqorbit span:nth-child(3){transform:rotate(240deg)}}
|
|
98
|
+
#lqgo,#lqback{display:none}
|
|
99
|
+
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
|
+
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
|
+
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}
|
|
103
|
+
.lqd.lq-ready{border-color:#F2A541}
|
|
83
104
|
/* the live work of an agent: what it's doing, its to-dos, its steps, its files, its pace */
|
|
84
105
|
.wk{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(260px,100%),1fr));gap:12px;margin-top:10px}
|
|
85
106
|
.wkbox{border-radius:14px;padding:10px 12px;background:rgba(255,255,255,.035);border:1px solid rgba(220,228,240,.12)}
|
|
@@ -119,7 +140,7 @@ body.lq-light .wkfile .fb i{background:linear-gradient(90deg,#6F7887,#0F1720)}
|
|
|
119
140
|
.steps span{font-size:11px;padding:2px 8px;border-radius:999px;border:1px solid rgba(220,228,240,.22);color:var(--faint);white-space:nowrap}
|
|
120
141
|
.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}
|
|
121
142
|
body.lq-light .thinking{background-image:linear-gradient(90deg,#4A5565 0%,#0F1720 40%,#4A5565 60%,#4A5565 100%)}
|
|
122
|
-
@media (prefers-reduced-motion: reduce){.
|
|
143
|
+
@media (prefers-reduced-motion: reduce){.wkdoing,.thinking,.orb.run,.wktodo.in_progress i{animation:none}}
|
|
123
144
|
|
|
124
145
|
|
|
125
146
|
*{box-sizing:border-box}html,body{margin:0;height:100%}
|
|
@@ -273,7 +294,10 @@ label.check input{width:auto}
|
|
|
273
294
|
</style></head><body>
|
|
274
295
|
<div id="liquid" aria-label="Symbiot">
|
|
275
296
|
<canvas id="lq" aria-hidden="true"></canvas>
|
|
297
|
+
<div id="lqorbits"></div>
|
|
276
298
|
<div id="lqdrops"></div>
|
|
299
|
+
<button type="button" id="lqback">‹ Home</button>
|
|
300
|
+
<button type="button" id="lqgo"></button>
|
|
277
301
|
<button type="button" id="lqcore" title="talk to Symbiot"></button>
|
|
278
302
|
<div id="lqmore"></div>
|
|
279
303
|
<div id="lqtalk" aria-live="polite"></div>
|
|
@@ -296,7 +320,7 @@ label.check input{width:auto}
|
|
|
296
320
|
<button class="tab" data-tab="settings">Settings</button>
|
|
297
321
|
</div>
|
|
298
322
|
<main>
|
|
299
|
-
<div
|
|
323
|
+
<div id="lqsinkrow"><button class="ghost" type="button" id="lqsink" title="close it: it sinks back into the liquid (Esc)">Sink back</button></div>
|
|
300
324
|
<section id="panel-map">
|
|
301
325
|
<div class="profile muted" id="profile">Mapping your work…</div>
|
|
302
326
|
<div class="maprow">
|
|
@@ -725,8 +749,8 @@ if(!w){if(run)h+="<div class='bar'><i></i></div>";if(pg)h+="<div class='wkstats'
|
|
|
725
749
|
var stats=[];if(w.model)stats.push("<span class='wkstat'>"+esc(w.model.split('[')[0])+"</span>");stats.push("<span class='wkstat'><b>"+w.count+"</b> step"+(w.count===1?'':'s')+"</span>");
|
|
726
750
|
if(w.turns!=null)stats.push("<span class='wkstat'><b>"+w.turns+"</b> turns</span>");if(w.tokens)stats.push("<span class='wkstat'><b>"+(w.tokens>999999?(w.tokens/1e6).toFixed(1)+'M':w.tokens>999?Math.round(w.tokens/1000)+'k':w.tokens)+"</b> tokens</span>");
|
|
727
751
|
if(w.cost!=null)stats.push("<span class='wkstat'><b>$"+w.cost.toFixed(2)+"</b></span>");if(w.tests)stats.push("<span class='wktest"+(w.tests.failed?' bad':'')+"'>"+w.tests.passed+" passed · "+w.tests.failed+" failed</span>");if(w.errors)stats.push("<span class='wktest bad'>"+w.errors+" error"+(w.errors>1?'s':'')+"</span>");
|
|
728
|
-
h+="<div class='wkstats'>"+stats.join('')+"</div>";
|
|
729
752
|
if(run&&w.doing)h+="<div class='wkdoing'>"+esc(w.doing)+"…</div>";
|
|
753
|
+
var first=(w.final||'').split(String.fromCharCode(10))[0];if(!run&&first)h+="<div class='wkfinal'>"+esc(first.length>180?first.slice(0,179)+'\u2026':first)+"</div>";
|
|
730
754
|
var boxes=[];
|
|
731
755
|
if(w.todos&&w.todos.length||pg){var td="<div class='wkbox'><div class='wkh'>"+(pg?wkRing(pg.done,pg.total):'')+"<span>its to-do list</span></div>";
|
|
732
756
|
(w.todos||[]).forEach(function(t){td+="<div class='wktodo "+esc(t.status)+"'><i></i><span>"+esc(t.status==='in_progress'?t.active:t.text)+"</span></div>";});
|
|
@@ -736,8 +760,9 @@ w.steps.slice(-10).forEach(function(x){sp+="<div class='wkstep "+esc(x.status)+"
|
|
|
736
760
|
if(w.count>10)sp+="<div class='muted' style='font-size:11px;margin-top:4px'>and "+(w.count-10)+" earlier step"+(w.count-10>1?'s':'')+"</div>";boxes.push(sp+"</div>");}
|
|
737
761
|
var fs=Object.keys(w.files||{});if(fs.length){var mx=Math.max.apply(null,fs.map(function(f){return w.files[f];}))||1,fb="<div class='wkbox'><div class='wkh'><span>files it changed</span></div>";
|
|
738
762
|
fs.sort(function(x,y){return w.files[y]-w.files[x];}).slice(0,8).forEach(function(f){fb+="<div class='wkfile'><span class='fn'>"+esc(f)+"</span><span class='fb'><i style='width:"+Math.round(100*w.files[f]/mx)+"%'></i></span><span class='wm'>"+w.files[f]+"×</span></div>";});boxes.push(fb+"</div>");}
|
|
739
|
-
|
|
740
|
-
if(
|
|
763
|
+
var todoBox=boxes.length&&(w.todos&&w.todos.length||pg)?boxes.shift():'';
|
|
764
|
+
if(todoBox)h+="<div class='wk'>"+todoBox+"</div>";
|
|
765
|
+
h+="<details class='wkraw'><summary>details: steps, files, cost</summary><div class='wkstats'>"+stats.join('')+"</div>"+(boxes.length?"<div class='wk'>"+boxes.join('')+"</div>":"")+(!run&&w.final?"<div class='wkfinal'>"+esc(w.final)+"</div>":"")+"</details>";
|
|
741
766
|
return h;}
|
|
742
767
|
function loadAgents(){loadLanes();api('/api/agents').then(function(list){var el=document.getElementById('agentslist');
|
|
743
768
|
if(!list||!list.length){AGENTLIST=[];el.innerHTML="<div class='muted' style='margin-top:12px'>No agents yet. In <b>Tasks</b>, tick ideas and hit <b>Send to repos</b> (with an agent command set in Settings) — you'll watch it work here.</div>";stopAgentsPoll();return;}
|
|
@@ -1258,6 +1283,33 @@ function doUpdate(){updBusy=true;try{localStorage.setItem('symbiot_update_tried'
|
|
|
1258
1283
|
setInterval(heartbeat,4000);heartbeat(true);
|
|
1259
1284
|
window.addEventListener('focus',function(){heartbeat(true);}); // re-check for updates when you come back to the window
|
|
1260
1285
|
|
|
1286
|
+
|
|
1287
|
+
// ---- the work scene ------------------------------------------------------------------
|
|
1288
|
+
// Tasks and agents, in the liquid: what each agent is doing (spheres revolve round
|
|
1289
|
+
// it while it works), what's done and waiting for your OK, and what's waiting its
|
|
1290
|
+
// turn. One Go starts what's waiting. A tap opens the details; nothing else to read.
|
|
1291
|
+
function lqWork(){LQ.scene='work';LQ.mode='aware';var b=document.body;if(b&&b.classList){b.classList.add('lq-work');b.classList.remove('lq-pooled');}lqLoadWork();
|
|
1292
|
+
if(LQ.workTimer)clearInterval(LQ.workTimer);LQ.workTimer=setInterval(function(){if(LQ.scene==='work'&&LQ.mode!=='pool')lqLoadWork();},5000);}
|
|
1293
|
+
function lqHome(){LQ.scene='home';var b=document.body;if(b&&b.classList){b.classList.remove('lq-work');b.classList.remove('lq-pooled');}if(LQ.workTimer){clearInterval(LQ.workTimer);LQ.workTimer=null;}LQ.mode='aware';lqLoad(true);}
|
|
1294
|
+
function lqLoadWork(){api('/api/work').then(function(w){LQ.workData=w||{};lqBuildWork();}).catch(function(){});}
|
|
1295
|
+
function lqBuildWork(){var w=LQ.workData||{},S=lqSize(),cx=S.w/2,cy=S.h*0.5,list=[];LQ.bw=S.w;LQ.bh=S.h;
|
|
1296
|
+
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
|
+
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
|
+
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});});
|
|
1302
|
+
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
|
+
lqRelax(list,S,cx,cy,60*rs);
|
|
1304
|
+
var old={};LQ.drops.forEach(function(d){old[d.id]=d;});
|
|
1305
|
+
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
|
+
var nr=(w.running||[]).length,nw=w.waitingCount||0,nk=(w.ready||[]).length;
|
|
1307
|
+
LQ.coreText=(nr?nr+' at work':'Nobody at work')+(nw?' · '+nw+' waiting':'')+(nk?' · '+nk+' ready for your OK':'');
|
|
1308
|
+
var go=$('lqgo');if(go){var n=w.canGo||0;go.textContent=n?'Go \u00b7 start '+n+' task'+(n>1?'s':''):'';if(go.classList)go.classList.toggle('on',n>0);}
|
|
1309
|
+
lqLabels();}
|
|
1310
|
+
function lqGoWork(){var go=$('lqgo');if(go)go.disabled=true;api('/api/work/go',{}).then(function(r){if(go)go.disabled=false;
|
|
1311
|
+
var t=r&&r.error?r.error:r.started||r.queued?(r.started?'Started '+r.started:'')+(r.started&&r.queued?', ':'')+(r.queued?r.queued+' queued behind work already going':'')+'.':(r&&r.note)||'Nothing waiting to start.';
|
|
1312
|
+
LQ.talk.push({me:false,text:t});LQ.talk=LQ.talk.slice(-8);lqTalkShow(false);LQ.ripple=[0.5,0.5,LQ.t];lqLoadWork();}).catch(function(){if(go)go.disabled=false;});}
|
|
1261
1313
|
// ---- the liquid: the app's home ---------------------------------------------------
|
|
1262
1314
|
// One silver surface is the app. Its droplets: what only you can do, what's new on
|
|
1263
1315
|
// what you watch, and the parts of the app, sized and placed from how you use them
|
|
@@ -1277,7 +1329,7 @@ if(a.style.color){var ca=getComputedStyle(a).color,cb=getComputedStyle(b).color;
|
|
|
1277
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);}
|
|
1278
1330
|
LQ.theme=th;return th;}
|
|
1279
1331
|
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))};}
|
|
1280
|
-
function lqLoad(commit){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(){});}
|
|
1332
|
+
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(){});}
|
|
1281
1333
|
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;
|
|
1282
1334
|
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();
|
|
1283
1335
|
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);});
|
|
@@ -1305,31 +1357,38 @@ lqLabels();}
|
|
|
1305
1357
|
// bridge (r²/d² fields sum to 1 midway at d = 2·√(r1²+r2²)), with a margin so the
|
|
1306
1358
|
// wobble can't flicker a bridge open and shut, and room for their labels when
|
|
1307
1359
|
// they share a column. Used once, when the layout is solved; nothing fights it live.
|
|
1308
|
-
|
|
1360
|
+
// Tags float on their spheres, so two on the same row also keep a tag's width apart.
|
|
1361
|
+
function lqSep(a,b,dx,dy){var m=Math.max(a.r+b.r+70,2.4*Math.sqrt(a.r*a.r+b.r*b.r))+(Math.abs(dx)<150?48:0);return dy!=null&&Math.abs(dy)<56?Math.max(m,232):m;}
|
|
1309
1362
|
// Solve the layout: push targets apart until every pair keeps lqSep (and clear
|
|
1310
1363
|
// of the core), within the screen. If they can't all fit, every droplet gives up
|
|
1311
1364
|
// a little of its size (the liquid is conserved, not crowded) and it solves again.
|
|
1312
1365
|
function lqRelax(L,S,cx,cy,coreR){var n=L.length;
|
|
1313
1366
|
for(var pass=0;pass<6;pass++){
|
|
1314
1367
|
for(var it=0;it<220;it++){var moved=0;for(var i=0;i<n;i++){var a=L[i];
|
|
1315
|
-
for(var j=i+1;j<n;j++){var b=L[j],dx=a.tx-b.tx,dy=a.ty-b.ty,dd=Math.sqrt(dx*dx+dy*dy)||0.01,mn=lqSep(a,b,dx);if(dd<mn){var f=(mn-dd)/2/dd;a.tx+=dx*f;a.ty+=dy*f;b.tx-=dx*f;b.ty-=dy*f;moved++;}}
|
|
1368
|
+
for(var j=i+1;j<n;j++){var b=L[j],dx=a.tx-b.tx,dy=a.ty-b.ty,dd=Math.sqrt(dx*dx+dy*dy)||0.01,mn=lqSep(a,b,dx,dy);if(dd<mn){var f=(mn-dd)/2/dd;a.tx+=dx*f;a.ty+=dy*f;b.tx-=dx*f;b.ty-=dy*f;moved++;}}
|
|
1316
1369
|
var cdx=a.tx-cx,cdy=a.ty-cy,cd=Math.sqrt(cdx*cdx+cdy*cdy)||0.01,cm=Math.max(a.r+coreR+70,2.4*Math.sqrt(a.r*a.r+coreR*coreR));if(cd<cm){a.tx+=cdx*(cm-cd)/cd;a.ty+=cdy*(cm-cd)/cd;moved++;}
|
|
1317
1370
|
a.tx=Math.max(a.r+60,Math.min(S.w-a.r-60,a.tx));a.ty=Math.max(140+a.r,Math.min(S.h-200-a.r,a.ty));}if(!moved)break;}
|
|
1318
|
-
var bad=false;for(var i2=0;i2<n&&!bad;i2++)for(var j2=i2+1;j2<n;j2++){var p=L[i2],q=L[j2],ddx=p.tx-q.tx,ddy=p.ty-q.ty;if(Math.sqrt(ddx*ddx+ddy*ddy)<lqSep(p,q,ddx)-1){bad=true;break;}}
|
|
1371
|
+
var bad=false;for(var i2=0;i2<n&&!bad;i2++)for(var j2=i2+1;j2<n;j2++){var p=L[i2],q=L[j2],ddx=p.tx-q.tx,ddy=p.ty-q.ty;if(Math.sqrt(ddx*ddx+ddy*ddy)<lqSep(p,q,ddx,ddy)-1){bad=true;break;}}
|
|
1319
1372
|
if(!bad)return true;L.forEach(function(x){x.r*=0.9;});coreR*=0.9;}return false;}
|
|
1320
1373
|
function lqLabels(){var el=$('lqdrops');if(!el)return;
|
|
1321
|
-
el.innerHTML=LQ.drops.map(function(d,i){return "<
|
|
1322
|
-
LQ.btns=[];el.querySelectorAll('.lqd').forEach(function(b){LQ.btns.push(b);
|
|
1374
|
+
el.innerHTML=LQ.drops.map(function(d,i){return "<div class='lqd lq-"+d.kind+(LQ.openTag===d.id?' open':'')+"' data-i='"+i+"'><button type='button' class='lt' aria-expanded='"+(LQ.openTag===d.id)+"'><span>"+esc(d.title)+"</span></button>"+(d.sub?"<small>"+esc(d.sub)+"</small>":"")+"<button type='button' class='lgo'>Open ›</button></div>";}).join('');
|
|
1375
|
+
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
|
+
if(lt)lt.addEventListener('click',function(ev){if(d.kind==='more'){lqOpen(d,ev);return;}lqTag(d.id);});
|
|
1377
|
+
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);});}
|
|
1323
1379
|
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();
|
|
1324
1380
|
var c=$('lqcore');if(c)c.textContent=LQ.coreText||'';}
|
|
1381
|
+
// Tapping a sphere (or its name) opens its tag; tapping it again, or elsewhere, closes it.
|
|
1382
|
+
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');});}
|
|
1325
1383
|
function lqVia(ev){return LQ.talking?'talk':ev&&ev.pointerType==='touch'?'touch':ev&&ev.detail===0?'key':'click';}
|
|
1326
1384
|
function lqUse(shape,ev,via){api('/api/adapt/use',{shape:shape,from:LQ.last,via:via||lqVia(ev)});LQ.last=shape;}
|
|
1327
1385
|
function lqOpen(d,ev){if(!d)return;lqAct();
|
|
1328
1386
|
if(d.kind==='more'){LQ.moreOpen=!LQ.moreOpen;lqMore(d);return;}
|
|
1329
|
-
var S=lqSize();LQ.ripple=[d.x/S.w,d.y/S.h,LQ.t];lqUse(d.shape||'board',ev);
|
|
1387
|
+
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');}
|
|
1330
1388
|
function lqMore(d){var el=$('lqmore');if(!el)return;if(!LQ.moreOpen||!d){el.innerHTML='';return;}
|
|
1331
|
-
el.innerHTML=(d.more||[]).map(function(s){return "<button type='button' class='
|
|
1332
|
-
el.querySelectorAll('.lqm').forEach(function(b){b.addEventListener('click',function(ev){var s=b.getAttribute('data-s');LQ.moreOpen=false;el.innerHTML='';lqUse(s,ev);
|
|
1389
|
+
el.innerHTML=(d.more||[]).map(function(s){return "<button type='button' class='lqmb lqm' data-s='"+s+"'>"+esc(LQNAMES[s]||s)+"</button>";}).join('');
|
|
1390
|
+
el.querySelectorAll('.lqm').forEach(function(b){b.addEventListener('click',function(ev){var s=b.getAttribute('data-s');LQ.moreOpen=false;el.innerHTML='';lqUse(s,ev);lqGo(s);});});}
|
|
1391
|
+
function lqGo(shape){if(shape==='tasks'||shape==='agents'){lqWork();return;}lqPool(shape);}
|
|
1333
1392
|
function lqPool(shape){tabPicked=true;setTab(shape);var b=document.body;if(b&&b.classList)b.classList.add('lq-pooled');LQ.mode='pool';var m=document.querySelector('main');if(m&&m.focus)m.focus();}
|
|
1334
1393
|
function lqSink(){var b=document.body;if(b&&b.classList)b.classList.remove('lq-pooled');LQ.mode='aware';lqAct();lqLoad(false);}
|
|
1335
1394
|
function lqAct(){LQ.lastAct=Date.now();if(LQ.mode==='rest'){LQ.mode='aware';lqLoad(true);}}
|
|
@@ -1338,12 +1397,14 @@ function lqTalkShow(wait){var el=$('lqtalk');if(!el)return;el.innerHTML=LQ.talk.
|
|
|
1338
1397
|
function lqSay(){var i=$('lqask');var q=((i&&i.value)||'').trim();if(!q)return;i.value='';lqAct();
|
|
1339
1398
|
var low=q.toLowerCase().replace(/^(please |can you |could you )/,''),hit='';
|
|
1340
1399
|
Object.keys(LQNAMES).forEach(function(k){var nm=LQNAMES[k].toLowerCase();if(hit)return;['open ','show ','go to ','take me to '].forEach(function(v){if(low.indexOf(v+nm)===0||low.indexOf(v+'my '+nm)===0)hit=k;});if(low===nm)hit=k;});
|
|
1341
|
-
if(hit){lqTalkMode(true);lqUse(hit,null,'talk');
|
|
1400
|
+
if(hit){lqTalkMode(true);lqUse(hit,null,'talk');lqGo(hit);return;}
|
|
1401
|
+
if(/^(go|start|go for it|start them|start it)[.! ]*$/.test(low)){lqTalkMode(true);lqGoWork();return;}
|
|
1402
|
+
if(/^(home|back)[.! ]*$/.test(low)&&LQ.scene==='work'){lqHome();return;}
|
|
1342
1403
|
LQ.talk.push({me:true,text:q});LQ.talk=LQ.talk.slice(-8);lqTalkShow(true);
|
|
1343
1404
|
api('/api/home/ask',{question:q}).then(function(r){LQ.talk.push({me:false,steps:r.steps||[],text:r.error==='not-connected'?'Connect an AI in Settings to talk to me. Say "open settings".':(r.answer||r.error||'(no answer)')});LQ.talk=LQ.talk.slice(-8);lqTalkShow(false);LQ.ripple=[0.5,0.47,LQ.t];lqLoad(false);}).catch(function(e){LQ.talk.push({me:false,text:String((e&&e.message)||e)});lqTalkShow(false);});}
|
|
1344
1405
|
// The physics: each droplet a critically damped spring to its place (no wobble,
|
|
1345
1406
|
// no overshoot), pushed off its neighbours and the core where they'd overlap.
|
|
1346
|
-
function lqStep(){var S0=lqSize();if(
|
|
1407
|
+
function lqStep(){var S0=lqSize();if(S0.w!==LQ.bw||S0.h!==LQ.bh){if(LQ.scene==='work'&&LQ.workData)lqBuildWork();else if(LQ.adapt)lqBuild();}var S=S0,cx=S.w/2,cy=S.h*0.47,k=0.022,c=2*Math.sqrt(k),th=LQ.theme,still=th.still,rest=LQ.mode==='rest',pool=LQ.mode==='pool',talk=LQ.talking&&!pool;
|
|
1347
1408
|
var core=LQ.core,ctx=cx,cty=talk?S.h-44:cy,ctr=(rest?140:pool?50:talk?54+30*(LQ.talkWeight||0.35):92)*S.s;
|
|
1348
1409
|
if(!core.x){core.x=cx;core.y=cy;}core.x+=(ctx-core.x)*(still?1:0.06);core.y+=(cty-core.y)*(still?1:0.06);core.cr+=(ctr-core.cr)*(still?1:0.05);
|
|
1349
1410
|
var D=LQ.drops,N=D.length;
|
|
@@ -1355,8 +1416,9 @@ var ax=k*(tx-d.x)-c*d.vx,ay=k*(ty-d.y)-c*d.vy;
|
|
|
1355
1416
|
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;}
|
|
1356
1417
|
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));}
|
|
1357
1418
|
d.cr+=(tr-d.cr)*(still?1:0.06);
|
|
1358
|
-
var b=LQ.btns[i];if(b&&b.offsetWidth)d.lw=b.offsetWidth;if(b&&b.style){
|
|
1359
|
-
var
|
|
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-(b.offsetHeight||0)/2)+'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;}}
|
|
1420
|
+
(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-(cb.offsetWidth||0)/2)+'px,'+(rest?Math.round(core.y+core.cr+18):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';}}
|
|
1360
1422
|
// The liquid: a metaball surface (Σ r²/d² = 1) shaded as chrome, on the GPU.
|
|
1361
1423
|
var LQ_FS=['precision highp float;',
|
|
1362
1424
|
'uniform vec2 uRes;uniform float uT;uniform float uDpr;uniform vec3 uB[16];uniform vec3 uRip;uniform float uLight;uniform float uContrast;uniform vec4 uAccent;uniform float uExposure;',
|
|
@@ -1364,7 +1426,7 @@ var LQ_FS=['precision highp float;',
|
|
|
1364
1426
|
'float hash(vec2 p){return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453);}',
|
|
1365
1427
|
'vec3 env(vec3 r){float y=r.y;vec3 c=mix(mix(vec3(0.02,0.022,0.03),vec3(0.36,0.38,0.43),uLight),vec3(0.82,0.84,0.90),smoothstep(-0.35,0.55,y));c+=vec3(1.0)*pow(max(0.0,1.0-abs(y-0.18+0.04*sin(uT*0.3))*5.0),4.0)*1.1;c+=vec3(0.75,0.82,1.0)*pow(max(0.0,r.x),10.0)*0.9;c+=vec3(1.0,0.93,0.86)*pow(max(0.0,-r.x),14.0)*0.5;c*=0.92+0.08*sin(r.x*4.0+r.y*3.0+uT*0.4);return c;}',
|
|
1366
1428
|
'void main(){vec2 p=vec2(gl_FragCoord.x,uRes.y-gl_FragCoord.y);float f=field(p);float e=2.0*uDpr;',
|
|
1367
|
-
'float fx=field(p+vec2(e,0.0))-field(p-vec2(e,0.0));float fy=field(p+vec2(0.0,e))-field(p-vec2(0.0,e));',
|
|
1429
|
+
'float fx=field(p+vec2(e,0.0))-field(p-vec2(e,0.0));float fy=field(p+vec2(0.0,e))-field(p-vec2(0.0,e));float sd=(f-1.0)/(length(vec2(fx,fy))/(2.0*e)+0.00001);',
|
|
1368
1430
|
'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);}',
|
|
1369
1431
|
'vec2 rdir=normalize(p-rc+0.001);fx+=ring*0.22*rdir.x;fy+=ring*0.22*rdir.y;',
|
|
1370
1432
|
'vec3 n=normalize(vec3(-fx*6.0,fy*6.0,1.0));vec3 r=reflect(vec3(0.0,0.0,-1.0),n);vec3 metal=env(r);',
|
|
@@ -1372,10 +1434,10 @@ var LQ_FS=['precision highp float;',
|
|
|
1372
1434
|
'metal=mix(metal,metal*(0.55+0.9*uAccent.rgb),uAccent.a*0.3);',
|
|
1373
1435
|
'metal=mix(metal,metal*(1.0-0.5*fres)+0.04,uLight);metal*=uExposure;',
|
|
1374
1436
|
'vec2 uv=p/uRes;vec2 cell=floor(p/(2.0*uDpr));float s=step(0.9975,hash(cell))*(0.35+0.25*sin(uT*1.5+hash(cell+3.1)*6.28))*(1.0-uLight);',
|
|
1375
|
-
'vec3 dark=vec3(0.012,0.014,0.02)+vec3(s);dark*=1.0-0.55*length(uv-0.5);dark+=vec3(0.05,0.055,0.07)*smoothstep(0.
|
|
1376
|
-
'vec3 pearl=vec3(0.93,0.94,0.955)-0.07*length(uv-0.5);pearl-=vec3(0.
|
|
1437
|
+
'vec3 dark=vec3(0.012,0.014,0.02)+vec3(s);dark*=1.0-0.55*length(uv-0.5);dark+=vec3(0.05,0.055,0.07)*smoothstep(0.65,1.0,f);',
|
|
1438
|
+
'float fs=field(p-vec2(0.0,9.0*uDpr));vec3 pearl=vec3(0.93,0.94,0.955)-0.07*length(uv-0.5);pearl-=vec3(0.12)*smoothstep(0.6,0.98,fs);',
|
|
1377
1439
|
'vec3 bg=mix(dark,pearl,uLight)*mix(1.0,uExposure,0.5);bg+=mix(vec3(0.55,0.6,0.7),vec3(-0.3),uLight)*abs(ring)*0.09;',
|
|
1378
|
-
'float
|
|
1440
|
+
'float m=smoothstep(-0.75*uDpr,0.75*uDpr,sd);',
|
|
1379
1441
|
'float line=(smoothstep(0.86,0.97,f)-smoothstep(0.97,1.08,f))*uContrast;',
|
|
1380
1442
|
'vec3 col=mix(bg,metal,m);col=mix(col,mix(vec3(1.0),vec3(0.0),uLight),line);',
|
|
1381
1443
|
'gl_FragColor=vec4(col,1.0);}'].join('');
|
|
@@ -1387,7 +1449,7 @@ var bf=gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER,bf);gl.bufferData(gl.ARRA
|
|
|
1387
1449
|
var al=gl.getAttribLocation(pr,'a');gl.enableVertexAttribArray(al);gl.vertexAttribPointer(al,2,gl.FLOAT,false,0,0);
|
|
1388
1450
|
var U={};['uRes','uT','uDpr','uB','uRip','uLight','uContrast','uAccent','uExposure'].forEach(function(n){U[n]=gl.getUniformLocation(pr,n);});
|
|
1389
1451
|
return {gl:gl,c:c,U:U,out:new Float32Array(48),t0:(window.performance&&performance.now)?performance.now():Date.now()};}
|
|
1390
|
-
function lqDraw(G){var gl=G.gl,c=G.c,S=lqSize(),d=Math.min(window.devicePixelRatio||1,
|
|
1452
|
+
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));
|
|
1391
1453
|
if(c.width!==W||c.height!==H){c.width=W;c.height=H;gl.viewport(0,0,W,H);}
|
|
1392
1454
|
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;
|
|
1393
1455
|
for(var i=0;i<48;i++)o[i]=0;
|
|
@@ -1404,11 +1466,13 @@ gl.uniform1f(U.uLight,th.light?1:0);gl.uniform1f(U.uContrast,th.contrast?1:0);va
|
|
|
1404
1466
|
gl.drawArrays(gl.TRIANGLES,0,3);}
|
|
1405
1467
|
function lqInit(){var bd=document.body;if(!bd||!bd.classList)return;lqTheme();bd.classList.add('lq-liquid');
|
|
1406
1468
|
['(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){}});
|
|
1407
|
-
var L=$('liquid');if(L&&L.addEventListener){L.addEventListener('pointermove',function(ev){LQ.pointer=[ev.clientX,ev.clientY];if(ev.pointerType==='touch')LQ.touch=true;lqAct();});L.addEventListener('pointerdown',function(ev){if(ev.pointerType==='touch')LQ.touch=true;lqAct();});}
|
|
1469
|
+
var L=$('liquid');if(L&&L.addEventListener){L.addEventListener('click',function(ev){var t=ev&&ev.target;if(t&&t.closest&&t.closest('button,input,form,.lqd'))return;var x=ev.clientX,y=ev.clientY,hit=null;LQ.drops.forEach(function(d){if(!hit&&Math.hypot(x-d.x,y-d.y)<=d.cr+8)hit=d;});if(hit){if(hit.kind==='more')lqOpen(hit,ev);else lqTag(hit.id);}else if(LQ.openTag)lqTag(LQ.openTag);});L.addEventListener('pointermove',function(ev){LQ.pointer=[ev.clientX,ev.clientY];if(ev.pointerType==='touch')LQ.touch=true;lqAct();});L.addEventListener('pointerdown',function(ev){if(ev.pointerType==='touch')LQ.touch=true;lqAct();});}
|
|
1408
1470
|
var f=$('lqform');if(f&&f.addEventListener)f.addEventListener('submit',function(ev){if(ev&&ev.preventDefault)ev.preventDefault();lqSay();});
|
|
1409
|
-
var ia=$('lqask');if(ia&&ia.addEventListener){ia.addEventListener('focus',function(){lqTalkMode(true);lqAct();});ia.addEventListener('blur',function(){if(!ia.value&&!LQ.talk.length)lqTalkMode(false);});ia.addEventListener('keydown',function(ev){if(ev&&ev.key==='Escape'){if(LQ.mode==='pool'){lqSink();return;}ia.value='';LQ.talk=[];lqTalkShow(false);lqTalkMode(false);if(ia.blur)ia.blur();}});}
|
|
1471
|
+
var ia=$('lqask');if(ia&&ia.addEventListener){ia.addEventListener('focus',function(){lqTalkMode(true);lqAct();});ia.addEventListener('blur',function(){if(!ia.value&&!LQ.talk.length)lqTalkMode(false);});ia.addEventListener('keydown',function(ev){if(ev&&ev.key==='Escape'){if(LQ.mode==='pool'){lqSink();return;}if(LQ.scene==='work'&&!ia.value){lqHome();return;}ia.value='';LQ.talk=[];lqTalkShow(false);lqTalkMode(false);if(ia.blur)ia.blur();}});}
|
|
1410
1472
|
var co=$('lqcore');if(co&&co.addEventListener)co.addEventListener('click',function(){var i=$('lqask');if(i&&i.focus)i.focus();});
|
|
1411
1473
|
var sk=$('lqsink');if(sk&&sk.addEventListener)sk.addEventListener('click',lqSink);
|
|
1474
|
+
var bk=$('lqback');if(bk&&bk.addEventListener)bk.addEventListener('click',lqHome);
|
|
1475
|
+
var gb=$('lqgo');if(gb&&gb.addEventListener)gb.addEventListener('click',lqGoWork);
|
|
1412
1476
|
var mn=document.querySelector('main');if(mn&&mn.addEventListener)mn.addEventListener('keydown',function(ev){if(ev&&ev.key==='Escape')lqSink();});
|
|
1413
1477
|
lqLoad(true);
|
|
1414
1478
|
// the only clocks: rest when left alone, fresh data now and then, the hour (night)
|