@bongos/core 1.24.60 → 1.24.62
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 +38 -33
- package/config/branding.neutral.json +2 -1
- package/docs/branding-contract.md +1 -1
- package/docs/copy-inventory.md +55 -56
- package/docs/copy-registry.json +64 -73
- package/docs/file-map.md +2 -2
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +229 -197
- package/modules/hall-ui/public/approval-queue.css +5 -3
- package/modules/hall-ui/public/studio.css +19 -20
- package/modules/hall-ui/public/studio.html +1 -2
- package/modules/hall-ui/public/style.css +17 -5
- package/modules/hall-ui/public/thinking.css +18 -1
- package/modules/hall-ui/records/studio.md +2 -1
- package/modules/hall-ui/specimens/components.css +7 -0
- package/modules/public-landing/public/index.html +4 -3
- package/modules/status-ui/public/README.md +1 -1
- package/modules/ui-design/docs/design-contract.md +3 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +12 -0
- package/src/bongos/serve-internal.js +5 -3
- package/src/branding.js +2 -0
- package/src/module-api.js +1 -1
- package/tests/brand_surfaces.mjs +14 -0
- package/tests/hall_studio_home.mjs +8 -1
- package/tests/hall_topbar_320.mjs +145 -0
- package/tests/status_company_attribution.mjs +2 -2
|
@@ -67,9 +67,11 @@ html.aq-open { overflow: hidden; }
|
|
|
67
67
|
min-width: 0;
|
|
68
68
|
box-sizing: border-box;
|
|
69
69
|
padding: 26px 30px;
|
|
70
|
-
|
|
70
|
+
/* the shared dialog's corner and hairline (controls.css .hall-dialog, task
|
|
71
|
+
1005137): the glass is the artboard's, the shape is the hall's one dialog */
|
|
72
|
+
border-radius: var(--ds-radius-panel);
|
|
71
73
|
background: var(--aq-glass);
|
|
72
|
-
border:
|
|
74
|
+
border: var(--line) solid var(--aq-glass-edge);
|
|
73
75
|
backdrop-filter: blur(30px) saturate(160%);
|
|
74
76
|
-webkit-backdrop-filter: blur(30px) saturate(160%);
|
|
75
77
|
box-shadow: inset 0 1px 0 var(--aq-glass-rim), var(--ds-shadow-pop);
|
|
@@ -309,7 +311,7 @@ html.aq-open { overflow: hidden; }
|
|
|
309
311
|
.aq-overlay { padding: 8px; align-items: stretch; --aq-scrim: rgba(4, 5, 9, .88); }
|
|
310
312
|
html[data-theme] .aq-overlay,
|
|
311
313
|
html:not([data-theme]) .aq-overlay { --aq-scrim: rgba(4, 5, 9, .88); }
|
|
312
|
-
.aq-dialog { width: min(100%, calc(100vw - 16px)); height: min(100%, calc(100dvh - 16px)); padding: 18px 16px;
|
|
314
|
+
.aq-dialog { width: min(100%, calc(100vw - 16px)); height: min(100%, calc(100dvh - 16px)); padding: 18px 16px; overflow-y: auto; gap: 14px; }
|
|
313
315
|
.aq-dialog > * { flex-shrink: 0; }
|
|
314
316
|
.aq-head { flex-wrap: wrap; row-gap: 10px; }
|
|
315
317
|
.aq-eyebrow { order: 1; flex: 1 1 auto; }
|
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
in the window, lighter towers with unlit panes, a warm wall and floor, the
|
|
26
26
|
lamp off and a patch of sun on the floor. The glass panels over the room
|
|
27
27
|
follow its sky; the hall chrome still keeps its own light and dark.
|
|
28
|
+
EXCEPT IN DARK (task 1005137): the day room is a light-theme room. Under the
|
|
29
|
+
hall's dark theme a cream room sat beside a black rail and bar, so every day
|
|
30
|
+
rule below is scoped html:not([data-theme="dark"]) and dark keeps the night
|
|
31
|
+
room at any hour. data-sky is only ever set by studio.js after shell.js has
|
|
32
|
+
set data-theme, so the one selector covers first paint too.
|
|
28
33
|
|
|
29
34
|
The second half is the older reviews' garden (R06 of goal 1000074, task
|
|
30
35
|
1003117), which still answers the reviews filed before Tweak Mode (TW14) in
|
|
@@ -85,8 +90,8 @@ body[data-page="studio"] {
|
|
|
85
90
|
inks are set darker than the mockup's so they hold AA on the glass over a
|
|
86
91
|
light wall (records/studio.md): the link, and the credits figure and focus
|
|
87
92
|
ring, which by night are the accent. */
|
|
88
|
-
body[data-page="ai-studio"][data-sky="day"],
|
|
89
|
-
body[data-page="studio"][data-sky="day"] {
|
|
93
|
+
html:not([data-theme="dark"]) body[data-page="ai-studio"][data-sky="day"],
|
|
94
|
+
html:not([data-theme="dark"]) body[data-page="studio"][data-sky="day"] {
|
|
90
95
|
--st-room: #efe7dc;
|
|
91
96
|
--st-frame: #d6c9b7;
|
|
92
97
|
--st-night: #bfdaf0;
|
|
@@ -132,17 +137,17 @@ body[data-page="studio"][data-sky="day"] {
|
|
|
132
137
|
}
|
|
133
138
|
/* The panels' scrim is the day wall's own tint, whatever the hall's theme, set
|
|
134
139
|
on the overlay so it outranks the theme's .42 and the phone's .88. */
|
|
135
|
-
body[data-page="studio"][data-sky="day"] .st-overlay {
|
|
140
|
+
html:not([data-theme="dark"]) body[data-page="studio"][data-sky="day"] .st-overlay {
|
|
136
141
|
--st-scrim: rgba(239, 231, 220, .9);
|
|
137
142
|
color-scheme: light;
|
|
138
143
|
}
|
|
139
|
-
body[data-page="ai-studio"][data-sky="day"] .studio,
|
|
140
|
-
body[data-page="studio"][data-sky="day"] .studio { color-scheme: light; }
|
|
144
|
+
html:not([data-theme="dark"]) body[data-page="ai-studio"][data-sky="day"] .studio,
|
|
145
|
+
html:not([data-theme="dark"]) body[data-page="studio"][data-sky="day"] .studio { color-scheme: light; }
|
|
141
146
|
/* The Approval queue (approval-queue.css) is night glass on its own root; over
|
|
142
147
|
the day room it wears the same day glass as the studio's own panels. Its
|
|
143
148
|
rendered page stays on its paper frame, and its accent button stays the
|
|
144
149
|
accent, which carries black words at AA in either sky. */
|
|
145
|
-
body[data-page="studio"][data-sky="day"] .aq-overlay {
|
|
150
|
+
html:not([data-theme="dark"]) body[data-page="studio"][data-sky="day"] .aq-overlay {
|
|
146
151
|
color-scheme: light;
|
|
147
152
|
--aq-scrim: rgba(239, 231, 220, .9);
|
|
148
153
|
--aq-glass: rgba(255, 255, 255, .75);
|
|
@@ -159,7 +164,7 @@ body[data-page="studio"][data-sky="day"] .aq-overlay {
|
|
|
159
164
|
--aq-link: var(--accent-deep);
|
|
160
165
|
--aq-rule: rgba(60, 40, 20, .12);
|
|
161
166
|
}
|
|
162
|
-
body[data-page="studio"][data-sky="day"] .aq-overlay :focus-visible { outline-color: var(--st-focus); }
|
|
167
|
+
html:not([data-theme="dark"]) body[data-page="studio"][data-sky="day"] .aq-overlay :focus-visible { outline-color: var(--st-focus); }
|
|
163
168
|
|
|
164
169
|
/* The stage fills the content box: the hall's content column is a reading
|
|
165
170
|
width, and the artboard is the whole window. */
|
|
@@ -288,11 +293,11 @@ html[data-fullscreen="on"] body[data-page="studio"] .fullscreen-toggle {
|
|
|
288
293
|
|
|
289
294
|
/* The day room: the sun rides higher and larger than the moon, the stars and
|
|
290
295
|
the lamp's glow are gone, the clouds and the patch of sun are there. */
|
|
291
|
-
body[data-sky="day"] .st-moon { left: 394px; top: 42px; width: 76px; height: 76px; box-shadow: 0 0 80px 26px var(--st-moon-halo); }
|
|
292
|
-
body[data-sky="day"] .st-star,
|
|
293
|
-
body[data-sky="day"] .st-glow { opacity: 0; }
|
|
294
|
-
body[data-sky="day"] .st-cloud { opacity: .9; }
|
|
295
|
-
body[data-sky="day"] .st-sunpatch { opacity: .7; }
|
|
296
|
+
html:not([data-theme="dark"]) body[data-sky="day"] .st-moon { left: 394px; top: 42px; width: 76px; height: 76px; box-shadow: 0 0 80px 26px var(--st-moon-halo); }
|
|
297
|
+
html:not([data-theme="dark"]) body[data-sky="day"] .st-star,
|
|
298
|
+
html:not([data-theme="dark"]) body[data-sky="day"] .st-glow { opacity: 0; }
|
|
299
|
+
html:not([data-theme="dark"]) body[data-sky="day"] .st-cloud { opacity: .9; }
|
|
300
|
+
html:not([data-theme="dark"]) body[data-sky="day"] .st-sunpatch { opacity: .7; }
|
|
296
301
|
|
|
297
302
|
/* The room's only motion: the clouds drift, and a change of sky fades over
|
|
298
303
|
.8s. Both live only without a reduced-motion preference, and style.css's
|
|
@@ -312,16 +317,10 @@ body[data-sky="day"] .st-sunpatch { opacity: .7; }
|
|
|
312
317
|
.st-actions { position: relative; z-index: 1; }
|
|
313
318
|
|
|
314
319
|
/* ---------------------------------------------------------------------------
|
|
315
|
-
The top row: the room's name,
|
|
320
|
+
The top row: the tally pill. (The room's name, a mono eyebrow above the
|
|
321
|
+
greeting, went in task 1005137: DESIGN.md bans a label block over the h1.)
|
|
316
322
|
--------------------------------------------------------------------------- */
|
|
317
323
|
.st-top { display: flex; align-items: center; min-height: 48px; gap: 16px; flex-wrap: wrap; }
|
|
318
|
-
.st-eyebrow {
|
|
319
|
-
font-family: var(--st-mono);
|
|
320
|
-
font-size: 11px;
|
|
321
|
-
letter-spacing: .14em;
|
|
322
|
-
text-transform: uppercase;
|
|
323
|
-
color: var(--st-ink-faint);
|
|
324
|
-
}
|
|
325
324
|
.st-tally {
|
|
326
325
|
margin-left: auto;
|
|
327
326
|
display: flex;
|
|
@@ -80,11 +80,10 @@
|
|
|
80
80
|
<div class="st-leaf st-leaf--2"></div>
|
|
81
81
|
</div>
|
|
82
82
|
|
|
83
|
-
<!-- The
|
|
83
|
+
<!-- The artist's tally (GET /copy-desk/tally)
|
|
84
84
|
in a glass pill top right. The shell's full-screen button keeps the
|
|
85
85
|
window's corner above it. -->
|
|
86
86
|
<div class="st-top">
|
|
87
|
-
<span class="st-eyebrow">{{worldName}} · studio</span>
|
|
88
87
|
<div class="st-tally" id="st-tally" hidden>
|
|
89
88
|
<span class="st-tally__item"><span class="st-tally__n st-tally__n--credits" id="st-tally-credits">0</span><span class="st-tally__label">credits</span></span>
|
|
90
89
|
<span class="st-tally__rule" aria-hidden="true"></span>
|
|
@@ -1307,7 +1307,7 @@ html[data-fullscreen="on"][data-fullscreen-plain="on"] .app-content { padding-to
|
|
|
1307
1307
|
display: inline-flex;
|
|
1308
1308
|
align-items: center;
|
|
1309
1309
|
gap: 2px;
|
|
1310
|
-
padding:
|
|
1310
|
+
padding: 2px;
|
|
1311
1311
|
border-radius: var(--radius-pill);
|
|
1312
1312
|
box-shadow: inset 0 0 0 1px var(--glass-edge);
|
|
1313
1313
|
flex-shrink: 0;
|
|
@@ -1959,6 +1959,15 @@ html.pb-editing main { padding-bottom: 180px; }
|
|
|
1959
1959
|
.topbar-trail .topbar-back svg { display: block; }
|
|
1960
1960
|
.topbar-trail .topbar-back { padding: 0 8px; }
|
|
1961
1961
|
.topbar-trail ~ .topbar-title { display: block; }
|
|
1962
|
+
/* The 320 top bar (task 1005137): the menu, the Update pill, the search, the
|
|
1963
|
+
mode switch and the three toggles measured 340px in a 320px bar, so the
|
|
1964
|
+
theme and full-screen icons were cut off at the edge. Here the bar packs
|
|
1965
|
+
tighter and each icon button is 28px — still above the 24px floor — so the
|
|
1966
|
+
whole row fits with room to spare. Pinned in tests/hall_topbar_320.mjs. */
|
|
1967
|
+
.app-topbar { gap: var(--ds-space-hair); padding: 0 var(--ds-space-xs); }
|
|
1968
|
+
.topbar-menu, .theme-toggle, .fullscreen-toggle { width: 28px; height: 28px; }
|
|
1969
|
+
.zoom-toggle { min-width: 28px; height: 28px; padding: 0 6px; }
|
|
1970
|
+
.topbar-side .topbar-jump { min-width: 28px; padding: 0 6px; }
|
|
1962
1971
|
}
|
|
1963
1972
|
.zoom-toggle__pct {
|
|
1964
1973
|
font-size: 12px;
|
|
@@ -4335,15 +4344,18 @@ a.stat-tile:hover .stat-tile__sub { color: var(--ink-soft); }
|
|
|
4335
4344
|
no new theme vars, so dark mode follows the neutral flip for free. The overlay
|
|
4336
4345
|
mirrors .deeplink-card's posture (fixed scrim + centred panel, z above the
|
|
4337
4346
|
drawer) so the two modal surfaces read as one system. */
|
|
4347
|
+
/* One shape with its neighbour (task 1005137): the search slot and the mode
|
|
4348
|
+
switch beside it are both a 32px pill with the same glass hairline, where the
|
|
4349
|
+
slot used to be a squarer, filled box next to a round one. */
|
|
4338
4350
|
.topbar-jump {
|
|
4339
4351
|
display: inline-flex;
|
|
4340
4352
|
align-items: center;
|
|
4341
4353
|
gap: 8px;
|
|
4342
4354
|
height: 32px;
|
|
4343
|
-
padding: 0 10px;
|
|
4344
|
-
border: 1px solid var(--
|
|
4345
|
-
border-radius: var(--radius-
|
|
4346
|
-
background:
|
|
4355
|
+
padding: 0 12px 0 10px;
|
|
4356
|
+
border: 1px solid var(--glass-edge);
|
|
4357
|
+
border-radius: var(--radius-pill);
|
|
4358
|
+
background: none;
|
|
4347
4359
|
color: var(--ink-faint);
|
|
4348
4360
|
font: inherit;
|
|
4349
4361
|
font-size: 13px;
|
|
@@ -109,6 +109,21 @@ html[data-fullscreen="on"] .sky-stage .top { padding-right: 184px; }
|
|
|
109
109
|
|
|
110
110
|
.top { position: fixed; left: 0; right: 0; top: 0; padding: calc(16px + env(safe-area-inset-top, 0px)) 20px 0; display: flex; align-items: flex-start; gap: 16px; pointer-events: none; z-index: 5; }
|
|
111
111
|
.top > * { pointer-events: auto; }
|
|
112
|
+
/* The sky is pannable, so its marks will always pass under the header strip and
|
|
113
|
+
the legend. Each band stands on a veil of the void that fades into the sky
|
|
114
|
+
(task 1005137: dots painted over the counts, the what-if pill and the legend).
|
|
115
|
+
The veil sits inside the band's own stacking layer, under its words and above
|
|
116
|
+
the canvas, and never catches the pointer. */
|
|
117
|
+
.top::before, .foot::before { content: ""; position: absolute; left: 0; right: 0; z-index: -1; pointer-events: none; }
|
|
118
|
+
.top::before { top: 0; bottom: -24px; background: linear-gradient(to bottom, var(--void) 62%, transparent); }
|
|
119
|
+
.foot::before { bottom: 0; top: -24px; background: linear-gradient(to top, var(--void) 62%, transparent); }
|
|
120
|
+
/* the ground the sky's own words stand on: a glow of the void itself, so a dot
|
|
121
|
+
passing under a count or a legend word dims instead of painting over it — in
|
|
122
|
+
both modes, since the void is the mode's ground */
|
|
123
|
+
.top, .foot, .composer { --sky-halo: 0 0 6px var(--void), 0 0 14px var(--void), 0 0 2px var(--void); }
|
|
124
|
+
.stats, .gravity, .obj, .alt { text-shadow: var(--sky-halo); }
|
|
125
|
+
/* the composer's hint floats lower than the strip's veil, so it carries its own */
|
|
126
|
+
.gravity:not(:empty) { padding: 2px 12px; border-radius: var(--ds-radius-tile); background: color-mix(in srgb, var(--void) 78%, transparent); }
|
|
112
127
|
.brand { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
|
|
113
128
|
.brand h1 { margin: 0; font-size: 26px; font-weight: 300; letter-spacing: -.035em; line-height: 1.05; white-space: nowrap; }
|
|
114
129
|
.brand h1 em { font-style: normal; color: var(--ink-dim); }
|
|
@@ -262,7 +277,9 @@ html[data-fullscreen="on"] .sky-stage .top { padding-right: 184px; }
|
|
|
262
277
|
.mk.settled { width: 12px; height: 12px; background: transparent; border: 1px solid color-mix(in srgb, var(--ink) 50%, transparent); box-shadow: inset 0 0 0 3px var(--void), inset 0 0 0 4px color-mix(in srgb, var(--ink) 35%, transparent), 0 0 8px color-mix(in srgb, var(--ink) 25%, transparent); }
|
|
263
278
|
.mk.collapsed { width: 9px; height: 9px; background: var(--void); border: 1px solid color-mix(in srgb, var(--ink) 30%, transparent); box-shadow: 0 0 0 2px var(--void), 0 0 0 3px color-mix(in srgb, var(--ink) 8%, transparent); }
|
|
264
279
|
.mk.unseen { width: 12px; height: 12px; background: transparent; border: 1px dashed color-mix(in srgb, var(--ink) 20%, transparent); }
|
|
265
|
-
|
|
280
|
+
/* `task` is also the hall's task-card class (style.css): its card padding blew the
|
|
281
|
+
legend's 4px dot up into a 48px blob over its own word, so it is zeroed here. */
|
|
282
|
+
.mk.task { width: 4px; height: 4px; padding: 0; border: 0; background: color-mix(in srgb, var(--ink) 60%, var(--void)); }
|
|
266
283
|
.mk.task.live { background: var(--accent); box-shadow: 0 0 6px var(--accent); }
|
|
267
284
|
.mk.born { width: 10px; height: 10px; background: transparent; border: 1px solid var(--accent); box-shadow: inset 0 0 0 2px var(--void), inset 0 0 0 5px var(--accent); }
|
|
268
285
|
.mk.route { width: 14px; height: 2px; border-radius: 1px; background: var(--chart); box-shadow: 0 0 6px var(--chart); }
|
|
@@ -34,7 +34,8 @@ Where it differs from the mockup, and why:
|
|
|
34
34
|
- **The link, the credits figure and the focus ring are #96410f by day**, not the mockup's #a3481a link and #fb9569 accent: #a3481a on Resume's tint over the wall is 4.3:1, under AA, and the accent is 1.8:1 on the wall.
|
|
35
35
|
- **The quiet inks are #5a595f (faint) and #66656b (dim, hint)** where the mockup has the one #66656b: the studio has three quiet tiers, and each holds AA on the glass and the bare wall (the hint is the lowest, 4.7:1).
|
|
36
36
|
- **The sun, clouds and sun patch are placed for the studio's 560px window** (the mockup's is 520px): the sun at 394/42 and 76px, the clouds about 8% larger, the patch 20px right to sit under the studio's lamp.
|
|
37
|
-
- **The
|
|
37
|
+
- **The day room is a light-theme room (task 1005137).** Under the hall's dark theme a cream room sat beside the black rail and bar, so every day rule is scoped `html:not([data-theme="dark"])`: in dark the room is the night room at any hour, and the clock only changes the greeting. The room's mono eyebrow ("{{worldName}} · studio") went in the same task, since DESIGN.md bans a label above the h1, and the Approval queue took the shared dialog's corner and hairline.
|
|
38
|
+
- **The panels follow the sky**: by day the two glass panels and the Approval queue wear white glass (.75) over a scrim of the wall's own tint (.9) on a phone and in the light theme. The queue's rendered page stays on its paper frame. The mockup draws no panel.
|
|
38
39
|
- **"Good morning." while being typed over shows the page's greeting line**, "Good evening.", the one line a tweak can rewrite. Rewritten, it is the greeting at every hour.
|
|
39
40
|
- **On a phone the sun patch lies on the phone's floor** (bottom right, 300 by 70); the mockup hides it under 900px, but the studio's phone keeps its desk.
|
|
40
41
|
- **The stars do not twinkle and the lamp does not breathe at night**: the night room stays as it shipped. This task adds only the day room's drift.
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
sheets; this lays them out in sections and stops the live toast and the
|
|
4
4
|
dialog from floating, so each sits at rest where it can be looked at. */
|
|
5
5
|
body[data-page="components"] { margin: 0; color: var(--ink); background: var(--bg); font-family: var(--font-body); }
|
|
6
|
+
/* The hall layering (task 1005137): style.css paints the plate and its veil as
|
|
7
|
+
fixed body::before / body::after at z-index 0, and a hall page lifts its
|
|
8
|
+
content above them (.app-body is position: relative; z-index: 1). The
|
|
9
|
+
specimen has no .app-body, so in dark the veil (--plate-veil, black at .82-.93)
|
|
10
|
+
lay OVER every section and the page read as near-black. Its two blocks take
|
|
11
|
+
the same lift. Pinned in tests/hall_topbar_320.mjs. */
|
|
12
|
+
.spec-head, .spec-main { position: relative; z-index: 1; }
|
|
6
13
|
.spec-head, .spec-main { box-sizing: border-box; max-width: 980px; margin: 0 auto; padding: 0 var(--ds-space-md); }
|
|
7
14
|
.spec-head { padding-top: var(--ds-space-sm); padding-bottom: var(--ds-space-hair); }
|
|
8
15
|
.spec-kicker { display: inline-block; margin: 0 var(--ds-space-xs) 0 0; vertical-align: middle; font-family: var(--label-face); font-size: var(--label-size); font-weight: 400; letter-spacing: var(--label-space); text-transform: var(--label-case); color: var(--ink-faint); }
|
|
@@ -887,11 +887,12 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
887
887
|
<!--apex-only:start-->
|
|
888
888
|
<!-- The maker's band (task 1005091): the company is the pack's (identity.company), never
|
|
889
889
|
a name written into the portable landing, and the band drops whole when the pack
|
|
890
|
-
names none.
|
|
890
|
+
names none. Its mark is the pack's too (identity.company.mark, task 1005139): no
|
|
891
|
+
mark in the pack, no image, so another company never wears the hub's medallion. -->
|
|
891
892
|
<!--brand-if:companyName--><section class="band">
|
|
892
893
|
<div class="wrap verro">
|
|
893
|
-
|
|
894
|
-
alt="The {{companyName}} mark"
|
|
894
|
+
<!--brand-if:companyMark--><img src="{{companyMark}}" width="384" height="384" loading="lazy" decoding="async"
|
|
895
|
+
alt="The {{companyName}} mark"><!--/brand-if:companyMark-->
|
|
895
896
|
<div>
|
|
896
897
|
<p class="wordmark">{{companyName}}</p>
|
|
897
898
|
<p class="body">Built by {{companyName}}, {{productName}} is our way of making AI co-building usable by entire project groups, whether that's a 5,000-person company, a 100-person department, a college research project, or a solo dev.</p>
|
|
@@ -17,7 +17,7 @@ The mirror is a port of these files with three changes:
|
|
|
17
17
|
|
|
18
18
|
1. **Availability banner (always truthful):** a top strip reads the external prober's `status.json` from the off-box `status-data` branch (`raw.githubusercontent.com`) — so it shows the *real* up/down even when the droplet (and its API) are down.
|
|
19
19
|
2. **Dev panels (degrade gracefully):** `status.js`'s `API` base points at the absolute droplet origin (`https://example.com`); `/api/bongos/public/*` now sends `<redacted>: *` (see `src/bongos/routes/public.js`) so the panels load cross-origin. Each fetch already `.catch(() => null)`, so when the droplet is down the panels show their empty state instead of breaking the page.
|
|
20
|
-
3. **Assets are
|
|
20
|
+
3. **Assets are root-absolute** (`/style.css`, `/status.js`, `/modules/ui-design/public/tokens.css`, `/modules/ui-design/public/brand/…`, `/modules/hall-ui/public/controls.css`): `scripts/status-mirror-sync.js` publishes the shared files at the same paths, so the links resolve on the custom domain (served from the host root). They do not resolve under a GitHub Pages project sub-path (`<owner>.github.io/<repo>/`), so the mirror must be served from a domain root.
|
|
21
21
|
|
|
22
22
|
## Probing & alerting
|
|
23
23
|
|
|
@@ -248,7 +248,9 @@ Adapters are discovered by the design subsystem at runtime. No static registry
|
|
|
248
248
|
|
|
249
249
|
## 5. CSS variable injection at runtime
|
|
250
250
|
|
|
251
|
-
The
|
|
251
|
+
> **What is live (task 1005139).** No served page reads a `--dt-*` variable today. The live surfaces read the fifteen pack tokens that `themeMarkup` (`src/theme-markup.js`) splices into every page, and the `--ds-*` scales of the shared token sheet `modules/ui-design/public/tokens.css`. The `--dt-*` names below are emitted only by the adapters' token-preview bundle (`adapters/claude-design/index.js`, `adapters/figma/index.js`), for a surface authored through an adapter under `src/ui/` — and none exists yet (`config/design-sources.json` is empty). [Task 1005092](https://cloudbongos.com/builders#/task/1005092) will publish the generated `/brand` reference as a Claude Design System; until a surface is authored through an adapter, read this section as the contract that output must follow, not as a description of the served pages.
|
|
252
|
+
|
|
253
|
+
The design system renders as CSS custom properties injected into the document at boot, consistent with the existing `themeMarkup` pattern in `src/theme-markup.js`. The naming convention:
|
|
252
254
|
|
|
253
255
|
```css
|
|
254
256
|
--dt-color-bg: /* tokens.colors.bg */
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.62",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.24.
|
|
9
|
+
"version": "1.24.62",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.24.
|
|
3
|
+
"version": "1.24.62",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/release-notes.json
CHANGED
|
@@ -11335,5 +11335,17 @@
|
|
|
11335
11335
|
"id": "1005134",
|
|
11336
11336
|
"text": "The automatic check that stops pages from typing in their own colours and sizes now also catches colours given a custom name and styles written directly inside a page, so those shortcuts can no longer slip new colours past it."
|
|
11337
11337
|
}
|
|
11338
|
+
],
|
|
11339
|
+
"1.24.61": [
|
|
11340
|
+
{
|
|
11341
|
+
"id": "1005139",
|
|
11342
|
+
"text": "The design docs now describe what is actually live, and the landing page shows a company's logo only when that company's settings provide one, so other projects no longer show the Verro Labs medallion."
|
|
11343
|
+
}
|
|
11344
|
+
],
|
|
11345
|
+
"1.24.62": [
|
|
11346
|
+
{
|
|
11347
|
+
"id": "1005137",
|
|
11348
|
+
"text": "On a phone the hall's top bar now fits without cutting off buttons, the Studio stays a night room in dark mode, the sky page's dots no longer cover its labels, and the components reference page is readable in dark mode."
|
|
11349
|
+
}
|
|
11338
11350
|
]
|
|
11339
11351
|
}
|
|
@@ -146,8 +146,9 @@ function injectBrandingGlobal(html, brand = clientBranding(), mods = clientModul
|
|
|
146
146
|
// served page can carry a correct cross-host href or canonical/og: URL: a client
|
|
147
147
|
// fill cannot reach them, and a baked one would make every instance advertise
|
|
148
148
|
// whichever instance happened to author the file (task 1002748, direction C5/C6).
|
|
149
|
-
// {{companyName}} {{companySite}} {{companyTagline}}
|
|
150
|
-
//
|
|
149
|
+
// {{companyName}} {{companySite}} {{companyTagline}} {{companyMark}} (the company's mark
|
|
150
|
+
// image URL, task 1005139) are optional, so a page wraps each clause in a brand-if region
|
|
151
|
+
// that drops whole when its field is empty (task 1002853).
|
|
151
152
|
// Pure + exported for the test; both origins are injectable so it drives with zero I/O.
|
|
152
153
|
function applyBrandTokens(html, brand = clientBranding(), origin = safePublicOrigin(), hallOrigin = safeBuildersOrigin(), sourceRepo = safeSourceRepoUrl(), instanceBase = safeInstanceBase(), hubOrigin = safeHubOrigin()) {
|
|
153
154
|
const world = (brand.identity && brand.identity.worldName) || '';
|
|
@@ -157,7 +158,7 @@ function applyBrandTokens(html, brand = clientBranding(), origin = safePublicOri
|
|
|
157
158
|
const Cur = cur ? cur.charAt(0).toUpperCase() + cur.slice(1) : cur;
|
|
158
159
|
const hall = hallOrigin || origin || '';
|
|
159
160
|
const company = (brand.identity && brand.identity.company) || {};
|
|
160
|
-
const conditioned = applyBrandConditionals(html == null ? '' : html, { companyName: company.name, companySite: company.site, companyTagline: company.tagline });
|
|
161
|
+
const conditioned = applyBrandConditionals(html == null ? '' : html, { companyName: company.name, companySite: company.site, companyTagline: company.tagline, companyMark: company.mark });
|
|
161
162
|
return dropEmptyHrefLinks(conditioned, { publicOrigin: origin || '', buildersOrigin: hall, sourceRepoUrl: sourceRepo || '' })
|
|
162
163
|
.split('{{worldName}}').join(esc(world))
|
|
163
164
|
.split('{{productName}}').join(esc(product))
|
|
@@ -167,6 +168,7 @@ function applyBrandTokens(html, brand = clientBranding(), origin = safePublicOri
|
|
|
167
168
|
.split('{{companyName}}').join(esc(company.name))
|
|
168
169
|
.split('{{companySite}}').join(esc(company.site))
|
|
169
170
|
.split('{{companyTagline}}').join(esc(company.tagline))
|
|
171
|
+
.split('{{companyMark}}').join(esc(company.mark || ''))
|
|
170
172
|
.split('{{publicOrigin}}').join(esc(origin || ''))
|
|
171
173
|
// a pack that names no hall of its own resolves to its OWN apex — never to
|
|
172
174
|
// whichever instance authored the file
|
package/src/branding.js
CHANGED
|
@@ -254,6 +254,8 @@ function clientBranding(b = branding()) {
|
|
|
254
254
|
name: b.identity?.company?.name || '',
|
|
255
255
|
site: b.identity?.company?.site || '',
|
|
256
256
|
tagline: b.identity?.company?.tagline || '',
|
|
257
|
+
// the company's mark, an image URL; '' shows none (task 1005139)
|
|
258
|
+
mark: b.identity?.company?.mark || '',
|
|
257
259
|
},
|
|
258
260
|
},
|
|
259
261
|
currency: {
|
package/src/module-api.js
CHANGED
|
@@ -81,7 +81,7 @@ const { sleep } = require('./sleep');
|
|
|
81
81
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
82
82
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
83
83
|
|
|
84
|
-
const CORE_VERSION = '1.24.
|
|
84
|
+
const CORE_VERSION = '1.24.62'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
85
85
|
|
|
86
86
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
87
87
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|
package/tests/brand_surfaces.mjs
CHANGED
|
@@ -214,3 +214,17 @@ test('the landing names its maker only from the pack, and drops the band when th
|
|
|
214
214
|
const none = stamp('');
|
|
215
215
|
assert.doesNotMatch(none, /class="wrap verro"|held by|Built by ,/, 'the band and the line drop whole');
|
|
216
216
|
});
|
|
217
|
+
|
|
218
|
+
// task 1005139: the band's image is the pack's (identity.company.mark). A pack that names a
|
|
219
|
+
// company but no mark shows the name without an image — never the hub's medallion.
|
|
220
|
+
test('the maker band shows the company mark only from the pack', () => {
|
|
221
|
+
const src = fs.readFileSync(path.join(ROOT, 'modules', 'public-landing', 'public', 'index.html'), 'utf8');
|
|
222
|
+
assert.doesNotMatch(src.replace(/<!--(?!\/?brand-if)[\s\S]*?-->/g, '').replace(/\/\*[\s\S]*?\*\//g, ''), /medallion/, 'no maker\'s image written into the portable page');
|
|
223
|
+
const stamp = (company) => applyBrandTokens(src, { identity: { productName: 'Acme', worldName: 'Acme', company: { site: '', tagline: '', ...company } } }, '', '', '', '', '');
|
|
224
|
+
const bare = stamp({ name: 'Rocket Labs', mark: '' });
|
|
225
|
+
assert.ok(bare.includes('<p class="wordmark">Rocket Labs</p>'), 'the band still shows the name');
|
|
226
|
+
assert.doesNotMatch(bare, /<img[^>]*alt="The Rocket Labs mark"/, 'no mark in the pack, no image');
|
|
227
|
+
const marked = stamp({ name: 'Rocket Labs', mark: '/assets/rocket.png' });
|
|
228
|
+
assert.match(marked, /<img src="\/assets\/rocket\.png"[^>]*alt="The Rocket Labs mark">/, 'the pack\'s mark is the image');
|
|
229
|
+
assert.equal(clientBranding({ identity: { company: { name: 'R', mark: '/m.png' } } }).identity.company.mark, '/m.png');
|
|
230
|
+
});
|
|
@@ -639,7 +639,14 @@ try {
|
|
|
639
639
|
assert.match(CSS, /\.st-light \{[^}]*background: var\(--st-pane\)/, 'the panes are lit by night and unlit by day through one token');
|
|
640
640
|
assert.match(CSS, /body\[data-sky="day"\] \.st-cloud \{ opacity: \.9; \}/);
|
|
641
641
|
assert.match(CSS, /body\[data-sky="day"\] \.st-sunpatch \{ opacity: \.7; \}/);
|
|
642
|
-
assert.match(CSS, /body\[data-sky="day"\] \.st-star,\s*body\[data-sky="day"\] \.st-glow \{ opacity: 0; \}/, 'no stars, and the lamp is off');
|
|
642
|
+
assert.match(CSS, /body\[data-sky="day"\] \.st-star,\s*html:not\(\[data-theme="dark"\]\) body\[data-sky="day"\] \.st-glow \{ opacity: 0; \}/, 'no stars, and the lamp is off');
|
|
643
|
+
// task 1005137: the day room is a light-theme room. Under the hall's dark theme a cream room
|
|
644
|
+
// sat beside the black rail, so EVERY day rule is scoped off dark and dark keeps the night room.
|
|
645
|
+
const daySelectors = CSS.replace(/\/\*[\s\S]*?\*\//g, '').split(/[{}]/).filter((_, i) => i % 2 === 0)
|
|
646
|
+
.flatMap((sel) => sel.split(',')).map((x) => x.trim()).filter((x) => x.includes('[data-sky="day"]'));
|
|
647
|
+
assert.ok(daySelectors.length >= 10, `the day rules are found (${daySelectors.length})`);
|
|
648
|
+
for (const sel of daySelectors) assert.ok(sel.startsWith('html:not([data-theme="dark"]) '), `${sel} is scoped off the dark theme`);
|
|
649
|
+
assert.ok(!/st-eyebrow/.test(CSS + HTML), 'no mono eyebrow above the greeting (DESIGN.md bans a label over the h1)');
|
|
643
650
|
assert.match(HTML, /class="st-cloud st-cloud--1"/);
|
|
644
651
|
assert.match(HTML, /class="st-sunpatch"/);
|
|
645
652
|
const anims = [...CSS.matchAll(/animation:\s*[^;]+;|transition:\s*[^;]+;/g)].map((m) => m.index);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// tests/hall_topbar_320.mjs — the hall's top bar fits a 320px phone, and the components
|
|
2
|
+
// specimen sits above the plate veil (task 1005137, goal 1000138).
|
|
3
|
+
//
|
|
4
|
+
// WHY. A visual check of the design system found every hall page 340px wide at 320: the menu, the
|
|
5
|
+
// Update pill, the search, the mode switch and the three toggles did not fit, so the theme and
|
|
6
|
+
// full-screen icons were cut off at the edge. And modules/hall-ui/specimens/components.html read as
|
|
7
|
+
// near-black in dark mode, because style.css's body::after veil (--plate-veil) covered the page: a
|
|
8
|
+
// hall page lifts its content over the plate (.app-body, z-index 1) and the specimen did not.
|
|
9
|
+
// Both are layout, so both are measured in a real browser on the REAL markup and the REAL sheets:
|
|
10
|
+
// the shell's own top bar on the Tasks page with the widest set of buttons a page can carry (the
|
|
11
|
+
// Update pill, the search, the full-screen switch), and the specimen as the kit renders it.
|
|
12
|
+
// Skips when the machine has no system Edge or Chrome, as welcome_boot_browser does.
|
|
13
|
+
import assert from 'node:assert/strict';
|
|
14
|
+
import { test, after } from 'node:test';
|
|
15
|
+
import { createRequire } from 'node:module';
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
22
|
+
const HALL = path.join(ROOT, 'modules', 'hall-ui', 'public');
|
|
23
|
+
const SPEC = path.join(ROOT, 'modules', 'hall-ui', 'specimens');
|
|
24
|
+
const lib = require(path.join(ROOT, 'modules', 'ui-design', 'kit', 'lib.js'));
|
|
25
|
+
|
|
26
|
+
let browser = null;
|
|
27
|
+
try { browser = await lib.launchBrowser(); } catch (e) { if (!(e instanceof lib.NoBrowserError)) throw e; }
|
|
28
|
+
after(() => browser && browser.close());
|
|
29
|
+
const skip = !browser && 'no system Edge or Chrome';
|
|
30
|
+
|
|
31
|
+
const ORIGIN = 'http://hall.test';
|
|
32
|
+
const TYPES = { '.js': 'text/javascript', '.css': 'text/css', '.html': 'text/html' };
|
|
33
|
+
|
|
34
|
+
// The Tasks page's real markup, its scripts swapped for the shell's own, on a page that opts into
|
|
35
|
+
// the full-screen switch (Studio and Thinking do), so the bar carries every button it can.
|
|
36
|
+
const WORK = fs.readFileSync(path.join(HALL, 'work.html'), 'utf8')
|
|
37
|
+
.replace(/<script\b[^>]*>[\s\S]*?<\/script>/g, '')
|
|
38
|
+
.replace(/\{\{[^}]*\}\}/g, '')
|
|
39
|
+
.replace('<body data-page="work"', '<body data-page="work" data-fullscreen-switch');
|
|
40
|
+
const STUB = '<script>window.__BRANDING__ = { identity: { worldName: "Harbor" } }; window.BongosClient = { createClient: () => ({ request: async () => ({ ok: false, status: 404, data: null }) }) };</script>';
|
|
41
|
+
const SCRIPTS = ['dom-utils.js', 'shell-trail.js', 'craft-mode.js', 'shell.js'].map((f) => `<script src="/builders/${f}"></script>`).join('');
|
|
42
|
+
const PAGE = WORK.replace('</body>', STUB + SCRIPTS + '</body>');
|
|
43
|
+
|
|
44
|
+
async function context(width, scheme) {
|
|
45
|
+
const ctx = await browser.newContext({ viewport: { width, height: 700 }, colorScheme: scheme });
|
|
46
|
+
await ctx.route(ORIGIN + '/**', (route) => {
|
|
47
|
+
const p = new URL(route.request().url()).pathname;
|
|
48
|
+
if (p === '/work') return route.fulfill({ contentType: 'text/html', body: PAGE });
|
|
49
|
+
let f = null;
|
|
50
|
+
if (p.startsWith('/builders/')) f = path.join(HALL, p.slice('/builders/'.length));
|
|
51
|
+
else if (p.startsWith('/modules/hall-ui/public/')) f = path.join(HALL, p.slice('/modules/hall-ui/public/'.length));
|
|
52
|
+
else if (p.startsWith('/modules/ui-design/public/')) f = path.join(ROOT, 'modules', 'ui-design', 'public', p.slice('/modules/ui-design/public/'.length));
|
|
53
|
+
else if (p.startsWith('/specimens/')) f = path.join(SPEC, p.slice('/specimens/'.length));
|
|
54
|
+
if (f && TYPES[path.extname(f)] && fs.existsSync(f)) return route.fulfill({ contentType: TYPES[path.extname(f)], body: fs.readFileSync(f) });
|
|
55
|
+
return route.fulfill({ status: 404, body: '' });
|
|
56
|
+
});
|
|
57
|
+
return ctx;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// The bar as update-pill.js and palette.js leave it: the Update pill first in the side, the search
|
|
61
|
+
// shown. Then every button's box, and how wide the document is.
|
|
62
|
+
async function measureBar(width, scheme) {
|
|
63
|
+
const ctx = await context(width, scheme);
|
|
64
|
+
const page = await ctx.newPage();
|
|
65
|
+
await page.goto(ORIGIN + '/work');
|
|
66
|
+
await page.waitForSelector('#app-topbar .topbar-side #theme-toggle');
|
|
67
|
+
return page.evaluate(() => {
|
|
68
|
+
const side = document.querySelector('#app-topbar .topbar-side');
|
|
69
|
+
if (!document.getElementById('upd-pill')) {
|
|
70
|
+
const b = document.createElement('button');
|
|
71
|
+
b.type = 'button'; b.id = 'upd-pill'; b.className = 'upd-pill'; b.setAttribute('data-face', 'ready');
|
|
72
|
+
b.innerHTML = '<span class="upd-pill__dot" aria-hidden="true"></span><span class="upd-pill__label">Update</span>';
|
|
73
|
+
side.insertBefore(b, side.firstChild);
|
|
74
|
+
}
|
|
75
|
+
const jump = document.getElementById('topbar-jump'); if (jump) jump.hidden = false;
|
|
76
|
+
const fs = document.getElementById('fullscreen-toggle'); if (fs) fs.hidden = false;
|
|
77
|
+
const buttons = [...document.querySelectorAll('#app-topbar button')]
|
|
78
|
+
.filter((b) => getComputedStyle(b).display !== 'none' && b.getClientRects().length)
|
|
79
|
+
.map((b) => { const r = b.getBoundingClientRect(); return { id: b.id || b.className, left: r.left, right: r.right, w: r.width, h: r.height }; });
|
|
80
|
+
const bar = document.getElementById('app-topbar').getBoundingClientRect();
|
|
81
|
+
return { scrollWidth: document.documentElement.scrollWidth, bar: { left: bar.left, right: bar.right }, buttons };
|
|
82
|
+
}).finally(() => ctx.close());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const scheme of ['light', 'dark']) {
|
|
86
|
+
test(`at 320px the top bar fits, every button inside it and at least 24px (${scheme})`, { skip }, async () => {
|
|
87
|
+
const m = await measureBar(320, scheme);
|
|
88
|
+
const ids = m.buttons.map((b) => b.id);
|
|
89
|
+
for (const want of ['upd-pill', 'topbar-jump', 'zoom-toggle', 'theme-toggle', 'fullscreen-toggle']) {
|
|
90
|
+
assert.ok(ids.includes(want), `${want} is on the bar (got ${ids.join(', ')})`);
|
|
91
|
+
}
|
|
92
|
+
assert.ok(m.scrollWidth <= 320, `the page is ${m.scrollWidth}px wide at 320 (the top bar spills)`);
|
|
93
|
+
for (const b of m.buttons) {
|
|
94
|
+
assert.ok(b.left >= 0 && b.right <= 320, `${b.id} sits at ${Math.round(b.left)}..${Math.round(b.right)}, outside 0..320`);
|
|
95
|
+
assert.ok(b.w >= 24 && b.h >= 24, `${b.id} is ${Math.round(b.w)}x${Math.round(b.h)}, under the 24px floor`);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
test('the top bar search and the mode switch are one shape: a pill with the same hairline', { skip }, async () => {
|
|
101
|
+
const ctx = await context(1440, 'dark');
|
|
102
|
+
const page = await ctx.newPage();
|
|
103
|
+
await page.goto(ORIGIN + '/work');
|
|
104
|
+
await page.waitForSelector('#app-topbar #topbar-jump', { state: 'attached' });
|
|
105
|
+
const s = await page.evaluate(() => {
|
|
106
|
+
const css = (el) => getComputedStyle(el);
|
|
107
|
+
const jump = document.getElementById('topbar-jump');
|
|
108
|
+
jump.hidden = false; // palette.js shows it; the shape is the sheet's either way
|
|
109
|
+
const sw = document.getElementById('craft-switch');
|
|
110
|
+
const root = css(document.documentElement);
|
|
111
|
+
return {
|
|
112
|
+
jumpRadius: css(jump).borderTopLeftRadius,
|
|
113
|
+
switchRadius: css(sw).borderTopLeftRadius,
|
|
114
|
+
jumpEdge: css(jump).borderTopColor,
|
|
115
|
+
switchShadow: css(sw).boxShadow,
|
|
116
|
+
pill: root.getPropertyValue('--ds-radius-pill').trim(),
|
|
117
|
+
};
|
|
118
|
+
}).finally(() => ctx.close());
|
|
119
|
+
assert.equal(s.jumpRadius, s.pill, 'the search is a full pill');
|
|
120
|
+
assert.equal(s.switchRadius, s.pill, 'the mode switch is a full pill');
|
|
121
|
+
assert.ok(s.switchShadow.startsWith(s.jumpEdge), `the two share one hairline colour (${s.jumpEdge} vs ${s.switchShadow})`);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('the components specimen sits above the plate veil in dark (the hall layering)', { skip }, async () => {
|
|
125
|
+
const ctx = await context(1440, 'dark');
|
|
126
|
+
const page = await ctx.newPage();
|
|
127
|
+
await page.goto(ORIGIN + '/specimens/components.html');
|
|
128
|
+
await page.waitForSelector('.spec-main');
|
|
129
|
+
const r = await page.evaluate(() => {
|
|
130
|
+
const veil = getComputedStyle(document.body, '::after');
|
|
131
|
+
const blocks = [...document.body.children].filter((el) => !/^(SCRIPT|TEMPLATE)$/.test(el.tagName) && getComputedStyle(el).display !== 'none')
|
|
132
|
+
.map((el) => { const cs = getComputedStyle(el); return { el: el.tagName.toLowerCase() + '.' + el.className, position: cs.position, z: cs.zIndex }; });
|
|
133
|
+
return { veilImage: veil.backgroundImage, veilZ: veil.zIndex, veilPosition: veil.position, blocks };
|
|
134
|
+
}).finally(() => ctx.close());
|
|
135
|
+
// The veil is really there in dark — otherwise this test proves nothing.
|
|
136
|
+
assert.notEqual(r.veilImage, 'none', 'dark paints the plate veil');
|
|
137
|
+
assert.equal(r.veilPosition, 'fixed');
|
|
138
|
+
const veilZ = Number(r.veilZ) || 0;
|
|
139
|
+
assert.ok(r.blocks.length >= 2, 'the specimen has its head and its main');
|
|
140
|
+
for (const b of r.blocks) {
|
|
141
|
+
if (b.position === 'fixed' || b.position === 'absolute') continue;
|
|
142
|
+
assert.notEqual(b.position, 'static', `${b.el} is static, so the fixed veil paints over it`);
|
|
143
|
+
assert.ok(Number(b.z) > veilZ, `${b.el} has z-index ${b.z}, not above the veil's ${veilZ}`);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
@@ -55,6 +55,6 @@ test('the off-box mirror stamps the footer exactly as the served page does (KEEP
|
|
|
55
55
|
|
|
56
56
|
test('clientBranding exposes the company site and tagline the footer stamps from', () => {
|
|
57
57
|
const c = clientBranding({ identity: { company: { name: 'Acme Corp', site: 'https://acme-corp.example', tagline: 'a rocket works' } } });
|
|
58
|
-
assert.deepEqual(c.identity.company, { name: 'Acme Corp', site: 'https://acme-corp.example', tagline: 'a rocket works' });
|
|
59
|
-
assert.deepEqual(clientBranding({}).identity.company, { name: '', site: '', tagline: '' });
|
|
58
|
+
assert.deepEqual(c.identity.company, { name: 'Acme Corp', site: 'https://acme-corp.example', tagline: 'a rocket works', mark: '' });
|
|
59
|
+
assert.deepEqual(clientBranding({}).identity.company, { name: '', site: '', tagline: '', mark: '' });
|
|
60
60
|
});
|