@bongos/core 1.19.1062 → 1.19.1064
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/.bongos-core.json +123 -48
- package/.claude/skills/grade-recover/SKILL.md +1 -0
- package/.claude/skills/tweak/SKILL.md +70 -0
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +8 -0
- package/clients/bongos-client/index.cjs +8 -0
- package/clients/bongos-client/index.d.ts +12 -0
- package/clients/bongos-client/index.mjs +8 -0
- package/docs/api/openapi.json +301 -3
- package/docs/api-reference.md +7 -3
- package/docs/copy-inventory.md +44 -7
- package/docs/copy-registry.json +393 -26
- package/docs/file-map.md +1 -0
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-inventory.json +36 -4
- package/docs/page-readings.json +34 -1
- package/modules/copy-desk/page-status.js +63 -0
- package/modules/copy-desk/pages.js +1 -0
- package/modules/copy-desk/routes/copy-desk.js +30 -0
- package/modules/hall-ui/public/tweak-editor-lib.js +137 -0
- package/modules/hall-ui/public/tweak-editor.css +437 -0
- package/modules/hall-ui/public/tweak-editor.html +111 -0
- package/modules/hall-ui/public/tweak-editor.js +447 -0
- package/modules/hall-ui/public/tweak-editor.states.json +99 -0
- package/modules/hall-ui/records/tweak-editor.md +32 -0
- package/modules/lifecycle/db-grade.js +16 -0
- package/modules/lifecycle/migrations/lifecycle_013_task_visual_slots.sql +52 -0
- package/modules/lifecycle/page-tweak-hold.js +74 -0
- package/modules/lifecycle/page-tweak-reads.js +33 -1
- package/modules/lifecycle/routes/tasks.js +4 -2
- package/modules/lifecycle/routes/visuals.js +82 -0
- package/modules/lifecycle/task-visual-db.js +40 -1
- package/modules/lifecycle/task-visuals.js +24 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/scripts/gds/copy-apply.js +277 -1
- package/scripts/gds/page-reader.js +2 -0
- package/scripts/gds/ship-finish.js +27 -2
- package/scripts/gds/ship-flow.js +17 -1
- package/scripts/gds/ship-land.js +30 -7
- package/scripts/gds/ship-merge.js +43 -15
- package/scripts/gds/strand-watch.js +41 -1
- package/scripts/gds/task.js +23 -5
- package/scripts/gds/tweak-renders.js +200 -0
- package/scripts/hall-preview/server.js +21 -0
- package/src/bongos/serve-internal.js +5 -1
- package/src/module-api.js +1 -1
- package/tests/copy_desk_page_editor.mjs +181 -0
- package/tests/hall_audit.mjs +5 -0
- package/tests/hall_page_gate_map.mjs +3 -0
- package/tests/hall_tweak_editor.mjs +432 -0
- package/tests/page_tweak_hold.mjs +265 -0
- package/tests/publish_status_branch.mjs +23 -0
- package/tests/task_visual_slots.mjs +161 -0
- package/tests/tweak_batch_apply.mjs +265 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/* modules/hall-ui/public/tweak-editor.css — the tweak editor, /builders/tweak-editor
|
|
2
|
+
(task 1004321 / BV2.TW10, goal 1000095; ADR 0341 D4 and D11).
|
|
3
|
+
|
|
4
|
+
THE PIXEL CONTRACT. docs/design/mocks/tweak-mode/Editor.dc.html is the
|
|
5
|
+
master design the owner approved, and this sheet is its inline styles, one
|
|
6
|
+
rule per element: the night studio behind, the glass top bar, the paper
|
|
7
|
+
document, the margin notes, the accent mark on a changed line, and the
|
|
8
|
+
"is in" panel. The values are the artboard's own, so the sheet is its own
|
|
9
|
+
token tier (the sky precedent, thinking.css; tests/hall_audit.mjs
|
|
10
|
+
LITERAL_EXEMPT names it). What a gate or a route forced to differ is in the
|
|
11
|
+
task's ship notes and modules/hall-ui/records/tweak-editor.md, one line each.
|
|
12
|
+
|
|
13
|
+
ONE STAGE, BOTH MODES. The artboard is the studio at night with a sheet of
|
|
14
|
+
paper over it. The paper is paper and the room is the room whatever the
|
|
15
|
+
hall's theme is, so the palette sits on the page's STAGE (main.tw-stage),
|
|
16
|
+
never on :root (the pack's :root is spliced after this sheet) and never on
|
|
17
|
+
body, so the hall chrome around the stage keeps its own light and dark.
|
|
18
|
+
The editor opens full screen (tweak-editor.js), which is the artboard. */
|
|
19
|
+
|
|
20
|
+
body[data-page="tweak-editor"] .tw-stage {
|
|
21
|
+
color-scheme: dark;
|
|
22
|
+
--tw-room: #07080d;
|
|
23
|
+
--tw-frame: #14151b;
|
|
24
|
+
--tw-night: #0c1122;
|
|
25
|
+
--tw-moon: #f1e6d2;
|
|
26
|
+
--tw-floor: #0d0d12;
|
|
27
|
+
--tw-lamp: rgba(251, 149, 105, .16);
|
|
28
|
+
--tw-lamp-out: rgba(251, 149, 105, 0);
|
|
29
|
+
--tw-ink: #ffffff;
|
|
30
|
+
--tw-ink-soft: #c0c0c0;
|
|
31
|
+
--tw-ink-faint: #a8a8a8;
|
|
32
|
+
--tw-ink-dim: #8a8a90;
|
|
33
|
+
--tw-glass: rgba(255, 255, 255, .07);
|
|
34
|
+
--tw-bar-back: rgba(7, 8, 13, .84);
|
|
35
|
+
--tw-glass-lift: rgba(255, 255, 255, .12);
|
|
36
|
+
--tw-glass-edge: rgba(255, 255, 255, .16);
|
|
37
|
+
--tw-glass-rim: rgba(255, 255, 255, .2);
|
|
38
|
+
--tw-panel: rgba(255, 255, 255, .08);
|
|
39
|
+
--tw-panel-edge: rgba(255, 255, 255, .18);
|
|
40
|
+
--tw-panel-rim: rgba(255, 255, 255, .22);
|
|
41
|
+
--tw-panel-rule: rgba(255, 255, 255, .12);
|
|
42
|
+
--tw-scrim: rgba(4, 5, 9, .88);
|
|
43
|
+
--tw-drop: rgba(0, 0, 0, .55);
|
|
44
|
+
--tw-accent: #fb9569;
|
|
45
|
+
--tw-on-accent: #000000;
|
|
46
|
+
--tw-link: #fcbfa4;
|
|
47
|
+
--tw-paper: #fbfafc;
|
|
48
|
+
--tw-paper-ink: #17181a;
|
|
49
|
+
--tw-paper-soft: #55565a;
|
|
50
|
+
--tw-paper-faint: #65656a;
|
|
51
|
+
--tw-shared: #4a5bb8;
|
|
52
|
+
--tw-engineer: #a3481a;
|
|
53
|
+
--tw-line-hover: rgba(23, 24, 26, .035);
|
|
54
|
+
--tw-line-focus: rgba(251, 149, 105, .08);
|
|
55
|
+
--tw-face: var(--font-body, Manrope, "Segoe UI", system-ui, sans-serif);
|
|
56
|
+
--tw-mono: var(--font-mono, "JetBrains Mono", ui-monospace, Consolas, monospace);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* The stage fills the content box: the hall's content column is a reading
|
|
60
|
+
width, and the artboard is the whole window. */
|
|
61
|
+
body[data-page="tweak-editor"] .app-content { max-width: none; padding: 0; }
|
|
62
|
+
body[data-page="tweak-editor"] .app-foot { display: none; }
|
|
63
|
+
.tw-stage {
|
|
64
|
+
position: relative;
|
|
65
|
+
min-height: calc(100vh - var(--topbar-h, 56px));
|
|
66
|
+
padding: 28px 0 24px;
|
|
67
|
+
background: var(--tw-room);
|
|
68
|
+
color: var(--tw-ink);
|
|
69
|
+
font-family: var(--tw-face);
|
|
70
|
+
isolation: isolate;
|
|
71
|
+
}
|
|
72
|
+
html[data-fullscreen="on"] .tw-stage { min-height: 100vh; }
|
|
73
|
+
.tw-stage a { color: var(--tw-link); }
|
|
74
|
+
.tw-stage a:hover { color: var(--tw-accent); }
|
|
75
|
+
.tw-stage button,
|
|
76
|
+
.tw-stage textarea { font-family: inherit; }
|
|
77
|
+
|
|
78
|
+
/* The room behind: the window with its moon, the floor, the lamp's glow. It
|
|
79
|
+
stays put while the paper scrolls over it (fixed, beside the rail and under
|
|
80
|
+
the top bar; the whole window in full screen), and it is clipped so it can
|
|
81
|
+
never widen the document. Positions are the artboard's, measured from the
|
|
82
|
+
centre of its 1440 frame. */
|
|
83
|
+
.tw-room {
|
|
84
|
+
position: fixed;
|
|
85
|
+
top: var(--topbar-h, 56px);
|
|
86
|
+
left: var(--sidebar-w-rail, 64px);
|
|
87
|
+
right: 0;
|
|
88
|
+
bottom: 0;
|
|
89
|
+
overflow: hidden;
|
|
90
|
+
pointer-events: none;
|
|
91
|
+
z-index: -1;
|
|
92
|
+
}
|
|
93
|
+
html[data-fullscreen="on"] .tw-room { top: 0; left: 0; }
|
|
94
|
+
.tw-window {
|
|
95
|
+
position: absolute;
|
|
96
|
+
left: calc(50% + 60px);
|
|
97
|
+
top: 86px;
|
|
98
|
+
width: 560px;
|
|
99
|
+
height: 560px;
|
|
100
|
+
box-sizing: border-box;
|
|
101
|
+
border: 16px solid var(--tw-frame);
|
|
102
|
+
border-radius: 6px;
|
|
103
|
+
background: var(--tw-night);
|
|
104
|
+
opacity: .45;
|
|
105
|
+
}
|
|
106
|
+
.tw-moon {
|
|
107
|
+
position: absolute;
|
|
108
|
+
left: 372px;
|
|
109
|
+
top: 64px;
|
|
110
|
+
width: 64px;
|
|
111
|
+
height: 64px;
|
|
112
|
+
border-radius: 50%;
|
|
113
|
+
background: var(--tw-moon);
|
|
114
|
+
}
|
|
115
|
+
.tw-floor {
|
|
116
|
+
position: absolute;
|
|
117
|
+
left: 0;
|
|
118
|
+
right: 0;
|
|
119
|
+
top: 780px;
|
|
120
|
+
bottom: 0;
|
|
121
|
+
background: var(--tw-floor);
|
|
122
|
+
}
|
|
123
|
+
.tw-lamp {
|
|
124
|
+
position: absolute;
|
|
125
|
+
left: calc(50% + 440px);
|
|
126
|
+
top: 560px;
|
|
127
|
+
width: 300px;
|
|
128
|
+
height: 300px;
|
|
129
|
+
border-radius: 50%;
|
|
130
|
+
background: radial-gradient(circle, var(--tw-lamp) 0%, var(--tw-lamp-out) 65%);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* In full screen the shell's one button floats over the night room in either
|
|
134
|
+
theme, so on this page it wears the room's glass, not the hall's light ink. */
|
|
135
|
+
html[data-fullscreen="on"] body[data-page="tweak-editor"] .fullscreen-toggle {
|
|
136
|
+
color: #ffffff;
|
|
137
|
+
background: rgba(255, 255, 255, .07);
|
|
138
|
+
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .16);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* THE GLASS TOP BAR. Sticky, so the four doors stay in reach down a long page
|
|
142
|
+
and the paper passes under the glass. In full screen it steps in far enough
|
|
143
|
+
that the shell's one button (top right) never sits on it. */
|
|
144
|
+
.tw-bar {
|
|
145
|
+
position: sticky;
|
|
146
|
+
top: 28px;
|
|
147
|
+
z-index: 2;
|
|
148
|
+
width: min(1200px, calc(100% - 48px));
|
|
149
|
+
margin: 0 auto;
|
|
150
|
+
box-sizing: border-box;
|
|
151
|
+
min-height: 64px;
|
|
152
|
+
padding: 0 12px 0 22px;
|
|
153
|
+
display: flex;
|
|
154
|
+
align-items: center;
|
|
155
|
+
gap: 14px;
|
|
156
|
+
border-radius: 999px;
|
|
157
|
+
/* The artboard's glass (.07 white) over the room. The bar is sticky and the
|
|
158
|
+
white paper scrolls under it, where .07 glass alone turns white and its
|
|
159
|
+
white words vanish, so the glass lies on a backing of the room's own
|
|
160
|
+
black: over the room it reads exactly as the artboard's bar. */
|
|
161
|
+
background: linear-gradient(var(--tw-glass), var(--tw-glass)), var(--tw-bar-back);
|
|
162
|
+
border: 1px solid var(--tw-glass-edge);
|
|
163
|
+
backdrop-filter: blur(28px) saturate(160%);
|
|
164
|
+
-webkit-backdrop-filter: blur(28px) saturate(160%);
|
|
165
|
+
box-shadow: inset 0 1px 0 var(--tw-glass-rim);
|
|
166
|
+
}
|
|
167
|
+
html[data-fullscreen="on"] .tw-bar { width: min(1200px, calc(100% - 144px)); }
|
|
168
|
+
/* With the chrome showing, the shell's fixed top bar holds the window's top,
|
|
169
|
+
so the stuck bar stops under it instead of sliding beneath it. */
|
|
170
|
+
html:not([data-fullscreen="on"]) .tw-bar { top: calc(var(--topbar-h, 56px) + 12px); }
|
|
171
|
+
.tw-stage .tw-back {
|
|
172
|
+
display: inline-flex;
|
|
173
|
+
align-items: center;
|
|
174
|
+
min-height: 44px;
|
|
175
|
+
font-size: 13.5px;
|
|
176
|
+
text-decoration: none;
|
|
177
|
+
color: var(--tw-ink-soft);
|
|
178
|
+
white-space: nowrap;
|
|
179
|
+
}
|
|
180
|
+
.tw-name { font-size: 15px; color: var(--tw-ink); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
181
|
+
.tw-surface { color: var(--tw-ink-dim); }
|
|
182
|
+
.tw-counter {
|
|
183
|
+
margin-left: auto;
|
|
184
|
+
font-size: 13px;
|
|
185
|
+
color: var(--tw-ink-faint);
|
|
186
|
+
font-variant-numeric: tabular-nums;
|
|
187
|
+
white-space: nowrap;
|
|
188
|
+
}
|
|
189
|
+
.tw-actions { display: flex; align-items: center; gap: 14px; }
|
|
190
|
+
.tw-stage .tw-btn {
|
|
191
|
+
display: inline-flex;
|
|
192
|
+
align-items: center;
|
|
193
|
+
height: 44px;
|
|
194
|
+
padding: 0 16px;
|
|
195
|
+
border-radius: 999px;
|
|
196
|
+
border: 0;
|
|
197
|
+
cursor: pointer;
|
|
198
|
+
font-size: 13.5px;
|
|
199
|
+
text-decoration: none;
|
|
200
|
+
white-space: nowrap;
|
|
201
|
+
color: var(--tw-ink);
|
|
202
|
+
background: var(--tw-glass);
|
|
203
|
+
}
|
|
204
|
+
.tw-stage .tw-btn:hover { color: var(--tw-ink); background: var(--tw-glass-lift); }
|
|
205
|
+
.tw-stage .tw-btn--submit {
|
|
206
|
+
padding: 0 20px;
|
|
207
|
+
font-size: 14px;
|
|
208
|
+
color: var(--tw-on-accent);
|
|
209
|
+
background: var(--tw-accent);
|
|
210
|
+
}
|
|
211
|
+
.tw-stage .tw-btn--submit:hover { color: var(--tw-on-accent); background: var(--tw-link); }
|
|
212
|
+
.tw-stage .tw-btn:disabled { cursor: default; opacity: .5; }
|
|
213
|
+
.tw-stage .tw-btn:focus-visible,
|
|
214
|
+
.tw-stage .tw-back:focus-visible { outline: 2px solid var(--tw-accent); outline-offset: 2px; }
|
|
215
|
+
|
|
216
|
+
/* THE PAPER. Every word of the page, in the order it shows them. It grows
|
|
217
|
+
with the page and the window scrolls (the hall's one-scroller seam), where
|
|
218
|
+
the artboard's fixed 860px sheet scrolled inside itself. */
|
|
219
|
+
.tw-paper {
|
|
220
|
+
position: relative;
|
|
221
|
+
z-index: 1;
|
|
222
|
+
width: min(860px, calc(100% - 48px));
|
|
223
|
+
margin: 24px auto 0;
|
|
224
|
+
box-sizing: border-box;
|
|
225
|
+
border-radius: 8px;
|
|
226
|
+
background: var(--tw-paper);
|
|
227
|
+
color: var(--tw-paper-ink);
|
|
228
|
+
box-shadow: 0 30px 80px var(--tw-drop);
|
|
229
|
+
padding: 64px 88px 72px 88px;
|
|
230
|
+
}
|
|
231
|
+
.tw-head { display: flex; flex-direction: column; gap: 6px; margin-bottom: 34px; }
|
|
232
|
+
.tw-paper h1 {
|
|
233
|
+
margin: 0;
|
|
234
|
+
font-family: var(--tw-face);
|
|
235
|
+
font-size: 40px;
|
|
236
|
+
font-weight: 300;
|
|
237
|
+
line-height: 1.2;
|
|
238
|
+
letter-spacing: -0.03em;
|
|
239
|
+
color: var(--tw-paper-ink);
|
|
240
|
+
}
|
|
241
|
+
.tw-intro { margin: 0; font-size: 14.5px; line-height: 1.5; color: var(--tw-paper-soft); }
|
|
242
|
+
.tw-section { margin-bottom: 26px; }
|
|
243
|
+
.tw-paper h2 {
|
|
244
|
+
margin: 0 0 6px 0;
|
|
245
|
+
font-family: var(--tw-mono);
|
|
246
|
+
font-size: 11px;
|
|
247
|
+
font-weight: 400;
|
|
248
|
+
line-height: 1.4;
|
|
249
|
+
letter-spacing: 0.14em;
|
|
250
|
+
text-transform: uppercase;
|
|
251
|
+
color: var(--tw-paper-faint);
|
|
252
|
+
}
|
|
253
|
+
.tw-row { display: grid; grid-template-columns: minmax(0, 1fr) 150px; gap: 18px; align-items: start; }
|
|
254
|
+
.tw-line {
|
|
255
|
+
display: block;
|
|
256
|
+
box-sizing: border-box;
|
|
257
|
+
width: 100%;
|
|
258
|
+
resize: none;
|
|
259
|
+
overflow: hidden;
|
|
260
|
+
border: 0;
|
|
261
|
+
border-radius: 4px;
|
|
262
|
+
padding: 5px 10px;
|
|
263
|
+
margin: 0 0 0 -10px;
|
|
264
|
+
font-size: 17px;
|
|
265
|
+
line-height: 1.6;
|
|
266
|
+
color: var(--tw-paper-ink);
|
|
267
|
+
background: transparent;
|
|
268
|
+
}
|
|
269
|
+
.tw-line:hover { background: var(--tw-line-hover); }
|
|
270
|
+
.tw-line:focus { outline: none; background: var(--tw-line-focus); }
|
|
271
|
+
/* The artboard's focus is the warm wash alone; the hall's focus floor wants a
|
|
272
|
+
ring a keyboard reader can find, so a hairline of the accent joins it. */
|
|
273
|
+
.tw-line:focus-visible { outline: 1px solid var(--tw-accent); outline-offset: -1px; background: var(--tw-line-focus); }
|
|
274
|
+
/* The accent mark: a changed line carries a small bar of the accent. */
|
|
275
|
+
.tw-line--changed { box-shadow: inset 3px 0 0 var(--tw-accent); }
|
|
276
|
+
.tw-line[readonly] { cursor: default; }
|
|
277
|
+
.tw-line[readonly]:hover { background: transparent; }
|
|
278
|
+
.tw-note { padding-top: 9px; font-size: 12px; line-height: 1.4; color: var(--tw-paper-faint); overflow-wrap: anywhere; }
|
|
279
|
+
.tw-note--shared { color: var(--tw-shared); }
|
|
280
|
+
.tw-note--engineer { color: var(--tw-engineer); }
|
|
281
|
+
.tw-sr {
|
|
282
|
+
position: absolute;
|
|
283
|
+
width: 1px;
|
|
284
|
+
height: 1px;
|
|
285
|
+
overflow: hidden;
|
|
286
|
+
clip: rect(0 0 0 0);
|
|
287
|
+
white-space: nowrap;
|
|
288
|
+
}
|
|
289
|
+
.tw-loading { margin: 0; font-size: 15px; line-height: 1.6; color: var(--tw-paper-soft); }
|
|
290
|
+
|
|
291
|
+
/* A notice inside the paper, above the words: the page is someone else's, the
|
|
292
|
+
upload was refused, the submit named lines. Not on the artboard, which has
|
|
293
|
+
no failing state; it wears the paper's own ink and the engineer note's warm
|
|
294
|
+
brown, and its one action is a paper pill. */
|
|
295
|
+
.tw-notice {
|
|
296
|
+
margin: 0 0 30px 0;
|
|
297
|
+
padding: 16px 18px;
|
|
298
|
+
border-radius: 8px;
|
|
299
|
+
background: var(--tw-line-hover);
|
|
300
|
+
box-shadow: inset 3px 0 0 var(--tw-engineer);
|
|
301
|
+
color: var(--tw-paper-ink);
|
|
302
|
+
font-size: 14.5px;
|
|
303
|
+
line-height: 1.5;
|
|
304
|
+
}
|
|
305
|
+
.tw-notice p { margin: 0; }
|
|
306
|
+
.tw-notice ul { margin: 8px 0 0 0; padding-left: 18px; }
|
|
307
|
+
.tw-notice li { margin: 2px 0; }
|
|
308
|
+
.tw-notice__act {
|
|
309
|
+
margin-top: 12px;
|
|
310
|
+
height: 44px;
|
|
311
|
+
padding: 0 20px;
|
|
312
|
+
border-radius: 999px;
|
|
313
|
+
border: 0;
|
|
314
|
+
cursor: pointer;
|
|
315
|
+
font-size: 14px;
|
|
316
|
+
color: var(--tw-on-accent);
|
|
317
|
+
background: var(--tw-accent);
|
|
318
|
+
}
|
|
319
|
+
.tw-notice__act:focus-visible { outline: 2px solid var(--tw-paper-ink); outline-offset: 2px; }
|
|
320
|
+
|
|
321
|
+
/* THE "IS IN" PANEL. Glass over the dimmed room, the next page named, and
|
|
322
|
+
three ways on. Fixed, so it sits over the whole window. The artboard's
|
|
323
|
+
scrim is .45, but the panel sits over the white PAPER, and its white and
|
|
324
|
+
grey words on paper seen through .45 fall under AA (2.9:1). At .88 the paper
|
|
325
|
+
dims to the room's own dark and every word in the panel clears 4.5:1. */
|
|
326
|
+
.tw-done {
|
|
327
|
+
position: fixed;
|
|
328
|
+
inset: 0;
|
|
329
|
+
z-index: var(--z-modal, 160);
|
|
330
|
+
display: flex;
|
|
331
|
+
align-items: center;
|
|
332
|
+
justify-content: center;
|
|
333
|
+
padding: 16px;
|
|
334
|
+
background: var(--tw-scrim);
|
|
335
|
+
}
|
|
336
|
+
.tw-done[hidden] { display: none; }
|
|
337
|
+
.tw-done__panel {
|
|
338
|
+
width: 560px;
|
|
339
|
+
max-width: 100%;
|
|
340
|
+
box-sizing: border-box;
|
|
341
|
+
padding: 36px;
|
|
342
|
+
border-radius: 30px;
|
|
343
|
+
background: var(--tw-panel);
|
|
344
|
+
border: 1px solid var(--tw-panel-edge);
|
|
345
|
+
backdrop-filter: blur(30px) saturate(160%);
|
|
346
|
+
-webkit-backdrop-filter: blur(30px) saturate(160%);
|
|
347
|
+
box-shadow: inset 0 1px 0 var(--tw-panel-rim), 0 40px 90px var(--tw-drop);
|
|
348
|
+
display: flex;
|
|
349
|
+
flex-direction: column;
|
|
350
|
+
gap: 22px;
|
|
351
|
+
}
|
|
352
|
+
.tw-done__head { display: flex; flex-direction: column; gap: 8px; }
|
|
353
|
+
.tw-done__title { margin: 0; font-size: 30px; font-weight: 200; line-height: 1.2; letter-spacing: -0.03em; color: var(--tw-ink); }
|
|
354
|
+
.tw-done__title:focus { outline: none; }
|
|
355
|
+
.tw-done__title:focus-visible { outline: 2px solid var(--tw-accent); outline-offset: 4px; }
|
|
356
|
+
.tw-done__sub { margin: 0; font-size: 15px; line-height: 1.5; color: var(--tw-ink-soft); }
|
|
357
|
+
.tw-next { display: flex; flex-direction: column; gap: 4px; padding-top: 18px; border-top: 1px solid var(--tw-panel-rule); }
|
|
358
|
+
.tw-next__label { font-family: var(--tw-mono); font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--tw-ink-faint); }
|
|
359
|
+
.tw-next__title { font-size: 24px; font-weight: 300; line-height: 1.3; color: var(--tw-ink); }
|
|
360
|
+
.tw-next__why { font-size: 14px; line-height: 1.5; color: var(--tw-ink-soft); }
|
|
361
|
+
.tw-picks { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
362
|
+
.tw-pick {
|
|
363
|
+
height: 44px;
|
|
364
|
+
padding: 0 16px;
|
|
365
|
+
border-radius: 999px;
|
|
366
|
+
cursor: pointer;
|
|
367
|
+
background: var(--tw-glass);
|
|
368
|
+
border: 1px solid var(--tw-glass-edge);
|
|
369
|
+
color: var(--tw-ink);
|
|
370
|
+
font-size: 13.5px;
|
|
371
|
+
}
|
|
372
|
+
.tw-pick:hover { background: var(--tw-glass-lift); }
|
|
373
|
+
.tw-done__acts { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
|
|
374
|
+
.tw-take {
|
|
375
|
+
height: 48px;
|
|
376
|
+
padding: 0 26px;
|
|
377
|
+
border-radius: 999px;
|
|
378
|
+
cursor: pointer;
|
|
379
|
+
border: 0;
|
|
380
|
+
background: var(--tw-accent);
|
|
381
|
+
color: var(--tw-on-accent);
|
|
382
|
+
font-size: 15px;
|
|
383
|
+
}
|
|
384
|
+
.tw-take:hover { background: var(--tw-link); }
|
|
385
|
+
.tw-take:disabled { cursor: default; opacity: .5; }
|
|
386
|
+
/* "pick another" is the artboard's link, drawn as a button because it opens
|
|
387
|
+
the other pages in place (the studio artboard's own "pick another"). */
|
|
388
|
+
.tw-stage .tw-linkish {
|
|
389
|
+
display: inline-flex;
|
|
390
|
+
align-items: center;
|
|
391
|
+
min-height: 44px;
|
|
392
|
+
padding: 0;
|
|
393
|
+
border: 0;
|
|
394
|
+
background: none;
|
|
395
|
+
cursor: pointer;
|
|
396
|
+
font-size: 13.5px;
|
|
397
|
+
color: var(--tw-ink-faint);
|
|
398
|
+
text-decoration: underline;
|
|
399
|
+
}
|
|
400
|
+
.tw-stage .tw-linkish:hover { color: var(--tw-accent); }
|
|
401
|
+
.tw-stage .tw-home { margin-left: auto; }
|
|
402
|
+
.tw-take:focus-visible,
|
|
403
|
+
.tw-pick:focus-visible,
|
|
404
|
+
.tw-stage .tw-linkish:focus-visible { outline: 2px solid var(--tw-accent); outline-offset: 2px; }
|
|
405
|
+
.tw-done__error { margin: 0; font-size: 14px; line-height: 1.5; color: var(--tw-link); }
|
|
406
|
+
|
|
407
|
+
/* PHONE WIDTH (not on the artboard, which is 1440 wide). The bar stops being
|
|
408
|
+
one pill and becomes a glass card of two rows: where you are and how far,
|
|
409
|
+
then the four doors, wrapping. The paper takes the width, and each margin
|
|
410
|
+
note drops under its line. */
|
|
411
|
+
@media (max-width: 760px) {
|
|
412
|
+
.tw-stage { padding-top: 12px; }
|
|
413
|
+
.tw-bar,
|
|
414
|
+
html[data-fullscreen="on"] .tw-bar {
|
|
415
|
+
top: 8px;
|
|
416
|
+
width: calc(100% - 24px);
|
|
417
|
+
flex-wrap: wrap;
|
|
418
|
+
gap: 4px 12px;
|
|
419
|
+
padding: 6px 12px 12px 16px;
|
|
420
|
+
border-radius: 24px;
|
|
421
|
+
}
|
|
422
|
+
/* The shell's button floats in the top right corner in full screen. */
|
|
423
|
+
html[data-fullscreen="on"] .tw-bar { padding-right: 56px; }
|
|
424
|
+
.tw-name { flex: 1 1 0; }
|
|
425
|
+
.tw-counter { flex: 1 0 100%; margin-left: 0; }
|
|
426
|
+
.tw-actions { flex: 1 0 100%; flex-wrap: wrap; gap: 8px; padding-top: 6px; }
|
|
427
|
+
html[data-fullscreen="on"] .tw-actions { margin-right: -44px; }
|
|
428
|
+
.tw-paper { width: calc(100% - 24px); margin-top: 16px; padding: 36px 20px 44px; }
|
|
429
|
+
.tw-paper h1 { font-size: 32px; }
|
|
430
|
+
.tw-row { grid-template-columns: minmax(0, 1fr); gap: 0; }
|
|
431
|
+
.tw-note { padding: 0 0 8px 0; }
|
|
432
|
+
.tw-note:empty { display: none; }
|
|
433
|
+
.tw-done__panel { padding: 28px 24px; border-radius: 24px; }
|
|
434
|
+
.tw-done__title { font-size: 26px; }
|
|
435
|
+
.tw-stage .tw-home { margin-left: 0; }
|
|
436
|
+
}
|
|
437
|
+
@media (max-width: 768px) { .tw-room { left: 0; } }
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!-- data-artist="on": the studio's mode marker (studio.html). The editor is
|
|
3
|
+
the artist's own room too, entered from the studio. -->
|
|
4
|
+
<html lang="en" data-artist="on">
|
|
5
|
+
<head>
|
|
6
|
+
<meta charset="utf-8">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
<title>Tweak a page — {{worldName}}</title>
|
|
9
|
+
<meta name="description" content="Tweak Mode's editor: every word of one page as a single document, typed over in place and saved as you go, then submitted as one batch.">
|
|
10
|
+
<link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
|
|
11
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
12
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
13
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
|
|
14
|
+
<link rel="stylesheet" href="/builders/tweak-editor.css?v=2026-09-28-tw10">
|
|
15
|
+
</head>
|
|
16
|
+
<!-- THE TWEAK EDITOR (task 1004321 / BV2.TW10, goal 1000095; ADR 0341 D4 and
|
|
17
|
+
D11). docs/design/mocks/tweak-mode/Editor.dc.html is the master design:
|
|
18
|
+
the glass top bar, one paper document holding every word of the page in
|
|
19
|
+
page order, typed over in place and autosaved, the margin notes, the
|
|
20
|
+
accent mark and "was:" on a changed line, and the "is in" panel with take
|
|
21
|
+
next, pick another and back to the studio. Every difference a gate or a
|
|
22
|
+
route forced is listed in modules/hall-ui/records/tweak-editor.md.
|
|
23
|
+
|
|
24
|
+
Which page: ?page=<page id> (landing:index). With none, the page the
|
|
25
|
+
caller holds (GET /copy-desk/next's resume). The reads and writes are
|
|
26
|
+
copy-desk's: GET and PUT /copy-desk/pages/:pageId/draft, the .docx pair,
|
|
27
|
+
the submit, GET /copy-desk/next and the claim "take next" posts.
|
|
28
|
+
|
|
29
|
+
data-fullscreen-switch: the shell's full-screen switch (task 1004327). The
|
|
30
|
+
artboard is the editor over the whole window, so the editor opens full
|
|
31
|
+
screen the first time (tweak-editor.js), and the switch is remembered. -->
|
|
32
|
+
<body data-page="tweak-editor" data-page-title="Tweak a page" data-fullscreen-switch>
|
|
33
|
+
|
|
34
|
+
<div class="app">
|
|
35
|
+
<aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
|
|
36
|
+
<div class="app-scrim" id="app-scrim" hidden></div>
|
|
37
|
+
<div class="app-body">
|
|
38
|
+
<header class="app-topbar" id="app-topbar"></header>
|
|
39
|
+
<div class="app-content">
|
|
40
|
+
|
|
41
|
+
<main id="hall-main" class="tw-stage">
|
|
42
|
+
<!-- The studio at night, dimmed behind the paper. -->
|
|
43
|
+
<div class="tw-room" aria-hidden="true">
|
|
44
|
+
<div class="tw-window"><div class="tw-moon"></div></div>
|
|
45
|
+
<div class="tw-floor"></div>
|
|
46
|
+
<div class="tw-lamp"></div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<!-- A <div>, not a <header>: the shell's top bar is the page's one
|
|
50
|
+
<header> (the kit's one-header-main-h1 floor). -->
|
|
51
|
+
<div class="tw-bar" id="tw-bar">
|
|
52
|
+
<a class="tw-back" href="/builders/studio">← studio</a>
|
|
53
|
+
<span class="tw-name"><span id="tw-name">Tweak a page</span> <span class="tw-surface" id="tw-surface"></span></span>
|
|
54
|
+
<span class="tw-counter" id="tw-counter" aria-live="polite"></span>
|
|
55
|
+
<div class="tw-actions">
|
|
56
|
+
<a class="tw-btn" id="tw-open" href="/builders/studio" target="_blank" rel="noopener">open the page ↗</a>
|
|
57
|
+
<button class="tw-btn" id="tw-download" type="button" disabled>download .docx</button>
|
|
58
|
+
<button class="tw-btn" id="tw-upload" type="button" disabled>upload .docx</button>
|
|
59
|
+
<input id="tw-file" type="file" accept=".docx,application/vnd.openxmlformats-officedocument.wordprocessingml.document" hidden>
|
|
60
|
+
<button class="tw-btn tw-btn--submit" id="tw-submit" type="button" disabled>submit page</button>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<article class="tw-paper" id="tw-paper" aria-labelledby="tw-title">
|
|
65
|
+
<div class="tw-head">
|
|
66
|
+
<h1 id="tw-title">Tweak a page</h1>
|
|
67
|
+
<p class="tw-intro">Every word on the page, in the order it shows them. Type over anything, or download it, work in Word, and upload it back.</p>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="tw-notice" id="tw-notice" role="status" hidden></div>
|
|
70
|
+
<div id="tw-doc">
|
|
71
|
+
<p class="tw-loading">Reading the page…</p>
|
|
72
|
+
</div>
|
|
73
|
+
</article>
|
|
74
|
+
|
|
75
|
+
<!-- Submit: "<page> is in", then next up. Hidden until the submit lands. -->
|
|
76
|
+
<div class="tw-done" id="tw-done" hidden>
|
|
77
|
+
<div class="tw-done__panel" role="dialog" aria-modal="true" aria-labelledby="tw-done-title">
|
|
78
|
+
<div class="tw-done__head">
|
|
79
|
+
<h2 class="tw-done__title" id="tw-done-title" tabindex="-1">The page is in.</h2>
|
|
80
|
+
<p class="tw-done__sub">It comes back to your studio once it is applied, and nothing ships until you have seen it.</p>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="tw-next" id="tw-next">
|
|
83
|
+
<span class="tw-next__label">next up</span>
|
|
84
|
+
<span class="tw-next__title" id="tw-next-title">Finding the next page…</span>
|
|
85
|
+
<span class="tw-next__why" id="tw-next-why"></span>
|
|
86
|
+
</div>
|
|
87
|
+
<p class="tw-done__error" id="tw-done-error" role="alert" hidden></p>
|
|
88
|
+
<div class="tw-done__acts">
|
|
89
|
+
<button class="tw-take" id="tw-take" type="button" disabled>take next</button>
|
|
90
|
+
<button class="tw-linkish" id="tw-pick" type="button" aria-expanded="false" aria-controls="tw-picks" hidden>pick another</button>
|
|
91
|
+
<a class="tw-linkish tw-home" href="/builders/studio">back to the studio</a>
|
|
92
|
+
</div>
|
|
93
|
+
<div class="tw-picks" id="tw-picks" hidden></div>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
</main>
|
|
97
|
+
|
|
98
|
+
<footer class="app-foot" id="app-foot"></footer>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
104
|
+
|
|
105
|
+
<script src="/builders/shell.js?v=2026-09-27-fullscreen"></script>
|
|
106
|
+
<script src="/builders/palette.js" defer></script>
|
|
107
|
+
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
108
|
+
<script src="/builders/tweak-editor-lib.js?v=2026-09-28-tw10"></script>
|
|
109
|
+
<script src="/builders/tweak-editor.js?v=2026-09-28-tw10"></script>
|
|
110
|
+
</body>
|
|
111
|
+
</html>
|