@jtakeit/kit 0.7.0 → 0.7.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/elements/writing.js +109 -7
- package/lib/phases.mjs +22 -1
- package/package.json +1 -1
- package/types/phases.d.ts +1 -0
package/elements/writing.js
CHANGED
|
@@ -14,15 +14,27 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Collapsed, it is a pill with the phase; a press opens it; the choice is
|
|
16
16
|
* remembered in this browser.
|
|
17
|
+
*
|
|
18
|
+
* ── the stage, while the page has no words ──────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* A scaffold just laid out is a page of empty fields and asterisks, and a
|
|
21
|
+
* developer who opens localhost to watch the agent saw that and nothing else
|
|
22
|
+
* (13 October 2026). So while the page being looked at has not one word in it
|
|
23
|
+
* yet, the element covers it with a stage: a page assembling itself, what the
|
|
24
|
+
* agent is on, what is left, the panel's steps meanwhile, and the promise that
|
|
25
|
+
* the page will appear here by itself. The moment the page has its first
|
|
26
|
+
* words, the stage goes and the page is loaded again — nobody presses
|
|
27
|
+
* anything. A page the files do not know (a listing, an entry) is never
|
|
28
|
+
* covered, nor is any page when the dev server cannot be read.
|
|
17
29
|
*/
|
|
18
|
-
import { PHASES, PHASE_NAMES, phasesOf, sayPhase, meanwhile } from '../lib/phases.mjs';
|
|
30
|
+
import { PHASES, PHASE_NAMES, phasesOf, sayPhase, meanwhile, blankHere } from '../lib/phases.mjs';
|
|
19
31
|
|
|
20
32
|
const STORE = 'jtk-writing:collapsed';
|
|
21
33
|
|
|
22
34
|
const CSS = `
|
|
23
35
|
:host{position:fixed;right:16px;bottom:16px;z-index:2147483000;font:13px/1.45 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;-webkit-font-smoothing:antialiased;
|
|
24
|
-
--bg:#fbfaf7;--ink:#1d1b17;--ink2:#3f3b33;--muted:#6b665c;--faint:#9b958a;--line:#e6e1d6;--line2:#d8d2c4;--ok:#2f7d5a;--attn:#9a6b1c;--shadow:0 12px 36px rgba(30,25,15,.16),0 2px 6px rgba(30,25,15,.08)}
|
|
25
|
-
@media (prefers-color-scheme:dark){:host{--bg:#1c1b18;--ink:#f1ede5;--ink2:#d9d3c7;--muted:#a8a295;--faint:#716c62;--line:#33302a;--line2:#443f36;--ok:#5bb38a;--attn:#d4a24a;--shadow:0 12px 36px rgba(0,0,0,.5)}}
|
|
36
|
+
--paper:#f4f1ea;--glow:rgba(212,162,74,.16);--bg:#fbfaf7;--ink:#1d1b17;--ink2:#3f3b33;--muted:#6b665c;--faint:#9b958a;--line:#e6e1d6;--line2:#d8d2c4;--ok:#2f7d5a;--attn:#9a6b1c;--shadow:0 12px 36px rgba(30,25,15,.16),0 2px 6px rgba(30,25,15,.08)}
|
|
37
|
+
@media (prefers-color-scheme:dark){:host{--paper:#141311;--glow:rgba(212,162,74,.12);--bg:#1c1b18;--ink:#f1ede5;--ink2:#d9d3c7;--muted:#a8a295;--faint:#716c62;--line:#33302a;--line2:#443f36;--ok:#5bb38a;--attn:#d4a24a;--shadow:0 12px 36px rgba(0,0,0,.5)}}
|
|
26
38
|
*{box-sizing:border-box;margin:0;padding:0}
|
|
27
39
|
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
|
|
28
40
|
a{color:var(--ink);text-decoration:underline;text-decoration-color:var(--line2);text-underline-offset:3px}
|
|
@@ -41,7 +53,9 @@ a:hover{text-decoration-color:var(--ink)}
|
|
|
41
53
|
.seg.done,.mini i.done{background:var(--ok)}.seg.current,.mini i.current{background:var(--attn)}.seg.current{animation:pulse 2.4s ease-in-out infinite}
|
|
42
54
|
.seg.unseen{background:transparent;border:1px solid var(--line2)}
|
|
43
55
|
@keyframes pulse{50%{opacity:.45}}@media (prefers-reduced-motion:reduce){.seg.current,.card.enter{animation:none}}
|
|
44
|
-
|
|
56
|
+
/* One line under the strip: the phase it is in, whole, and the one after —
|
|
57
|
+
seven names in 340 px were seven ellipses (13 October 2026). */
|
|
58
|
+
.names{display:flex;gap:8px;align-items:baseline;margin-top:7px;font-size:12px}.names .current{color:var(--ink);font-weight:600}.names .next{color:var(--faint)}
|
|
45
59
|
.say{display:flex;gap:9px;align-items:flex-start;margin-top:12px;font-weight:600;font-size:13.5px;line-height:1.35}.say i{flex:none;width:8px;height:8px;border-radius:999px;background:var(--attn);margin-top:5px}
|
|
46
60
|
.todo{list-style:none;margin:6px 0 0 17px;color:var(--muted);font-size:12.5px}.todo li+li{margin-top:2px}
|
|
47
61
|
.rule{height:1px;background:var(--line);margin:12px 0 10px}
|
|
@@ -54,6 +68,35 @@ a:hover{text-decoration-color:var(--ink)}
|
|
|
54
68
|
.foot{margin-top:10px;color:var(--faint);font-size:11px}
|
|
55
69
|
`;
|
|
56
70
|
|
|
71
|
+
const STAGE_CSS = `
|
|
72
|
+
.stage{position:fixed;inset:0;z-index:2147482999;display:grid;place-items:center;overflow:auto;padding:32px 20px;
|
|
73
|
+
background:radial-gradient(1200px 600px at 50% -10%,var(--glow),transparent 60%),var(--paper);color:var(--ink);
|
|
74
|
+
font:15px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;animation:fade .4s ease both}
|
|
75
|
+
.stage.leaving{animation:out .35s ease both}
|
|
76
|
+
@keyframes fade{from{opacity:0}to{opacity:1}}@keyframes out{to{opacity:0}}
|
|
77
|
+
.wrap{width:min(560px,100%);display:grid;gap:22px}
|
|
78
|
+
.sheet{position:relative;height:210px;border-radius:18px;background:var(--bg);border:1px solid var(--line);box-shadow:var(--shadow);overflow:hidden;padding:18px}
|
|
79
|
+
.bar{height:10px;width:34%;border-radius:999px}
|
|
80
|
+
.row{display:flex;gap:8px;margin-top:22px}.row b{display:block;height:9px;border-radius:999px}
|
|
81
|
+
.hero{height:46px;width:72%;border-radius:12px;margin-top:18px}
|
|
82
|
+
.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:18px}.cards b{display:block;height:46px;border-radius:10px}
|
|
83
|
+
.sk{background:linear-gradient(90deg,var(--line) 0%,var(--line2) 50%,var(--line) 100%);background-size:200% 100%;animation:shine 1.8s linear infinite;opacity:0;transform:translateY(6px)}
|
|
84
|
+
.sk{animation:shine 1.8s linear infinite,rise .6s cubic-bezier(.22,.61,.36,1) forwards}
|
|
85
|
+
.d1{animation-delay:0s,.1s}.d2{animation-delay:.1s,.35s}.d3{animation-delay:.2s,.6s}.d4{animation-delay:.3s,.85s}.d5{animation-delay:.4s,1.1s}.d6{animation-delay:.5s,1.35s}
|
|
86
|
+
@keyframes shine{to{background-position:-200% 0}}@keyframes rise{to{opacity:1;transform:none}}
|
|
87
|
+
.caret{position:absolute;right:18px;bottom:16px;display:flex;gap:5px}.caret i{width:6px;height:6px;border-radius:999px;background:var(--attn);animation:dot 1.2s ease-in-out infinite}
|
|
88
|
+
.caret i:nth-child(2){animation-delay:.15s}.caret i:nth-child(3){animation-delay:.3s}@keyframes dot{50%{opacity:.25;transform:translateY(-3px)}}
|
|
89
|
+
.kicker{font-size:12px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--faint)}
|
|
90
|
+
.big{font-size:clamp(24px,4vw,32px);line-height:1.15;font-weight:650;letter-spacing:-.01em;margin-top:6px}
|
|
91
|
+
.now{display:flex;gap:10px;align-items:flex-start;margin-top:10px;color:var(--ink2)}.now i{flex:none;width:9px;height:9px;border-radius:999px;background:var(--attn);margin-top:7px;animation:pulse 2.4s ease-in-out infinite}
|
|
92
|
+
.promise{color:var(--muted);font-size:14px}
|
|
93
|
+
.right{margin:6px 0 0 19px;color:var(--muted);font-size:13px}
|
|
94
|
+
.stage .strip{margin-top:0}
|
|
95
|
+
.cols{display:grid;grid-template-columns:1fr 1fr;gap:18px}@media (max-width:560px){.cols{grid-template-columns:1fr}}
|
|
96
|
+
.stage .todo{margin:6px 0 0;padding-left:16px;list-style:disc}
|
|
97
|
+
@media (prefers-reduced-motion:reduce){.stage,.stage.leaving,.sk,.caret i,.now i{animation:none!important}.sk{opacity:1;transform:none}}
|
|
98
|
+
`;
|
|
99
|
+
|
|
57
100
|
const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' })[c]);
|
|
58
101
|
|
|
59
102
|
function ago(iso) {
|
|
@@ -90,7 +133,7 @@ class JtkWriting extends HTMLElement {
|
|
|
90
133
|
connectedCallback() {
|
|
91
134
|
const root = this.attachShadow({ mode: 'open' });
|
|
92
135
|
const style = document.createElement('style');
|
|
93
|
-
style.textContent = CSS;
|
|
136
|
+
style.textContent = CSS + STAGE_CSS;
|
|
94
137
|
this.box = document.createElement('div');
|
|
95
138
|
root.append(style, this.box);
|
|
96
139
|
root.addEventListener('click', (event) => {
|
|
@@ -128,6 +171,19 @@ class JtkWriting extends HTMLElement {
|
|
|
128
171
|
const where = phasesOf(progress, half);
|
|
129
172
|
const n = where.index + 1;
|
|
130
173
|
|
|
174
|
+
// The stage, while this page has not one word in it (see the top).
|
|
175
|
+
const blank = !this.failed && blankHere(progress);
|
|
176
|
+
if (this.wasBlank && !blank && progress !== null) {
|
|
177
|
+
// Its first words: let the page be the page.
|
|
178
|
+
this.leave();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
this.wasBlank = blank;
|
|
182
|
+
if (blank) {
|
|
183
|
+
this.stage(where, progress, half);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
131
187
|
if (this.collapsed) {
|
|
132
188
|
this.show(`<button class="pill" data-toggle aria-expanded="false" aria-label="Where the site is: phase ${n} of 7">
|
|
133
189
|
<span class="mini">${where.phases.map((p) => `<i class="${p.state}"></i>`).join('')}</span>
|
|
@@ -148,15 +204,18 @@ class JtkWriting extends HTMLElement {
|
|
|
148
204
|
? `<p class="note">The platform could not be read: ${esc(platform.error)}</p>`
|
|
149
205
|
: `<p class="note">Not connected to the platform yet — in the working tree: <code>npx jtkit session <session_id> <watch_key></code>, both from the session's answer.</p>`;
|
|
150
206
|
|
|
207
|
+
const last = this.state?.lastChange ?? null;
|
|
151
208
|
const foot = [
|
|
152
|
-
this.failed ? 'the dev server did not answer'
|
|
209
|
+
this.failed ? 'the dev server did not answer'
|
|
210
|
+
: last ? `last change: ${last.path.split('/').slice(-2).join('/')} · ${ago(last.at)}`
|
|
211
|
+
: progress?.changedAt ? `read from the files, changed ${ago(progress.changedAt)}` : 'read from the files',
|
|
153
212
|
half?.until ? `platform through the session until ${until(half.until)}` : '',
|
|
154
213
|
].filter(Boolean).join(' · ');
|
|
155
214
|
|
|
156
215
|
this.show(`<section class="card${this.enter ? ' enter' : ''}" aria-label="Where the site is">
|
|
157
216
|
<div class="head"><span class="title">What the agent is doing</span><span class="count">phase ${n} of 7</span><button class="hide" data-toggle aria-label="Collapse">–</button></div>
|
|
158
217
|
<ol class="strip" aria-label="The seven phases">${where.phases.map((p, i) => `<li class="seg ${p.state}" title="${i + 1}. ${PHASE_NAMES[p.key]}"></li>`).join('')}</ol>
|
|
159
|
-
<
|
|
218
|
+
<p class="names"><span class="current">${n}. ${esc(PHASE_NAMES[where.current] ?? '')}</span>${where.index < 6 ? `<span class="next">next: ${esc(PHASE_NAMES[where.phases[where.index + 1].key])}</span>` : ''}</p>
|
|
160
219
|
<p class="say"><i></i><span>${esc(say)}</span></p>
|
|
161
220
|
${todo.length > 0 && where.current !== 'preview'
|
|
162
221
|
? `<ul class="todo">${todo.slice(0, 4).map((line) => `<li>${esc(line)}</li>`).join('')}${todo.length > 4 ? `<li>+ ${todo.length - 4} more — <code>jtkit progress</code></li>` : ''}</ul>`
|
|
@@ -169,6 +228,48 @@ class JtkWriting extends HTMLElement {
|
|
|
169
228
|
</section>`);
|
|
170
229
|
}
|
|
171
230
|
|
|
231
|
+
/** The stage over a page with no words yet: what is happening, and that it will appear. */
|
|
232
|
+
stage(where, progress, half) {
|
|
233
|
+
const say = sayPhase(where, progress, half);
|
|
234
|
+
const todo = (progress?.missing ?? []).slice(0, 4);
|
|
235
|
+
const steps = meanwhile(half);
|
|
236
|
+
const open = half ? steps.open : [];
|
|
237
|
+
document.documentElement.style.overflow = 'hidden';
|
|
238
|
+
this.show(`<section class="stage" role="status" aria-live="polite" aria-label="The site is being written">
|
|
239
|
+
<div class="wrap">
|
|
240
|
+
<div class="sheet" aria-hidden="true">
|
|
241
|
+
<div class="bar sk d1"></div>
|
|
242
|
+
<div class="hero sk d2"></div>
|
|
243
|
+
<div class="row"><b class="sk d3" style="width:58%"></b><b class="sk d3" style="width:24%"></b></div>
|
|
244
|
+
<div class="cards"><b class="sk d4"></b><b class="sk d5"></b><b class="sk d6"></b></div>
|
|
245
|
+
<div class="caret"><i></i><i></i><i></i></div>
|
|
246
|
+
</div>
|
|
247
|
+
<div>
|
|
248
|
+
<p class="kicker">Phase ${where.index + 1} of 7 · ${esc(PHASE_NAMES[where.current] ?? '')}</p>
|
|
249
|
+
<h1 class="big">The site is being written</h1>
|
|
250
|
+
<p class="now"><i></i><span>${esc(say)}</span></p>
|
|
251
|
+
${this.state?.lastChange ? `<p class="right">right now: <code>${esc(this.state.lastChange.path)}</code> · ${esc(ago(this.state.lastChange.at))}</p>` : ''}
|
|
252
|
+
</div>
|
|
253
|
+
<ol class="strip" aria-hidden="true">${where.phases.map((p) => `<li class="seg ${p.state}"></li>`).join('')}</ol>
|
|
254
|
+
<div class="cols">
|
|
255
|
+
${todo.length > 0 ? `<div><p class="label">Still to do</p><ul class="todo">${todo.map((line) => `<li>${esc(line)}</li>`).join('')}</ul></div>` : ''}
|
|
256
|
+
${open.length > 0
|
|
257
|
+
? `<div><p class="label">Meanwhile, in the panel</p><ul class="steps">${open.map((s) => `<li><i class="o"></i><span>${esc(s.name)}</span></li>`).join('')}</ul>${half?.panel ? `<a class="panel" href="${esc(half.panel)}" target="_blank" rel="noopener">Open the panel ↗</a>` : ''}</div>`
|
|
258
|
+
: ''}
|
|
259
|
+
</div>
|
|
260
|
+
<p class="promise">This page appears here by itself the moment it has its first words — leave the tab open.</p>
|
|
261
|
+
</div>
|
|
262
|
+
</section>`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** The page has its words: the stage fades and the page is read again. */
|
|
266
|
+
leave() {
|
|
267
|
+
this.wasBlank = false;
|
|
268
|
+
const stage = this.box.querySelector('.stage');
|
|
269
|
+
stage?.classList.add('leaving');
|
|
270
|
+
setTimeout(() => location.reload(), stage ? 350 : 0);
|
|
271
|
+
}
|
|
272
|
+
|
|
172
273
|
/** Put this on the page, unless it is what is there already. */
|
|
173
274
|
show(html) {
|
|
174
275
|
const key = html.replace(' enter', '');
|
|
@@ -176,6 +277,7 @@ class JtkWriting extends HTMLElement {
|
|
|
176
277
|
this.shown = key;
|
|
177
278
|
this.box.innerHTML = html;
|
|
178
279
|
this.enter = false;
|
|
280
|
+
if (!html.includes('class="stage"')) document.documentElement.style.removeProperty('overflow');
|
|
179
281
|
}
|
|
180
282
|
}
|
|
181
283
|
|
package/lib/phases.mjs
CHANGED
|
@@ -103,9 +103,15 @@ export function sayPhase(where, progress, platform) {
|
|
|
103
103
|
case 'laidOut':
|
|
104
104
|
return 'Laying the site out';
|
|
105
105
|
case 'writing': {
|
|
106
|
+
// In the order the work goes, not always «the pages»: an agent declaring
|
|
107
|
+
// the blocks and setting the diary up for five minutes read as writing
|
|
108
|
+
// a page it had not reached (13 October 2026).
|
|
109
|
+
if (progress && progress.catalogue !== true) return 'Declaring the blocks — what the site is made of';
|
|
110
|
+
if (progress && progress.kit !== null && progress.settings === null) return 'Setting up the bookings — hours, prices, what is booked';
|
|
106
111
|
const pages = progress?.pages ?? [];
|
|
107
112
|
const written = pages.filter((one) => one.written).length;
|
|
108
|
-
return `Writing the pages — ${written} of ${pages.length} with their text
|
|
113
|
+
if (pages.length > 0 && written < pages.length) return `Writing the pages — ${written} of ${pages.length} with their text`;
|
|
114
|
+
return `Finishing — still to do: ${progress?.missing.length ?? 0}`;
|
|
109
115
|
}
|
|
110
116
|
case 'judged':
|
|
111
117
|
if (!known || !platform.judged) return 'To be checked before the push';
|
|
@@ -137,3 +143,18 @@ export function meanwhile(platform) {
|
|
|
137
143
|
done: theirs.filter((step) => step.done).map(named),
|
|
138
144
|
};
|
|
139
145
|
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Whether the page being looked at has no words yet: a page the files know,
|
|
149
|
+
* with not one text field filled. A page they do not know — a listing, an
|
|
150
|
+
* entry, a 404 — is never blank by this; nor is any page before the files
|
|
151
|
+
* could be read.
|
|
152
|
+
* @param {any} progress
|
|
153
|
+
*/
|
|
154
|
+
export function blankHere(progress, path = typeof location === 'undefined' ? '/' : location.pathname) {
|
|
155
|
+
if (!progress || !Array.isArray(progress.pages)) return false;
|
|
156
|
+
const norm = (p) => (p.length > 1 && p.endsWith('/') ? p.slice(0, -1) : p);
|
|
157
|
+
const here = norm(path);
|
|
158
|
+
const page = progress.pages.find((one) => norm(one.path) === here);
|
|
159
|
+
return page !== undefined && page.filled === 0;
|
|
160
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jtakeit/kit",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "The framework-free half of a site on jtakeit: the booking form as an element and as markup, for Astro through @jtakeit/astro and for any other generator directly",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/types/phases.d.ts
CHANGED