@parley-im/parley 0.2.9 → 0.2.10
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/package.json +1 -1
- package/web/app.css +46 -19
- package/web/chat.html +3 -2
- package/web/chat.js +30 -4
- package/web/ended.html +2 -2
- package/web/home.js +18 -8
- package/web/icon-180.png +0 -0
- package/web/icon.png +0 -0
- package/web/icon.svg +1 -1
- package/web/mark.js +43 -0
- package/web/missing.html +2 -2
- package/web/panel.html +2 -2
package/package.json
CHANGED
package/web/app.css
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
control panel and parley's site. Dark, with lilac for the accents, for the
|
|
3
3
|
people who use Claude Code and Codex: Instrument Sans for everything
|
|
4
4
|
people read, JetBrains Mono for the wordmark, labels, files and numbers.
|
|
5
|
-
The
|
|
6
|
-
|
|
5
|
+
The mark (mark.js) is equilateral triangles gathered into hexagons, and
|
|
6
|
+
its triangles do the motion: they say when a page opens, when the agent
|
|
7
|
+
is reading or writing, when a reply lands, and when the sender's computer
|
|
8
|
+
is offline. Six of them mark the agent's words. The fonts are served from here, never from a
|
|
7
9
|
font service, so guest pages only ever talk to parley. */
|
|
8
10
|
|
|
9
11
|
@font-face { font-family: "Instrument Sans"; src: url("/static/fonts/instrument-sans.woff2") format("woff2"); font-weight: 400 700; font-style: normal; font-display: swap; }
|
|
@@ -24,7 +26,6 @@
|
|
|
24
26
|
--accent-fill: #9d92ff; /* buttons, with dark text */
|
|
25
27
|
--accent-soft: #221f3d;
|
|
26
28
|
--accent-ink: #0c0c0d;
|
|
27
|
-
--tile: #9d92ff; /* the mark's tile */
|
|
28
29
|
--guest: #221f3d; /* your own messages */
|
|
29
30
|
--guest-ink: #e6e2ff;
|
|
30
31
|
--ok: #3ccf7a;
|
|
@@ -57,11 +58,27 @@ code, kbd, pre { font-family: var(--mono); }
|
|
|
57
58
|
|
|
58
59
|
/* ---------- shared pieces ---------- */
|
|
59
60
|
|
|
60
|
-
/* The mark
|
|
61
|
-
|
|
62
|
-
.mark
|
|
63
|
-
.mark
|
|
64
|
-
.
|
|
61
|
+
/* The mark: a ring of thirty triangles around a ring of space and a core of
|
|
62
|
+
six, two tones each by which way a triangle points. */
|
|
63
|
+
.mark { display: inline-block; width: 26px; height: 26px; flex: none; }
|
|
64
|
+
.mark svg { display: block; width: 100%; height: 100%; overflow: visible; }
|
|
65
|
+
.r1 { fill: #9d92ff; } .r2 { fill: #8a7ff0; }
|
|
66
|
+
.c1 { fill: #d2ccff; } .c2 { fill: #bdb4ff; }
|
|
67
|
+
.g1 { fill: #6b60d6; } .g2 { fill: #5e54c4; }
|
|
68
|
+
.mark path { transition: fill 0.4s ease; }
|
|
69
|
+
/* Offline, the ring goes dark and the core turns red; ended, it all dims. */
|
|
70
|
+
.app.offline .topbar .mark .r1, .mark.dim .r1, .mark.dim .c1 { fill: #2e2a45; }
|
|
71
|
+
.app.offline .topbar .mark .r2, .mark.dim .r2, .mark.dim .c2 { fill: #28253c; }
|
|
72
|
+
.app.offline .topbar .mark .c1 { fill: #ff6b63; }
|
|
73
|
+
.app.offline .topbar .mark .c2 { fill: #e85a53; }
|
|
74
|
+
/* As a page opens, the ring builds around and then the core arrives. */
|
|
75
|
+
.mark.opening .r1, .mark.opening .r2 { transform-box: fill-box; transform-origin: center; animation: build 0.45s ease-out both; }
|
|
76
|
+
.mark.opening .c1, .mark.opening .c2 { transform-box: view-box; transform-origin: 32px 32px; animation: arrive 0.5s ease-out 1.05s both; }
|
|
77
|
+
@keyframes build { from { opacity: 0; transform: scale(0.2); } }
|
|
78
|
+
@keyframes arrive { from { opacity: 0; transform: scale(0.3); } 70% { transform: scale(1.12); } }
|
|
79
|
+
/* A reply lands: the core pulses once. */
|
|
80
|
+
.mark.pulse .c1, .mark.pulse .c2 { transform-box: view-box; transform-origin: 32px 32px; animation: pulse-core 0.8s ease-in-out; }
|
|
81
|
+
@keyframes pulse-core { 40% { transform: scale(1.3); } 75% { transform: scale(0.94); } }
|
|
65
82
|
.wordmark { font-family: var(--mono); font-weight: 700; letter-spacing: -0.04em; }
|
|
66
83
|
|
|
67
84
|
.topbar {
|
|
@@ -166,8 +183,12 @@ body:has(> .app) { overflow: hidden; overscroll-behavior: none; }
|
|
|
166
183
|
position: absolute; left: 0; top: 0; width: 14px; text-align: center;
|
|
167
184
|
font-family: var(--mono); font-size: 14px; line-height: 1.55;
|
|
168
185
|
}
|
|
169
|
-
/* The agent's words
|
|
170
|
-
.msg.agent::before {
|
|
186
|
+
/* The agent's words are marked with the mark's core: six triangles. */
|
|
187
|
+
.msg.agent::before {
|
|
188
|
+
content: ""; left: 2px; top: 6px; width: 11px; height: 12.7px;
|
|
189
|
+
clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
|
|
190
|
+
background: conic-gradient(#d2ccff 0deg 60deg, #bdb4ff 60deg 120deg, #d2ccff 120deg 180deg, #bdb4ff 180deg 240deg, #d2ccff 240deg 300deg, #bdb4ff 300deg 360deg);
|
|
191
|
+
}
|
|
171
192
|
/* The interviewer's words are what people read: set for reading. */
|
|
172
193
|
.msg.agent .body { font-size: 16.5px; line-height: 1.6; }
|
|
173
194
|
/* Your own words are compact and plainly yours, on the right. */
|
|
@@ -222,15 +243,21 @@ body:has(> .app) { overflow: hidden; overscroll-behavior: none; }
|
|
|
222
243
|
.body th { background: var(--subtle); font-weight: 600; white-space: nowrap; }
|
|
223
244
|
.body tr:last-child td { border-bottom: 0; }
|
|
224
245
|
|
|
225
|
-
/*
|
|
226
|
-
|
|
227
|
-
.thinking
|
|
228
|
-
.thinking
|
|
229
|
-
.thinking
|
|
230
|
-
|
|
246
|
+
/* Working: the mark, small. Light runs around its empty ring while the agent
|
|
247
|
+
writes; while it looks things up (.reading), its core flickers instead. */
|
|
248
|
+
.thinking { display: inline-block; width: 24px; height: 24px; margin-top: 2px; vertical-align: top; }
|
|
249
|
+
.thinking svg { display: block; width: 100%; height: 100%; }
|
|
250
|
+
.thinking .g1, .thinking .g2 { opacity: 0; animation: chase 1.8s linear infinite; }
|
|
251
|
+
.thinking.reading .g1, .thinking.reading .g2 { animation: none; }
|
|
252
|
+
.thinking.reading .c1, .thinking.reading .c2 { animation: scan 1.2s ease-in-out infinite; }
|
|
253
|
+
@keyframes chase { 0% { opacity: 0; } 4% { opacity: 1; } 22% { opacity: 0; } 100% { opacity: 0; } }
|
|
254
|
+
@keyframes scan { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
|
|
231
255
|
@keyframes blink { 0%, 49% { opacity: 1; } 50%, 100% { opacity: 0; } }
|
|
232
256
|
.cursor::after { content: ""; display: inline-block; width: 7px; height: 1.05em; background: var(--accent); border-radius: 1px; margin-left: 3px; vertical-align: -3px; animation: blink 1.06s steps(1) infinite; }
|
|
233
|
-
@media (prefers-reduced-motion: reduce) {
|
|
257
|
+
@media (prefers-reduced-motion: reduce) {
|
|
258
|
+
.cursor::after, .dot.busy, .thinking .c1, .thinking .c2, .mark.opening path, .mark.pulse path { animation: none; }
|
|
259
|
+
.thinking .g1, .thinking .g2 { animation: none; opacity: 0.6; }
|
|
260
|
+
}
|
|
234
261
|
|
|
235
262
|
.dock { border-top: 1px solid var(--line); background: var(--bg); }
|
|
236
263
|
.dock .chat-inner { padding: 8px 16px calc(8px + env(safe-area-inset-bottom)); }
|
|
@@ -387,7 +414,7 @@ body.panel { overflow: auto; }
|
|
|
387
414
|
|
|
388
415
|
.lost { min-height: 100dvh; display: grid; place-items: center; padding: 16px; text-align: center; }
|
|
389
416
|
.lost.inline { min-height: 100%; }
|
|
390
|
-
.lost .mark { width:
|
|
417
|
+
.lost .mark { width: 52px; height: 52px; margin: 0 auto 16px; }
|
|
391
418
|
.lost h1 { font-size: 20px; margin: 0 0 6px; letter-spacing: -0.01em; }
|
|
392
419
|
.lost p { color: var(--muted); margin: 0 auto; max-width: 34ch; }
|
|
393
420
|
|
|
@@ -466,7 +493,7 @@ span.live { display: inline-flex; align-items: center; gap: 7px; font-size: 13px
|
|
|
466
493
|
.demo-msgs { list-style: none; margin: 0; padding: 18px 18px 6px; min-height: 340px; display: flex; flex-direction: column; gap: 14px; }
|
|
467
494
|
.demo .msg { margin: 0; animation: rise 0.25s ease-out; }
|
|
468
495
|
.demo .msg.agent .body { font-size: 15.5px; }
|
|
469
|
-
.demo .msg.agent::before { top:
|
|
496
|
+
.demo .msg.agent::before { top: 5px; }
|
|
470
497
|
.demo .msg.guest .bubble { font-size: 14.5px; max-width: 86%; }
|
|
471
498
|
.demo .steps { margin-top: 2px; }
|
|
472
499
|
.demo-say { margin: 0 14px 14px; padding: 8px 8px 8px 14px; border: 1.5px solid var(--line-strong); border-radius: 13px; color: var(--faint); font-size: 15px; display: flex; align-items: center; justify-content: space-between; }
|
package/web/chat.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<meta property="og:description" content="{{PREVIEW_TEXT}}">
|
|
11
11
|
<meta property="og:site_name" content="parley">
|
|
12
12
|
<title>{{TITLE}}</title>
|
|
13
|
-
<link rel="icon" href="/static/icon.svg?v=
|
|
13
|
+
<link rel="icon" href="/static/icon.svg?v=6" type="image/svg+xml">
|
|
14
14
|
<link rel="stylesheet" href="/static/app.css">
|
|
15
15
|
</head>
|
|
16
16
|
<body>
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
<nav class="crumbs" id="crumbs">
|
|
20
20
|
<!-- What parley is, in a new tab so the conversation stays put. -->
|
|
21
21
|
<a class="brand" id="home-link" href="https://parley.im" target="_blank" rel="noopener" title="parley: let people talk to your agent">
|
|
22
|
-
<span class="mark" aria-hidden="true"><svg viewBox="0 0 64 64"><
|
|
22
|
+
<span class="mark" aria-hidden="true"><svg viewBox="0 0 64 64" aria-hidden="true"><path class="r1" d="M32 7L39.2 11.2L32 15.3Z"/><path class="r2" d="M39.2 11.2L39.2 19.5L32 15.3Z"/><path class="r1" d="M39.2 11.2L46.4 15.3L39.2 19.5Z"/><path class="r2" d="M46.4 15.3L46.4 23.7L39.2 19.5Z"/><path class="r1" d="M46.4 15.3L53.7 19.5L46.4 23.7Z"/><path class="r2" d="M53.7 19.5L53.7 27.8L46.4 23.7Z"/><path class="r1" d="M46.4 23.7L53.7 27.8L46.4 32Z"/><path class="r2" d="M53.7 27.8L53.7 36.2L46.4 32Z"/><path class="r1" d="M46.4 32L53.7 36.2L46.4 40.3Z"/><path class="r2" d="M53.7 36.2L53.7 44.5L46.4 40.3Z"/><path class="r1" d="M46.4 40.3L53.7 44.5L46.4 48.7Z"/><path class="r2" d="M46.4 40.3L46.4 48.7L39.2 44.5Z"/><path class="r1" d="M39.2 44.5L46.4 48.7L39.2 52.8Z"/><path class="r2" d="M39.2 44.5L39.2 52.8L32 48.7Z"/><path class="r1" d="M32 48.7L39.2 52.8L32 57Z"/><path class="r2" d="M32 48.7L32 57L24.8 52.8Z"/><path class="r1" d="M24.8 44.5L32 48.7L24.8 52.8Z"/><path class="r2" d="M24.8 44.5L24.8 52.8L17.6 48.7Z"/><path class="r1" d="M17.6 40.3L24.8 44.5L17.6 48.7Z"/><path class="r2" d="M17.6 40.3L17.6 48.7L10.3 44.5Z"/><path class="r1" d="M10.3 36.2L17.6 40.3L10.3 44.5Z"/><path class="r2" d="M17.6 32L17.6 40.3L10.3 36.2Z"/><path class="r1" d="M10.3 27.8L17.6 32L10.3 36.2Z"/><path class="r2" d="M17.6 23.7L17.6 32L10.3 27.8Z"/><path class="r1" d="M10.3 19.5L17.6 23.7L10.3 27.8Z"/><path class="r2" d="M17.6 15.3L17.6 23.7L10.3 19.5Z"/><path class="r1" d="M17.6 15.3L24.8 19.5L17.6 23.7Z"/><path class="r2" d="M24.8 11.2L24.8 19.5L17.6 15.3Z"/><path class="r1" d="M24.8 11.2L32 15.3L24.8 19.5Z"/><path class="r2" d="M32 7L32 15.3L24.8 11.2Z"/><path class="c1" d="M32 23.7L39.2 27.8L32 32Z"/><path class="c2" d="M39.2 27.8L39.2 36.2L32 32Z"/><path class="c1" d="M32 32L39.2 36.2L32 40.3Z"/><path class="c2" d="M32 32L32 40.3L24.8 36.2Z"/><path class="c1" d="M24.8 27.8L32 32L24.8 36.2Z"/><path class="c2" d="M32 23.7L32 32L24.8 27.8Z"/></svg></span><span class="product">parley</span>
|
|
23
23
|
</a><span class="slash">/</span><span class="here" id="project"> </span>
|
|
24
24
|
</nav>
|
|
25
25
|
<span class="status" id="status" hidden><span class="dot" id="status-dot"></span><span id="status-text"></span></span>
|
|
@@ -84,6 +84,7 @@
|
|
|
84
84
|
<script>window.PARLEY = {{CONFIG}};</script>
|
|
85
85
|
<script src="/static/markdown.js"></script>
|
|
86
86
|
<script src="/static/seal.js"></script>
|
|
87
|
+
<script src="/static/mark.js"></script>
|
|
87
88
|
<script src="/static/chat.js"></script>
|
|
88
89
|
</body>
|
|
89
90
|
</html>
|
package/web/chat.js
CHANGED
|
@@ -222,7 +222,16 @@
|
|
|
222
222
|
if (live.text.trim()) {
|
|
223
223
|
live.bodyEl.innerHTML = window.renderMarkdown(live.text);
|
|
224
224
|
live.bodyEl.lastElementChild?.classList.add("cursor");
|
|
225
|
-
} else
|
|
225
|
+
} else {
|
|
226
|
+
// Working: the mark's core flickers while it looks things up, and light
|
|
227
|
+
// runs around it while it writes.
|
|
228
|
+
let working = live.bodyEl.querySelector(".thinking");
|
|
229
|
+
if (!working) {
|
|
230
|
+
live.bodyEl.innerHTML = `<span class="thinking" role="img" aria-label="Working">${window.parleyMark({ moving: true })}</span>`;
|
|
231
|
+
working = live.bodyEl.querySelector(".thinking");
|
|
232
|
+
}
|
|
233
|
+
working.classList.toggle("reading", live.steps.length > 0 && !live.text);
|
|
234
|
+
}
|
|
226
235
|
// The last few things it looked at, newest highlighted.
|
|
227
236
|
live.stepsEl.textContent = "";
|
|
228
237
|
live.steps.slice(-4).forEach((line, i, shown) => live.stepsEl.append(el("li", { className: i === shown.length - 1 ? "now" : "", textContent: line })));
|
|
@@ -438,12 +447,26 @@
|
|
|
438
447
|
els.layout.append(el("div", { className: "lost inline", style: "flex:1" }, el("div", {}, markEl(), el("h1", { textContent: title }), el("p", { textContent: text }), extra())));
|
|
439
448
|
}
|
|
440
449
|
|
|
450
|
+
// An ended or broken link shows the mark dimmed.
|
|
441
451
|
function markEl() {
|
|
442
|
-
const span = el("span", { className: "mark" });
|
|
443
|
-
span.innerHTML =
|
|
452
|
+
const span = el("span", { className: "mark dim" });
|
|
453
|
+
span.innerHTML = window.parleyMark();
|
|
444
454
|
return span;
|
|
445
455
|
}
|
|
446
456
|
|
|
457
|
+
// The header's mark: it builds itself as the page opens, and its core
|
|
458
|
+
// pulses once when a reply lands.
|
|
459
|
+
const headMark = document.querySelector(".topbar .mark");
|
|
460
|
+
function markOnce(name, ms) {
|
|
461
|
+
if (!headMark || matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
462
|
+
headMark.classList.remove(name);
|
|
463
|
+
void headMark.offsetWidth;
|
|
464
|
+
headMark.classList.add(name);
|
|
465
|
+
setTimeout(() => headMark.classList.remove(name), ms);
|
|
466
|
+
}
|
|
467
|
+
headMark?.querySelectorAll(".r1, .r2").forEach((path, i) => (path.style.animationDelay = `${i * 30}ms`));
|
|
468
|
+
markOnce("opening", 1800);
|
|
469
|
+
|
|
447
470
|
function banner(text) {
|
|
448
471
|
els.banner.hidden = !text;
|
|
449
472
|
els.banner.textContent = text || "";
|
|
@@ -902,7 +925,10 @@
|
|
|
902
925
|
// A live update: a new message, the reply streaming in, what the agent is
|
|
903
926
|
// looking at (your view only), replies left, or the end.
|
|
904
927
|
function apply(event) {
|
|
905
|
-
if (event.type === "message")
|
|
928
|
+
if (event.type === "message") {
|
|
929
|
+
if (event.message.role === "agent" && !seen.has(event.message.id)) markOnce("pulse", 900);
|
|
930
|
+
addMessage(event.message);
|
|
931
|
+
}
|
|
906
932
|
else if (event.type === "turn") event.live ? showLive(event.live) : clearLive();
|
|
907
933
|
else if (event.type === "delta") {
|
|
908
934
|
if (!live) showLive({ text: "", activity: null });
|
package/web/ended.html
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
<meta name="theme-color" content="#0c0c0d">
|
|
7
7
|
<meta name="robots" content="noindex, nofollow">
|
|
8
8
|
<title>This parley has ended</title>
|
|
9
|
-
<link rel="icon" href="/static/icon.svg?v=
|
|
9
|
+
<link rel="icon" href="/static/icon.svg?v=6" type="image/svg+xml">
|
|
10
10
|
<link rel="stylesheet" href="/static/app.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div class="lost">
|
|
14
14
|
<div>
|
|
15
|
-
<span class="mark" aria-hidden="true"><svg viewBox="0 0 64 64"><
|
|
15
|
+
<span class="mark dim" aria-hidden="true"><svg viewBox="0 0 64 64" aria-hidden="true"><path class="r1" d="M32 7L39.2 11.2L32 15.3Z"/><path class="r2" d="M39.2 11.2L39.2 19.5L32 15.3Z"/><path class="r1" d="M39.2 11.2L46.4 15.3L39.2 19.5Z"/><path class="r2" d="M46.4 15.3L46.4 23.7L39.2 19.5Z"/><path class="r1" d="M46.4 15.3L53.7 19.5L46.4 23.7Z"/><path class="r2" d="M53.7 19.5L53.7 27.8L46.4 23.7Z"/><path class="r1" d="M46.4 23.7L53.7 27.8L46.4 32Z"/><path class="r2" d="M53.7 27.8L53.7 36.2L46.4 32Z"/><path class="r1" d="M46.4 32L53.7 36.2L46.4 40.3Z"/><path class="r2" d="M53.7 36.2L53.7 44.5L46.4 40.3Z"/><path class="r1" d="M46.4 40.3L53.7 44.5L46.4 48.7Z"/><path class="r2" d="M46.4 40.3L46.4 48.7L39.2 44.5Z"/><path class="r1" d="M39.2 44.5L46.4 48.7L39.2 52.8Z"/><path class="r2" d="M39.2 44.5L39.2 52.8L32 48.7Z"/><path class="r1" d="M32 48.7L39.2 52.8L32 57Z"/><path class="r2" d="M32 48.7L32 57L24.8 52.8Z"/><path class="r1" d="M24.8 44.5L32 48.7L24.8 52.8Z"/><path class="r2" d="M24.8 44.5L24.8 52.8L17.6 48.7Z"/><path class="r1" d="M17.6 40.3L24.8 44.5L17.6 48.7Z"/><path class="r2" d="M17.6 40.3L17.6 48.7L10.3 44.5Z"/><path class="r1" d="M10.3 36.2L17.6 40.3L10.3 44.5Z"/><path class="r2" d="M17.6 32L17.6 40.3L10.3 36.2Z"/><path class="r1" d="M10.3 27.8L17.6 32L10.3 36.2Z"/><path class="r2" d="M17.6 23.7L17.6 32L10.3 27.8Z"/><path class="r1" d="M10.3 19.5L17.6 23.7L10.3 27.8Z"/><path class="r2" d="M17.6 15.3L17.6 23.7L10.3 19.5Z"/><path class="r1" d="M17.6 15.3L24.8 19.5L17.6 23.7Z"/><path class="r2" d="M24.8 11.2L24.8 19.5L17.6 15.3Z"/><path class="r1" d="M24.8 11.2L32 15.3L24.8 19.5Z"/><path class="r2" d="M32 7L32 15.3L24.8 11.2Z"/><path class="c1" d="M32 23.7L39.2 27.8L32 32Z"/><path class="c2" d="M39.2 27.8L39.2 36.2L32 32Z"/><path class="c1" d="M32 32L39.2 36.2L32 40.3Z"/><path class="c2" d="M32 32L32 40.3L24.8 36.2Z"/><path class="c1" d="M24.8 27.8L32 32L24.8 36.2Z"/><path class="c2" d="M32 23.7L32 32L24.8 27.8Z"/></svg></span>
|
|
16
16
|
<h1>This parley has ended</h1>
|
|
17
17
|
<p>Thanks for the feedback. The conversation isn't available here anymore.</p>
|
|
18
18
|
</div>
|
package/web/home.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// parley's front page: plays the demo, three short parleys in turn, the way
|
|
2
|
-
// a guest sees one. The first is already in the page, so without script (or
|
|
2
|
+
// a guest sees one (mark.js draws the working mark). The first is already in the page, so without script (or
|
|
3
3
|
// for anyone who prefers less motion) it just sits there, and the tabs swap
|
|
4
4
|
// scenes without playing them.
|
|
5
5
|
(() => {
|
|
@@ -32,6 +32,16 @@
|
|
|
32
32
|
};
|
|
33
33
|
const wait = (ms, id) => new Promise((resolve, reject) => setTimeout(() => (id === run ? resolve() : reject(new Error("stopped"))), ms));
|
|
34
34
|
|
|
35
|
+
// Each parley opens the way a guest's does: the mark builds itself.
|
|
36
|
+
const mark = root.querySelector(".demo-top .mark");
|
|
37
|
+
mark?.querySelectorAll(".r1, .r2").forEach((path, k) => (path.style.animationDelay = `${k * 30}ms`));
|
|
38
|
+
function opening() {
|
|
39
|
+
if (!mark) return;
|
|
40
|
+
mark.classList.remove("opening");
|
|
41
|
+
void mark.getBoundingClientRect();
|
|
42
|
+
mark.classList.add("opening");
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
function pick(i) {
|
|
36
46
|
tabs.forEach((tab, j) => {
|
|
37
47
|
tab.classList.toggle("on", i === j);
|
|
@@ -49,18 +59,19 @@
|
|
|
49
59
|
const scene = scenes[i];
|
|
50
60
|
pick(i);
|
|
51
61
|
list.replaceChildren();
|
|
62
|
+
opening();
|
|
52
63
|
await wait(500, id);
|
|
53
64
|
list.append(guest(scene.asks));
|
|
54
65
|
await wait(900, id);
|
|
55
|
-
// Working: the
|
|
66
|
+
// Working: the mark reads (its core flickers), then writes (light runs
|
|
67
|
+
// around it), then the reply streams in.
|
|
56
68
|
const node = el("li", "msg agent");
|
|
57
69
|
const body = el("div", "body");
|
|
58
|
-
body.innerHTML = `<span class="thinking" role="img" aria-label="
|
|
59
|
-
|
|
60
|
-
node.append(body, steps);
|
|
70
|
+
body.innerHTML = `<span class="thinking reading" role="img" aria-label="Working">${window.parleyMark({ moving: true })}</span>`;
|
|
71
|
+
node.append(body);
|
|
61
72
|
list.append(node);
|
|
62
|
-
await wait(
|
|
63
|
-
|
|
73
|
+
await wait(1600, id);
|
|
74
|
+
body.firstElementChild.classList.remove("reading");
|
|
64
75
|
await wait(1400, id);
|
|
65
76
|
const p = el("p", "cursor");
|
|
66
77
|
body.replaceChildren(p);
|
|
@@ -70,7 +81,6 @@
|
|
|
70
81
|
await wait(55, id);
|
|
71
82
|
}
|
|
72
83
|
p.classList.remove("cursor");
|
|
73
|
-
steps.remove();
|
|
74
84
|
await wait(1500, id);
|
|
75
85
|
list.append(guest(scene.then));
|
|
76
86
|
await wait(4500, id);
|
package/web/icon-180.png
CHANGED
|
Binary file
|
package/web/icon.png
CHANGED
|
Binary file
|
package/web/icon.svg
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="#9D92FF"/><
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="#1E1C27"/><path fill="#9D92FF" d="M32 7L39.2 11.2L32 15.3Z"/><path fill="#8A7FF0" d="M39.2 11.2L39.2 19.5L32 15.3Z"/><path fill="#9D92FF" d="M39.2 11.2L46.4 15.3L39.2 19.5Z"/><path fill="#8A7FF0" d="M46.4 15.3L46.4 23.7L39.2 19.5Z"/><path fill="#9D92FF" d="M46.4 15.3L53.7 19.5L46.4 23.7Z"/><path fill="#8A7FF0" d="M53.7 19.5L53.7 27.8L46.4 23.7Z"/><path fill="#9D92FF" d="M46.4 23.7L53.7 27.8L46.4 32Z"/><path fill="#8A7FF0" d="M53.7 27.8L53.7 36.2L46.4 32Z"/><path fill="#9D92FF" d="M46.4 32L53.7 36.2L46.4 40.3Z"/><path fill="#8A7FF0" d="M53.7 36.2L53.7 44.5L46.4 40.3Z"/><path fill="#9D92FF" d="M46.4 40.3L53.7 44.5L46.4 48.7Z"/><path fill="#8A7FF0" d="M46.4 40.3L46.4 48.7L39.2 44.5Z"/><path fill="#9D92FF" d="M39.2 44.5L46.4 48.7L39.2 52.8Z"/><path fill="#8A7FF0" d="M39.2 44.5L39.2 52.8L32 48.7Z"/><path fill="#9D92FF" d="M32 48.7L39.2 52.8L32 57Z"/><path fill="#8A7FF0" d="M32 48.7L32 57L24.8 52.8Z"/><path fill="#9D92FF" d="M24.8 44.5L32 48.7L24.8 52.8Z"/><path fill="#8A7FF0" d="M24.8 44.5L24.8 52.8L17.6 48.7Z"/><path fill="#9D92FF" d="M17.6 40.3L24.8 44.5L17.6 48.7Z"/><path fill="#8A7FF0" d="M17.6 40.3L17.6 48.7L10.3 44.5Z"/><path fill="#9D92FF" d="M10.3 36.2L17.6 40.3L10.3 44.5Z"/><path fill="#8A7FF0" d="M17.6 32L17.6 40.3L10.3 36.2Z"/><path fill="#9D92FF" d="M10.3 27.8L17.6 32L10.3 36.2Z"/><path fill="#8A7FF0" d="M17.6 23.7L17.6 32L10.3 27.8Z"/><path fill="#9D92FF" d="M10.3 19.5L17.6 23.7L10.3 27.8Z"/><path fill="#8A7FF0" d="M17.6 15.3L17.6 23.7L10.3 19.5Z"/><path fill="#9D92FF" d="M17.6 15.3L24.8 19.5L17.6 23.7Z"/><path fill="#8A7FF0" d="M24.8 11.2L24.8 19.5L17.6 15.3Z"/><path fill="#9D92FF" d="M24.8 11.2L32 15.3L24.8 19.5Z"/><path fill="#8A7FF0" d="M32 7L32 15.3L24.8 11.2Z"/><path fill="#D2CCFF" d="M32 23.7L39.2 27.8L32 32Z"/><path fill="#BDB4FF" d="M39.2 27.8L39.2 36.2L32 32Z"/><path fill="#D2CCFF" d="M32 32L39.2 36.2L32 40.3Z"/><path fill="#BDB4FF" d="M32 32L32 40.3L24.8 36.2Z"/><path fill="#D2CCFF" d="M24.8 27.8L32 32L24.8 36.2Z"/><path fill="#BDB4FF" d="M32 23.7L32 32L24.8 27.8Z"/></svg>
|
package/web/mark.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// parley's mark, drawn on an isometric grid of equilateral triangles: a ring
|
|
2
|
+
// of thirty around a ring of space and a core of six. The same triangles do
|
|
3
|
+
// the motion. While the agent works, the core flickers as it reads and light
|
|
4
|
+
// runs around the empty ring as it writes (app.css has the animations).
|
|
5
|
+
(() => {
|
|
6
|
+
const S = 25 / 3; // a triangle's side, so the mark spans 50 of 64 units
|
|
7
|
+
const H = Math.sqrt(3) / 2;
|
|
8
|
+
const at = (i, j) => [32 + i * S * H, 32 + j * S + (i * S) / 2];
|
|
9
|
+
const far = (i, j) => Math.max(Math.abs(i), Math.abs(j), Math.abs(i + j));
|
|
10
|
+
const fmt = (n) => +n.toFixed(1);
|
|
11
|
+
|
|
12
|
+
const tris = [];
|
|
13
|
+
for (let i = -4; i <= 3; i++) {
|
|
14
|
+
for (let j = -4; j <= 3; j++) {
|
|
15
|
+
for (const corners of [[[i, j], [i + 1, j], [i, j + 1]], [[i + 1, j], [i + 1, j + 1], [i, j + 1]]]) {
|
|
16
|
+
const ring = Math.max(...corners.map(([a, b]) => far(a, b)));
|
|
17
|
+
if (ring > 3) continue;
|
|
18
|
+
const pts = corners.map(([a, b]) => at(a, b));
|
|
19
|
+
const cx = (pts[0][0] + pts[1][0] + pts[2][0]) / 3 - 32;
|
|
20
|
+
const cy = (pts[0][1] + pts[1][1] + pts[2][1]) / 3 - 32;
|
|
21
|
+
const xs = pts.map((p) => p[0]).sort((a, b) => a - b);
|
|
22
|
+
tris.push({
|
|
23
|
+
ring,
|
|
24
|
+
turn: ((Math.atan2(cx, -cy) * 180) / Math.PI + 360) % 360, // clockwise from the top
|
|
25
|
+
tone: Math.abs(xs[0] - xs[1]) < 0.01 ? 1 : 2, // which way it points
|
|
26
|
+
d: "M" + pts.map((p) => `${fmt(p[0])} ${fmt(p[1])}`).join("L") + "Z",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
tris.sort((a, b) => a.turn - b.turn);
|
|
32
|
+
const SCAN = [0, 0.4, 0.8, 0.2, 0.6, 1.0];
|
|
33
|
+
|
|
34
|
+
// The still mark, or with `moving` the one that reads and writes.
|
|
35
|
+
window.parleyMark = ({ moving = false } = {}) => {
|
|
36
|
+
const ring = tris.filter((t) => t.ring === 3).map((t) => `<path class="r${t.tone}" d="${t.d}"/>`);
|
|
37
|
+
const core = tris
|
|
38
|
+
.filter((t) => t.ring === 1)
|
|
39
|
+
.map((t, k) => `<path class="c${t.tone}"${moving ? ` style="animation-delay:${SCAN[k]}s"` : ""} d="${t.d}"/>`);
|
|
40
|
+
const space = moving ? tris.filter((t) => t.ring === 2).map((t, k) => `<path class="g${t.tone}" style="animation-delay:${(k * 0.1).toFixed(1)}s" d="${t.d}"/>`) : [];
|
|
41
|
+
return `<svg viewBox="0 0 64 64" aria-hidden="true">${ring.join("")}${core.join("")}${space.join("")}</svg>`;
|
|
42
|
+
};
|
|
43
|
+
})();
|
package/web/missing.html
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
<meta name="theme-color" content="#0c0c0d">
|
|
7
7
|
<meta name="robots" content="noindex, nofollow">
|
|
8
8
|
<title>Link not found</title>
|
|
9
|
-
<link rel="icon" href="/static/icon.svg?v=
|
|
9
|
+
<link rel="icon" href="/static/icon.svg?v=6" type="image/svg+xml">
|
|
10
10
|
<link rel="stylesheet" href="/static/app.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div class="lost">
|
|
14
14
|
<div>
|
|
15
|
-
<span class="mark" aria-hidden="true"><svg viewBox="0 0 64 64"><
|
|
15
|
+
<span class="mark dim" aria-hidden="true"><svg viewBox="0 0 64 64" aria-hidden="true"><path class="r1" d="M32 7L39.2 11.2L32 15.3Z"/><path class="r2" d="M39.2 11.2L39.2 19.5L32 15.3Z"/><path class="r1" d="M39.2 11.2L46.4 15.3L39.2 19.5Z"/><path class="r2" d="M46.4 15.3L46.4 23.7L39.2 19.5Z"/><path class="r1" d="M46.4 15.3L53.7 19.5L46.4 23.7Z"/><path class="r2" d="M53.7 19.5L53.7 27.8L46.4 23.7Z"/><path class="r1" d="M46.4 23.7L53.7 27.8L46.4 32Z"/><path class="r2" d="M53.7 27.8L53.7 36.2L46.4 32Z"/><path class="r1" d="M46.4 32L53.7 36.2L46.4 40.3Z"/><path class="r2" d="M53.7 36.2L53.7 44.5L46.4 40.3Z"/><path class="r1" d="M46.4 40.3L53.7 44.5L46.4 48.7Z"/><path class="r2" d="M46.4 40.3L46.4 48.7L39.2 44.5Z"/><path class="r1" d="M39.2 44.5L46.4 48.7L39.2 52.8Z"/><path class="r2" d="M39.2 44.5L39.2 52.8L32 48.7Z"/><path class="r1" d="M32 48.7L39.2 52.8L32 57Z"/><path class="r2" d="M32 48.7L32 57L24.8 52.8Z"/><path class="r1" d="M24.8 44.5L32 48.7L24.8 52.8Z"/><path class="r2" d="M24.8 44.5L24.8 52.8L17.6 48.7Z"/><path class="r1" d="M17.6 40.3L24.8 44.5L17.6 48.7Z"/><path class="r2" d="M17.6 40.3L17.6 48.7L10.3 44.5Z"/><path class="r1" d="M10.3 36.2L17.6 40.3L10.3 44.5Z"/><path class="r2" d="M17.6 32L17.6 40.3L10.3 36.2Z"/><path class="r1" d="M10.3 27.8L17.6 32L10.3 36.2Z"/><path class="r2" d="M17.6 23.7L17.6 32L10.3 27.8Z"/><path class="r1" d="M10.3 19.5L17.6 23.7L10.3 27.8Z"/><path class="r2" d="M17.6 15.3L17.6 23.7L10.3 19.5Z"/><path class="r1" d="M17.6 15.3L24.8 19.5L17.6 23.7Z"/><path class="r2" d="M24.8 11.2L24.8 19.5L17.6 15.3Z"/><path class="r1" d="M24.8 11.2L32 15.3L24.8 19.5Z"/><path class="r2" d="M32 7L32 15.3L24.8 11.2Z"/><path class="c1" d="M32 23.7L39.2 27.8L32 32Z"/><path class="c2" d="M39.2 27.8L39.2 36.2L32 32Z"/><path class="c1" d="M32 32L39.2 36.2L32 40.3Z"/><path class="c2" d="M32 32L32 40.3L24.8 36.2Z"/><path class="c1" d="M24.8 27.8L32 32L24.8 36.2Z"/><path class="c2" d="M32 23.7L32 32L24.8 27.8Z"/></svg></span>
|
|
16
16
|
<h1>This link doesn't lead anywhere</h1>
|
|
17
17
|
<p>Check that you have the whole link, or ask whoever sent it for a fresh one.</p>
|
|
18
18
|
</div>
|
package/web/panel.html
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<meta name="theme-color" content="#0c0c0d">
|
|
7
7
|
<title>Control panel · parley</title>
|
|
8
|
-
<link rel="icon" href="/static/icon.svg?v=
|
|
8
|
+
<link rel="icon" href="/static/icon.svg?v=6" type="image/svg+xml">
|
|
9
9
|
<link rel="stylesheet" href="/static/app.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body class="panel">
|
|
12
12
|
<header class="topbar">
|
|
13
|
-
<span class="mark" aria-hidden="true"><svg viewBox="0 0 64 64"><
|
|
13
|
+
<span class="mark" aria-hidden="true"><svg viewBox="0 0 64 64" aria-hidden="true"><path class="r1" d="M32 7L39.2 11.2L32 15.3Z"/><path class="r2" d="M39.2 11.2L39.2 19.5L32 15.3Z"/><path class="r1" d="M39.2 11.2L46.4 15.3L39.2 19.5Z"/><path class="r2" d="M46.4 15.3L46.4 23.7L39.2 19.5Z"/><path class="r1" d="M46.4 15.3L53.7 19.5L46.4 23.7Z"/><path class="r2" d="M53.7 19.5L53.7 27.8L46.4 23.7Z"/><path class="r1" d="M46.4 23.7L53.7 27.8L46.4 32Z"/><path class="r2" d="M53.7 27.8L53.7 36.2L46.4 32Z"/><path class="r1" d="M46.4 32L53.7 36.2L46.4 40.3Z"/><path class="r2" d="M53.7 36.2L53.7 44.5L46.4 40.3Z"/><path class="r1" d="M46.4 40.3L53.7 44.5L46.4 48.7Z"/><path class="r2" d="M46.4 40.3L46.4 48.7L39.2 44.5Z"/><path class="r1" d="M39.2 44.5L46.4 48.7L39.2 52.8Z"/><path class="r2" d="M39.2 44.5L39.2 52.8L32 48.7Z"/><path class="r1" d="M32 48.7L39.2 52.8L32 57Z"/><path class="r2" d="M32 48.7L32 57L24.8 52.8Z"/><path class="r1" d="M24.8 44.5L32 48.7L24.8 52.8Z"/><path class="r2" d="M24.8 44.5L24.8 52.8L17.6 48.7Z"/><path class="r1" d="M17.6 40.3L24.8 44.5L17.6 48.7Z"/><path class="r2" d="M17.6 40.3L17.6 48.7L10.3 44.5Z"/><path class="r1" d="M10.3 36.2L17.6 40.3L10.3 44.5Z"/><path class="r2" d="M17.6 32L17.6 40.3L10.3 36.2Z"/><path class="r1" d="M10.3 27.8L17.6 32L10.3 36.2Z"/><path class="r2" d="M17.6 23.7L17.6 32L10.3 27.8Z"/><path class="r1" d="M10.3 19.5L17.6 23.7L10.3 27.8Z"/><path class="r2" d="M17.6 15.3L17.6 23.7L10.3 19.5Z"/><path class="r1" d="M17.6 15.3L24.8 19.5L17.6 23.7Z"/><path class="r2" d="M24.8 11.2L24.8 19.5L17.6 15.3Z"/><path class="r1" d="M24.8 11.2L32 15.3L24.8 19.5Z"/><path class="r2" d="M32 7L32 15.3L24.8 11.2Z"/><path class="c1" d="M32 23.7L39.2 27.8L32 32Z"/><path class="c2" d="M39.2 27.8L39.2 36.2L32 32Z"/><path class="c1" d="M32 32L39.2 36.2L32 40.3Z"/><path class="c2" d="M32 32L32 40.3L24.8 36.2Z"/><path class="c1" d="M24.8 27.8L32 32L24.8 36.2Z"/><path class="c2" d="M32 23.7L32 32L24.8 27.8Z"/></svg></span>
|
|
14
14
|
<nav class="crumbs"><span class="product">parley</span><span class="slash">/</span><span class="here">Control panel</span></nav>
|
|
15
15
|
<span class="status" id="server"><span class="dot" id="server-dot"></span><span id="server-text">Checking…</span></span>
|
|
16
16
|
</header>
|