@bongos/core 1.21.164 → 1.21.166
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 +34 -29
- package/docs/copy-inventory.md +91 -91
- package/docs/copy-registry.json +126 -116
- package/docs/module-api-changelog.md +4 -0
- package/docs/page-readings.json +304 -305
- package/modules/builder-settings/builder-needs.js +5 -0
- package/modules/hall-ui/public/builders.js +3 -17
- package/modules/hall-ui/public/craft-mode.js +32 -158
- package/modules/hall-ui/public/craft-review.css +7 -120
- package/modules/hall-ui/public/hall-render.js +8 -24
- package/modules/hall-ui/public/index.html +3 -4
- package/modules/hall-ui/public/page-notes.css +64 -1
- package/modules/hall-ui/public/page-notes.js +96 -11
- package/modules/hall-ui/public/settings.js +4 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +16 -0
- package/src/bongos/routes/me.js +3 -0
- package/src/module-api.js +1 -1
- package/tests/craft_review_mode.mjs +21 -104
- package/tests/hall_board_world.mjs +18 -39
- package/tests/home_attention_cards.mjs +6 -2
- package/tests/page_notes_panel.mjs +161 -27
- package/tests/settings_art_key_absent.mjs +137 -0
|
@@ -38,7 +38,12 @@ const SETTINGS_PATH = '/settings';
|
|
|
38
38
|
// ownKeySet → satisfied (their own key runs the pipeline)
|
|
39
39
|
// newcomer (xenos) → covered ("on us while you get started")
|
|
40
40
|
// thetes+ and no own key → action_needed (coverage has ended)
|
|
41
|
+
//
|
|
42
|
+
// ctx.artPipeline === false — no art pipeline on this instance, so there are no
|
|
43
|
+
// key routes and nothing to add: no need at all
|
|
44
|
+
// (task 1004786). Unset keeps the states above.
|
|
41
45
|
function artKeyNeed(ctx) {
|
|
46
|
+
if (ctx.artPipeline === false) return null;
|
|
42
47
|
const rank = ctx.rank || null;
|
|
43
48
|
const isNewcomer = rank === 'xenos';
|
|
44
49
|
const base = {
|
|
@@ -179,11 +179,9 @@
|
|
|
179
179
|
// (same pattern as lastNeedsState). These are un-landed confirmed tasks that
|
|
180
180
|
// GATE new claims and whose reward is pending until they land on ship.
|
|
181
181
|
let lastRebaseState = null;
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
// Null until each lands; the sentence says only what it knows.
|
|
182
|
+
// The public version progress, which the Home version tab draws (task 1004774).
|
|
183
|
+
// Null until it lands.
|
|
185
184
|
let lastProgressState = null;
|
|
186
|
-
let lastClaimableCount = null;
|
|
187
185
|
|
|
188
186
|
// Cached /me payload from the most recent loadAll() call. Used by the
|
|
189
187
|
// welcome-chip reopen path (#408) so we can re-render the welcome panel
|
|
@@ -949,17 +947,6 @@
|
|
|
949
947
|
// waiting instead of leaving two rows to imply there are only two.
|
|
950
948
|
// task 1004774: five rows, level with the five Recently shipped rows beside it.
|
|
951
949
|
const resp = await getJSON(`${API}/tasks/claimable?limit=5`, { softAuth: true });
|
|
952
|
-
// task 1003464: the greeting's "N tasks are claimable right now" — rank-scoped,
|
|
953
|
-
// so it is THIS builder's number. It reads claimable_total rather than the length
|
|
954
|
-
// of a fully-downloaded feed: the same number, without the wire cost that this
|
|
955
|
-
// commit exists to remove. The .length fallback covers a server that predates
|
|
956
|
-
// the field (a deploy skew), so the greeting degrades to correct-but-costly
|
|
957
|
-
// rather than to blank.
|
|
958
|
-
const claimableTotal = Number(resp && resp.claimable_total);
|
|
959
|
-
lastClaimableCount = Number.isFinite(claimableTotal)
|
|
960
|
-
? claimableTotal
|
|
961
|
-
: (resp && Array.isArray(resp.claimable) ? resp.claimable.length : null);
|
|
962
|
-
renderGreeting();
|
|
963
950
|
// capped as well as asked for: a server that ignores ?limit must not grow the card.
|
|
964
951
|
const rows = resp && Array.isArray(resp.claimable) ? resp.claimable.slice(0, 5) : [];
|
|
965
952
|
if (!rows.length) { wrap.hidden = true; if (subhead) subhead.hidden = true; return; }
|
|
@@ -1177,7 +1164,7 @@
|
|
|
1177
1164
|
getJSON(`${API}/me/recent-ships?limit=all`),
|
|
1178
1165
|
getJSON(`${API}/versions`),
|
|
1179
1166
|
getJSON(`${API}/public/leaderboard`), // task 1004437: the list with each builder's roles
|
|
1180
|
-
getJSON(`${API}/public/progress`), //
|
|
1167
|
+
getJSON(`${API}/public/progress`), // the Home version tab (task 1004774)
|
|
1181
1168
|
]);
|
|
1182
1169
|
|
|
1183
1170
|
if (progress.status === 'fulfilled') {
|
|
@@ -1412,7 +1399,6 @@
|
|
|
1412
1399
|
const { acclaimEvidenceHtml, analyticsState, lbState, mountHallWidgets, renderCurveBody, renderGreeting, renderLanding, renderNav, renderOnboarding, renderProfile, renderRecentShips, renderWelcomeCore, shipState, shipsPager, welcomeDismissalKey, wireNextUpClaim } = window.OTBHallRender({
|
|
1413
1400
|
API, RANK_ORDER, api, brandWorld, curLabel, emptyRow, fmtDate, loadNextUp, mins, normalizeRank, pct, postJSON, rankLabel, rankWeight, signClasses, toast, usd,
|
|
1414
1401
|
get instanceRepo() { return instanceRepo; },
|
|
1415
|
-
get lastClaimableCount() { return lastClaimableCount; },
|
|
1416
1402
|
get lastCostState() { return lastCostState; },
|
|
1417
1403
|
get lastKarmaState() { return lastKarmaState; },
|
|
1418
1404
|
get lastMeState() { return lastMeState; },
|
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
governing: '<path d="M2 6 8 2.5 14 6M3.5 6.5v6M6.5 6.5v6M9.5 6.5v6M12.5 6.5v6M2 13.5h12"/>',
|
|
35
35
|
brush: '<path d="M13.6 2.4 7.4 8.6"/><path d="M7.4 8.6c-1.3-.7-3-.1-3.4 1.5-.3 1.3-.8 2.2-1.9 2.9 2.4.7 4.6.1 5.6-1.4.6-.9.5-2.1-.3-3Z"/>',
|
|
36
36
|
note: '<path d="M9.5 2.5h-6v11h9v-6"/><path d="M5.5 8h5M5.5 10.5h3.5"/><path d="M12.5 1.5v4M10.5 3.5h4"/>',
|
|
37
|
-
write: '<path d="M2.5 13.5h11"/><path d="M10.4 2.6l2.9 2.9-6.4 6.4H4V9z"/>',
|
|
38
37
|
modes: '<rect x="2.5" y="2.5" width="4.5" height="4.5" rx="1"/><rect x="9" y="2.5" width="4.5" height="4.5" rx="1"/><rect x="2.5" y="9" width="4.5" height="4.5" rx="1"/><rect x="9" y="9" width="4.5" height="4.5" rx="1"/>',
|
|
39
38
|
};
|
|
40
39
|
// The four crafts. `short` is the switch's label; `line` is the
|
|
@@ -342,8 +341,9 @@
|
|
|
342
341
|
artistry: [
|
|
343
342
|
// Named "Artist review" since task 1004499: the public site's button
|
|
344
343
|
// (public-landing assets/artist-review.js) opens this same panel.
|
|
345
|
-
// `
|
|
346
|
-
|
|
344
|
+
// `docked` (task 1004771): no top-bar button. The panel itself sits on
|
|
345
|
+
// the right edge behind a small tab, and carries the review-mode choice.
|
|
346
|
+
{ id: 'palette', icon: 'artistry', short: 'Review', label: 'Artist review: write feedback on this page', script: 'page-notes.js', api: 'OTBPageNotes', docked: true },
|
|
347
347
|
// Task 1004497: this page's words, edited where they stand as a proposed wording change.
|
|
348
348
|
{ id: 'paintbrush', icon: 'brush', short: 'Edit words', label: 'Edit this page\'s words', script: 'paintbrush.js', api: 'OTBPaintbrush' },
|
|
349
349
|
],
|
|
@@ -393,21 +393,24 @@
|
|
|
393
393
|
b.setAttribute('aria-label', t.label);
|
|
394
394
|
b.title = t.label;
|
|
395
395
|
b.innerHTML = icon(t.icon, 'craft-tool__icon') + '<span class="craft-tool__label">' + esc(t.short) + '</span>';
|
|
396
|
-
b.addEventListener('click', function (
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
396
|
+
b.addEventListener('click', function () { openTool(t, b); });
|
|
397
|
+
return b;
|
|
398
|
+
}
|
|
399
|
+
function toolNode(t) { return document.getElementById('craft-tool-' + t.id); }
|
|
400
|
+
// A docked tool has no button: in its mode the panel is docked at the edge,
|
|
401
|
+
// out of it the panel goes. Only a tool already loaded needs closing.
|
|
402
|
+
function applyDocked(mode) {
|
|
403
|
+
var want = toolsFor(mode);
|
|
404
|
+
for (var m in TOOLS) TOOLS[m].forEach(function (t) {
|
|
405
|
+
if (!t.docked) return;
|
|
406
|
+
if (want.indexOf(t) !== -1) loadTool(t).then(function (api) { if (api.dock) api.dock(); }, function () { /* the page still works */ });
|
|
407
|
+
else if (window[t.api] && window[t.api].close) window[t.api].close();
|
|
400
408
|
});
|
|
401
|
-
return t.menu ? reviewGroup(t, b) : b;
|
|
402
|
-
}
|
|
403
|
-
// The node a tool occupies in the bar: its button, or Review's whole group.
|
|
404
|
-
function toolNode(t) {
|
|
405
|
-
var b = document.getElementById('craft-tool-' + t.id);
|
|
406
|
-
return b && t.menu && b.parentNode && b.parentNode.id === REVIEW_GROUP ? b.parentNode : b;
|
|
407
409
|
}
|
|
408
410
|
function applyTools(mode) {
|
|
409
411
|
var sw = document.getElementById('craft-switch');
|
|
410
|
-
|
|
412
|
+
applyDocked(mode);
|
|
413
|
+
var own = toolsFor(mode).filter(function (t) { return !t.docked; });
|
|
411
414
|
var old = document.querySelectorAll ? document.querySelectorAll('[data-craft-tool]') : [];
|
|
412
415
|
for (var i = old.length - 1; i >= 0; i--) {
|
|
413
416
|
if (own.some(function (t) { return t.id === old[i].getAttribute('data-craft-tool'); })) continue;
|
|
@@ -429,26 +432,19 @@
|
|
|
429
432
|
var el = document.getElementById('craft-tool-' + t.id);
|
|
430
433
|
if (el && el.classList) el.classList.toggle('craft-tool--guest', !!review && TOOLS.artistry.indexOf(t) === -1);
|
|
431
434
|
});
|
|
432
|
-
var on = document.getElementById('craft-tool-palette');
|
|
433
|
-
if (on) {
|
|
434
|
-
on.classList.toggle('craft-tool--reviewing', !!review);
|
|
435
|
-
var word = on.querySelector && on.querySelector('.craft-tool__label');
|
|
436
|
-
if (word) word.textContent = review ? 'Reviewing' : 'Review';
|
|
437
|
-
}
|
|
438
435
|
}
|
|
439
436
|
|
|
440
437
|
// ---------- artist review mode (task 1004686) ----------
|
|
441
|
-
//
|
|
442
|
-
// (owner, 2026-10-09, in the owner's order and words). Picking one makes the hall
|
|
438
|
+
// The review panel's Review mode row (page-notes.js, task 1004771) offers the four
|
|
439
|
+
// experiences (owner, 2026-10-09, in the owner's order and words). Picking one makes the hall
|
|
443
440
|
// wear that mode on every page — its tabs, its look, its tools — the way a
|
|
444
441
|
// builder in it sees it, while the artist keeps Artistry's tools and small
|
|
445
442
|
// sky-style doodles are scribbled into the top bar's empty space (craft-review.js draws them,
|
|
446
|
-
// loaded only in review mode; craft-review.css styles it
|
|
443
|
+
// loaded only in review mode; craft-review.css styles it).
|
|
447
444
|
// The artist's own mode stays Artistry: the experience is this session's
|
|
448
445
|
// alone (sessionStorage), and choosing any mode from the switch or the
|
|
449
446
|
// picker ends it. Cosmetic like the mode: a tab the gate hides stays hidden.
|
|
450
447
|
var REVIEW_KEY = 'hall-craft-review';
|
|
451
|
-
var REVIEW_GROUP = 'craft-review';
|
|
452
448
|
var EXPERIENCES = [
|
|
453
449
|
{ id: 'ideating', label: 'Ideator experience' },
|
|
454
450
|
{ id: 'governing', label: 'Governing experience' },
|
|
@@ -478,138 +474,6 @@
|
|
|
478
474
|
apply('artistry');
|
|
479
475
|
}
|
|
480
476
|
|
|
481
|
-
// The menu. Hover opens it where a pointer can hover; the chevron (or, on a
|
|
482
|
-
// phone, Review itself) opens it by click, tap or keyboard. Built on every
|
|
483
|
-
// open, so the checked experience is always the live one.
|
|
484
|
-
var closeTimer = null;
|
|
485
|
-
// Hover is a mouse's alone: a touch also fires pointer events, and a laptop
|
|
486
|
-
// with a touch screen can report no hover at all, so ask the pointer itself.
|
|
487
|
-
function byMouse(e) { return !!e && e.pointerType === 'mouse'; }
|
|
488
|
-
function reviewMenuHtml() {
|
|
489
|
-
var items = '<button class="craft-review__item" type="button" role="menuitem" data-review-act="write">' +
|
|
490
|
-
icon('write', 'craft-review__icon') + '<span>Write feedback on this page</span></button>' +
|
|
491
|
-
'<p class="craft-review__head" role="presentation">Review mode</p>';
|
|
492
|
-
items += EXPERIENCES.map(function (x) {
|
|
493
|
-
var on = review === x.id;
|
|
494
|
-
return '<button class="craft-review__item" type="button" role="menuitemradio" aria-checked="' + (on ? 'true' : 'false') + '" data-review-pick="' + x.id + '">' +
|
|
495
|
-
icon(x.id, 'craft-review__icon') + '<span>' + esc(x.label) + '</span>' +
|
|
496
|
-
(on ? '<svg class="craft-review__check" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 8.5l3 3 6-7"/></svg>' : '') +
|
|
497
|
-
'</button>';
|
|
498
|
-
}).join('');
|
|
499
|
-
if (review) {
|
|
500
|
-
items += '<button class="craft-review__item craft-review__item--stop" type="button" role="menuitem" data-review-act="stop">' +
|
|
501
|
-
'<svg class="craft-review__icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" aria-hidden="true"><path d="M4.5 4.5l7 7M11.5 4.5l-7 7"/></svg><span>Stop reviewing</span></button>';
|
|
502
|
-
}
|
|
503
|
-
return items;
|
|
504
|
-
}
|
|
505
|
-
function menuEl() { return document.getElementById(REVIEW_GROUP + '-menu'); }
|
|
506
|
-
function menuItems() {
|
|
507
|
-
var m = menuEl();
|
|
508
|
-
return m && m.querySelectorAll ? Array.prototype.slice.call(m.querySelectorAll('[role^="menuitem"]')) : [];
|
|
509
|
-
}
|
|
510
|
-
function openReviewMenu(focusFirst) {
|
|
511
|
-
var m = menuEl(), more = document.getElementById(REVIEW_GROUP + '-more');
|
|
512
|
-
if (!m) return;
|
|
513
|
-
clearTimeout(closeTimer);
|
|
514
|
-
if (m.hasAttribute('hidden')) {
|
|
515
|
-
m.innerHTML = reviewMenuHtml();
|
|
516
|
-
m.removeAttribute('hidden');
|
|
517
|
-
if (more) more.setAttribute('aria-expanded', 'true');
|
|
518
|
-
document.addEventListener('click', onOutside, true);
|
|
519
|
-
}
|
|
520
|
-
if (focusFirst) {
|
|
521
|
-
var first = menuItems()[0];
|
|
522
|
-
if (first && first.focus) first.focus();
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
function closeReviewMenu(refocus) {
|
|
526
|
-
var m = menuEl(), more = document.getElementById(REVIEW_GROUP + '-more');
|
|
527
|
-
clearTimeout(closeTimer);
|
|
528
|
-
document.removeEventListener('click', onOutside, true);
|
|
529
|
-
if (!m || m.hasAttribute('hidden')) return;
|
|
530
|
-
m.setAttribute('hidden', '');
|
|
531
|
-
if (more) more.setAttribute('aria-expanded', 'false');
|
|
532
|
-
if (refocus) {
|
|
533
|
-
var back = narrow() ? document.getElementById('craft-tool-palette') : more;
|
|
534
|
-
if (back && back.focus) back.focus();
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
function toggleReviewMenu(focusFirst) {
|
|
538
|
-
var m = menuEl();
|
|
539
|
-
if (m && !m.hasAttribute('hidden')) closeReviewMenu(false); else openReviewMenu(focusFirst);
|
|
540
|
-
}
|
|
541
|
-
function onOutside(e) {
|
|
542
|
-
var g = document.getElementById(REVIEW_GROUP);
|
|
543
|
-
if (g && g.contains && !g.contains(e.target)) closeReviewMenu(false);
|
|
544
|
-
}
|
|
545
|
-
function onMenuClick(e) {
|
|
546
|
-
var btn = e.target.closest && e.target.closest('button');
|
|
547
|
-
if (!btn) return;
|
|
548
|
-
var pick = btn.getAttribute('data-review-pick');
|
|
549
|
-
var act = btn.getAttribute('data-review-act');
|
|
550
|
-
if (!pick && !act) return;
|
|
551
|
-
closeReviewMenu(false);
|
|
552
|
-
if (pick) startReview(pick);
|
|
553
|
-
else if (act === 'stop') stopReview();
|
|
554
|
-
else if (act === 'write') openTool(toolFor('palette'), document.getElementById('craft-tool-palette'));
|
|
555
|
-
}
|
|
556
|
-
// The menu keys: arrows move, Home and End jump, Escape closes back to the
|
|
557
|
-
// button that opened it, Tab closes and lets focus move on.
|
|
558
|
-
function onMenuKey(e) {
|
|
559
|
-
var items = menuItems();
|
|
560
|
-
var at = items.indexOf(document.activeElement);
|
|
561
|
-
var to = null;
|
|
562
|
-
if (e.key === 'ArrowDown') to = (at + 1) % items.length;
|
|
563
|
-
else if (e.key === 'ArrowUp') to = (at - 1 + items.length) % items.length;
|
|
564
|
-
else if (e.key === 'Home') to = 0;
|
|
565
|
-
else if (e.key === 'End') to = items.length - 1;
|
|
566
|
-
else if (e.key === 'Escape') { e.preventDefault(); closeReviewMenu(true); return; }
|
|
567
|
-
else if (e.key === 'Tab') { closeReviewMenu(false); return; }
|
|
568
|
-
if (to === null || !items[to]) return;
|
|
569
|
-
e.preventDefault();
|
|
570
|
-
items[to].focus();
|
|
571
|
-
}
|
|
572
|
-
function reviewGroup(t, b) {
|
|
573
|
-
var g = document.createElement('span');
|
|
574
|
-
g.id = REVIEW_GROUP;
|
|
575
|
-
g.className = 'craft-review';
|
|
576
|
-
var more = document.createElement('button');
|
|
577
|
-
more.type = 'button';
|
|
578
|
-
more.id = REVIEW_GROUP + '-more';
|
|
579
|
-
more.className = 'craft-review__more';
|
|
580
|
-
more.setAttribute('aria-haspopup', 'menu');
|
|
581
|
-
more.setAttribute('aria-expanded', 'false');
|
|
582
|
-
more.setAttribute('aria-controls', REVIEW_GROUP + '-menu');
|
|
583
|
-
more.setAttribute('aria-label', 'Review mode: see the hall as another mode');
|
|
584
|
-
more.title = 'Review mode: see the hall as another mode';
|
|
585
|
-
more.innerHTML = '<svg viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 4.5 6 7.5 9 4.5"/></svg>';
|
|
586
|
-
more.addEventListener('click', function (e) { toggleReviewMenu(e && e.detail === 0); });
|
|
587
|
-
more.addEventListener('keydown', function (e) {
|
|
588
|
-
if (e.key === 'ArrowDown') { e.preventDefault(); openReviewMenu(true); }
|
|
589
|
-
});
|
|
590
|
-
var menu = document.createElement('div');
|
|
591
|
-
menu.id = REVIEW_GROUP + '-menu';
|
|
592
|
-
menu.className = 'craft-review__menu';
|
|
593
|
-
menu.setAttribute('role', 'menu');
|
|
594
|
-
menu.setAttribute('aria-label', 'Review mode');
|
|
595
|
-
menu.setAttribute('hidden', '');
|
|
596
|
-
menu.addEventListener('click', onMenuClick);
|
|
597
|
-
menu.addEventListener('keydown', onMenuKey);
|
|
598
|
-
b.addEventListener('keydown', function (e) {
|
|
599
|
-
if (e.key === 'ArrowDown') { e.preventDefault(); openReviewMenu(true); }
|
|
600
|
-
});
|
|
601
|
-
g.addEventListener('pointerenter', function (e) { if (byMouse(e)) openReviewMenu(false); });
|
|
602
|
-
g.addEventListener('pointerleave', function (e) {
|
|
603
|
-
if (!byMouse(e)) return;
|
|
604
|
-
clearTimeout(closeTimer);
|
|
605
|
-
closeTimer = setTimeout(function () { closeReviewMenu(false); }, 220);
|
|
606
|
-
});
|
|
607
|
-
g.appendChild(b);
|
|
608
|
-
g.appendChild(more);
|
|
609
|
-
g.appendChild(menu);
|
|
610
|
-
return g;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
477
|
// The scribble wall: the top bar's empty space, doodled on while reviewing.
|
|
614
478
|
// craft-review.js draws it (decorative, aria-hidden); every motion is CSS,
|
|
615
479
|
// so the reduced-motion kill switch leaves three finished doodles standing.
|
|
@@ -637,7 +501,7 @@
|
|
|
637
501
|
loadTool(wallTool).then(function (api) {
|
|
638
502
|
var live = document.getElementById(WALL);
|
|
639
503
|
if (live === wall && review === mode) wall.innerHTML = api.wallHtml(mode);
|
|
640
|
-
}, function () { /* a bare wall; the
|
|
504
|
+
}, function () { /* a bare wall; the review panel's mode row still says which */ });
|
|
641
505
|
}
|
|
642
506
|
// sessionStorage: this session's mode, or that the picker was already shown.
|
|
643
507
|
var MODE_KEY = 'hall-craft';
|
|
@@ -703,7 +567,7 @@
|
|
|
703
567
|
function choose(mode, keep) {
|
|
704
568
|
mode = valid(mode);
|
|
705
569
|
if (!mode || !offered(mode)) return;
|
|
706
|
-
if (review) { drop('sessionStorage', REVIEW_KEY); review = null;
|
|
570
|
+
if (review) { drop('sessionStorage', REVIEW_KEY); review = null; }
|
|
707
571
|
set('sessionStorage', MODE_KEY, mode);
|
|
708
572
|
set('sessionStorage', ASKED_KEY, '1');
|
|
709
573
|
if (keep === true) set('localStorage', KEEP_KEY, mode);
|
|
@@ -909,6 +773,16 @@
|
|
|
909
773
|
review: startReview,
|
|
910
774
|
stopReview: stopReview,
|
|
911
775
|
EXPERIENCES: EXPERIENCES.map(function (x) { return x.id; }),
|
|
776
|
+
// The review panel's Review mode row (page-notes.js) draws the four
|
|
777
|
+
// experiences; `short` is the mode switch's own word for each, so the
|
|
778
|
+
// row and the switch never name one mode two ways.
|
|
779
|
+
experiences: function () {
|
|
780
|
+
return EXPERIENCES.map(function (x) {
|
|
781
|
+
var m = MODES.filter(function (y) { return y.id === x.id; })[0];
|
|
782
|
+
return { id: x.id, label: x.label, short: m ? m.short : x.label };
|
|
783
|
+
});
|
|
784
|
+
},
|
|
785
|
+
iconHtml: icon,
|
|
912
786
|
open: openPicker,
|
|
913
787
|
ALWAYS_TABS: ALWAYS_TABS.slice(),
|
|
914
788
|
tabsFor: tabsFor,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
/* craft-review.css — artist review mode (task 1004686; craft-
|
|
2
|
-
|
|
1
|
+
/* craft-review.css — artist review mode (task 1004686; craft-review.js draws
|
|
2
|
+
the wall). Imported by style.css, beside page-notes.css.
|
|
3
3
|
|
|
4
|
-
The
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
The experience under review is picked in the review panel's own Review mode
|
|
5
|
+
row (page-notes.js, task 1004771); the top bar has no Review button any more.
|
|
6
|
+
While an artist reviews one, small black-and-white doodles in the sky's hand
|
|
7
|
+
are scribbled into the top bar's empty space.
|
|
8
8
|
|
|
9
9
|
Every colour is a token (tests/hall_audit.mjs holds that). Every motion is
|
|
10
10
|
CSS on transform and opacity, and the hall's reduced-motion kill switch
|
|
@@ -13,126 +13,13 @@
|
|
|
13
13
|
An @import lands BEFORE style.css's own rules, so a rule here that restyles
|
|
14
14
|
.craft-tool must out-rank it (two classes), or style.css silently wins. */
|
|
15
15
|
|
|
16
|
-
/* ---------- the split pill ---------- */
|
|
17
|
-
.craft-review {
|
|
18
|
-
position: relative;
|
|
19
|
-
display: inline-flex;
|
|
20
|
-
align-items: center;
|
|
21
|
-
flex-shrink: 0;
|
|
22
|
-
border-radius: var(--radius-pill);
|
|
23
|
-
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--craft, var(--ink)) 40%, transparent);
|
|
24
|
-
}
|
|
25
|
-
.craft-review > .craft-tool {
|
|
26
|
-
box-shadow: none;
|
|
27
|
-
border-radius: var(--radius-pill) 0 0 var(--radius-pill);
|
|
28
|
-
}
|
|
29
|
-
.craft-review__more {
|
|
30
|
-
display: inline-flex;
|
|
31
|
-
align-items: center;
|
|
32
|
-
justify-content: center;
|
|
33
|
-
width: 24px;
|
|
34
|
-
height: 32px;
|
|
35
|
-
padding: 0 4px 0 0;
|
|
36
|
-
border: 0;
|
|
37
|
-
border-radius: 0 var(--radius-pill) var(--radius-pill) 0;
|
|
38
|
-
background: none;
|
|
39
|
-
color: var(--craft, var(--ink-soft));
|
|
40
|
-
cursor: pointer;
|
|
41
|
-
}
|
|
42
|
-
.craft-review__more svg { width: 12px; height: 12px; transition: transform 200ms cubic-bezier(.2, .7, .3, 1); }
|
|
43
|
-
.craft-review__more[aria-expanded="true"] svg { transform: rotate(180deg); }
|
|
44
|
-
.craft-review__more:hover { background: color-mix(in srgb, var(--craft, var(--ink)) 12%, transparent); }
|
|
45
|
-
.craft-review__more:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
|
|
46
|
-
@media (min-width: 1181px) {
|
|
47
|
-
.craft-review > .craft-tool { padding-right: 6px; }
|
|
48
|
-
}
|
|
49
|
-
@media (max-width: 1180px) {
|
|
50
|
-
.craft-review > .craft-tool { width: 30px; }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/* Reviewing: the pill takes a wash of the experience's colour, says
|
|
54
|
-
"Reviewing", and a drop of wet paint hangs from it. */
|
|
55
|
-
.craft-tool.craft-tool--reviewing { position: relative; background: color-mix(in srgb, var(--craft, var(--sea)) 14%, transparent); }
|
|
56
|
-
.craft-tool.craft-tool--reviewing::after {
|
|
57
|
-
content: '';
|
|
58
|
-
position: absolute;
|
|
59
|
-
left: 13px;
|
|
60
|
-
bottom: -4px;
|
|
61
|
-
width: 4px;
|
|
62
|
-
height: 7px;
|
|
63
|
-
border-radius: 0 0 3px 3px;
|
|
64
|
-
background: var(--craft, var(--sea));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/* ---------- the menu ---------- */
|
|
68
|
-
.craft-review__menu {
|
|
69
|
-
position: absolute;
|
|
70
|
-
top: calc(100% + 8px);
|
|
71
|
-
right: 0;
|
|
72
|
-
z-index: var(--z-drawer);
|
|
73
|
-
display: flex;
|
|
74
|
-
flex-direction: column;
|
|
75
|
-
gap: 2px;
|
|
76
|
-
min-width: 248px;
|
|
77
|
-
padding: 6px;
|
|
78
|
-
box-sizing: border-box;
|
|
79
|
-
background: var(--bg-card);
|
|
80
|
-
border-radius: var(--radius);
|
|
81
|
-
box-shadow: 0 0 0 1px var(--glass-edge), var(--shadow-pop);
|
|
82
|
-
}
|
|
83
|
-
.craft-review__menu[hidden] { display: none; }
|
|
84
|
-
/* A bridge over the gap, so a pointer travelling down to the menu never
|
|
85
|
-
leaves the pill and the hover menu does not close under it. */
|
|
86
|
-
.craft-review__menu::before { content: ''; position: absolute; left: 0; right: 0; top: -8px; height: 8px; }
|
|
87
|
-
.craft-review__head {
|
|
88
|
-
margin: 8px 12px 2px;
|
|
89
|
-
font-family: var(--label-face);
|
|
90
|
-
font-size: 11px;
|
|
91
|
-
letter-spacing: .12em;
|
|
92
|
-
text-transform: uppercase;
|
|
93
|
-
color: var(--ink-faint);
|
|
94
|
-
}
|
|
95
|
-
.craft-review__item {
|
|
96
|
-
display: flex;
|
|
97
|
-
align-items: center;
|
|
98
|
-
gap: 10px;
|
|
99
|
-
min-height: 36px;
|
|
100
|
-
padding: 0 12px;
|
|
101
|
-
border: 0;
|
|
102
|
-
border-radius: var(--radius-sm);
|
|
103
|
-
background: none;
|
|
104
|
-
color: var(--ink);
|
|
105
|
-
font: inherit;
|
|
106
|
-
font-size: 14px;
|
|
107
|
-
text-align: left;
|
|
108
|
-
white-space: nowrap;
|
|
109
|
-
cursor: pointer;
|
|
110
|
-
}
|
|
111
|
-
.craft-review__item:hover { background: color-mix(in srgb, var(--craft, var(--ink)) 10%, transparent); }
|
|
112
|
-
.craft-review__item:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }
|
|
113
|
-
.craft-review__icon { width: 16px; height: 16px; flex-shrink: 0; color: var(--ink-soft); }
|
|
114
|
-
.craft-review__item[aria-checked="true"] { color: var(--craft, var(--ink)); font-weight: 500; }
|
|
115
|
-
.craft-review__item[aria-checked="true"] .craft-review__icon { color: var(--craft, var(--ink)); }
|
|
116
|
-
.craft-review__check { width: 14px; height: 14px; margin-left: auto; flex-shrink: 0; }
|
|
117
|
-
.craft-review__item--stop { margin-top: 4px; border-top: 1px solid var(--rule-soft); border-radius: 0 0 var(--radius-sm) var(--radius-sm); }
|
|
118
|
-
|
|
119
|
-
/* A phone: no hover and no room for the chevron, so Review itself opens the
|
|
120
|
-
menu (craft-mode.js), and the menu spans the bar instead of hanging off a
|
|
121
|
-
pill that may sit near the left edge. */
|
|
122
|
-
@media (max-width: 640px) {
|
|
123
|
-
.craft-review { position: static; }
|
|
124
|
-
.craft-review__more { display: none; }
|
|
125
|
-
.craft-review > .craft-tool { width: 28px; border-radius: var(--radius-pill); }
|
|
126
|
-
.craft-review__menu { top: calc(100% + 6px); left: 12px; right: 12px; min-width: 0; }
|
|
127
|
-
.craft-review__item { min-height: 44px; }
|
|
128
|
-
}
|
|
129
16
|
/* Under 360px the bar holds two tools, not three: reviewing Ideating, its
|
|
130
17
|
own Ideas button folds away (craft-mode.js marks it a guest). */
|
|
131
18
|
@media (max-width: 359px) { .craft-tool.craft-tool--guest { display: none; } }
|
|
132
19
|
|
|
133
20
|
/* ---------- the scribble wall ---------- */
|
|
134
21
|
/* The top bar's empty space, between the title and the controls. Where none
|
|
135
|
-
is left (a phone), the
|
|
22
|
+
is left (a phone), the review panel's mode row still says which. */
|
|
136
23
|
.craft-graffiti {
|
|
137
24
|
position: relative;
|
|
138
25
|
flex: 1 1 0;
|
|
@@ -722,20 +722,19 @@
|
|
|
722
722
|
if (window.OTBAttention && typeof window.OTBAttention.refresh === 'function') window.OTBAttention.refresh();
|
|
723
723
|
}
|
|
724
724
|
|
|
725
|
-
// task 1003464: the greeting IS Home's one h1 (the mock opens with it)
|
|
726
|
-
//
|
|
727
|
-
//
|
|
728
|
-
//
|
|
729
|
-
const SMALL_NUMBERS = ['', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve'];
|
|
725
|
+
// task 1003464: the greeting IS Home's one h1 (the mock opens with it).
|
|
726
|
+
// task 1004780: it has no sub-line any more. The version tab under the banner
|
|
727
|
+
// shows how far the version is and Next up shows the ready work, so a sentence
|
|
728
|
+
// repeating both was redundant (owner, 2026-10-23).
|
|
730
729
|
const greetingWord = (hour) => (hour < 12 ? 'Good morning' : (hour < 18 ? 'Good afternoon' : 'Good evening'));
|
|
731
|
-
// The version the
|
|
730
|
+
// The version the tab is about: the one being built with the most work in it.
|
|
732
731
|
function currentVersion(progress) {
|
|
733
732
|
const rows = progress && Array.isArray(progress.progress) ? progress.progress : [];
|
|
734
733
|
return rows.filter((r) => r && r.status === 'building')
|
|
735
734
|
.sort((a, b) => (Number(b.total_count) || 0) - (Number(a.total_count) || 0))[0] || null;
|
|
736
735
|
}
|
|
737
|
-
// task 1004774: the version tab under the Home banner
|
|
738
|
-
//
|
|
736
|
+
// task 1004774: the version tab under the Home banner, drawn as a bar.
|
|
737
|
+
// Hidden while no version is being built.
|
|
739
738
|
function renderVersionTab(v) {
|
|
740
739
|
const tab = document.getElementById('home-version');
|
|
741
740
|
if (!tab) return;
|
|
@@ -752,26 +751,11 @@
|
|
|
752
751
|
}
|
|
753
752
|
function renderGreeting() {
|
|
754
753
|
const h1 = document.getElementById('hall-h1');
|
|
755
|
-
const sub = document.getElementById('hall-sub');
|
|
756
754
|
const builder = ctx.lastMeState && ctx.lastMeState.builder;
|
|
757
755
|
if (h1 && builder) {
|
|
758
756
|
h1.textContent = `${greetingWord(new Date().getHours())}, ${builder.display_name || builder.github_login || 'builder'}`;
|
|
759
757
|
}
|
|
760
|
-
|
|
761
|
-
renderVersionTab(v);
|
|
762
|
-
if (!sub) return;
|
|
763
|
-
const parts = [];
|
|
764
|
-
if (v) parts.push(`${v.version_id} is ${Math.round(Number(v.percent_complete) || 0)} percent complete.`);
|
|
765
|
-
const n = ctx.lastClaimableCount;
|
|
766
|
-
if (typeof n === 'number') {
|
|
767
|
-
if (n === 0) parts.push('Nothing is claimable right now.');
|
|
768
|
-
else {
|
|
769
|
-
const word = n < SMALL_NUMBERS.length ? SMALL_NUMBERS[n] : fmtNum(n);
|
|
770
|
-
parts.push(`${word.charAt(0).toUpperCase()}${word.slice(1)} ${n === 1 ? 'task is' : 'tasks are'} claimable right now.`);
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
sub.textContent = parts.join(' ');
|
|
774
|
-
sub.hidden = parts.length === 0;
|
|
758
|
+
renderVersionTab(currentVersion(ctx.lastProgressState));
|
|
775
759
|
}
|
|
776
760
|
const ONBOARDING_STAGE_ORDER = [
|
|
777
761
|
'auth_complete',
|
|
@@ -42,12 +42,11 @@
|
|
|
42
42
|
(one-header-main-h1 — never a second one). The static text is the no-JS /
|
|
43
43
|
signed-out fallback and what brand-apply.js writes the hall's name into;
|
|
44
44
|
hall-render.js renderGreeting turns it into "Good morning, <name>" once
|
|
45
|
-
/me answers
|
|
46
|
-
the version is
|
|
47
|
-
|
|
45
|
+
/me answers. It has no sub-line (task 1004780): the version tab and Next
|
|
46
|
+
up already say how far the version is and what is ready. The label voice
|
|
47
|
+
is the board's own (.page-head--board). -->
|
|
48
48
|
<div class="page-head page-head--board page-head--hall">
|
|
49
49
|
<h1 id="hall-h1" class="page-head__h">Builders' Hall</h1>
|
|
50
|
-
<p id="hall-sub" class="page-head__sub" hidden></p>
|
|
51
50
|
<!-- task 1004774: Standing left the Home band for this chip. At rest it
|
|
52
51
|
shows the avatar and rank. On hover or keyboard focus the chip itself
|
|
53
52
|
widens to the left into the top of the card, and only the facts below
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
--pn-glass: var(--glass, rgba(255, 255, 255, .08));
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
/*
|
|
19
|
+
/* A sheet from the right over a scrim: the lightbulb's panel (lightbulb.js), and
|
|
20
|
+
the feedback doc wherever it is OPENED rather than docked (the public site's
|
|
21
|
+
Artist review button, artist-review.js). */
|
|
20
22
|
html.pn-open { overflow: hidden; }
|
|
21
23
|
.pn {
|
|
22
24
|
position: fixed;
|
|
@@ -26,6 +28,67 @@ html.pn-open { overflow: hidden; }
|
|
|
26
28
|
justify-content: flex-end;
|
|
27
29
|
background: var(--pn-scrim);
|
|
28
30
|
}
|
|
31
|
+
/* The page feedback doc (.pn--dock, task 1004771): docked on the right with no
|
|
32
|
+
scrim and no scroll lock, so the reviewer keeps scrolling the page while
|
|
33
|
+
writing about it. A small tab on its left edge slides it away and back
|
|
34
|
+
(.pn--collapsed). */
|
|
35
|
+
.pn.pn--dock {
|
|
36
|
+
inset: 0 0 0 auto;
|
|
37
|
+
display: block;
|
|
38
|
+
width: min(560px, calc(100% - 36px));
|
|
39
|
+
background: none;
|
|
40
|
+
transition: transform .25s ease;
|
|
41
|
+
}
|
|
42
|
+
.pn--collapsed { transform: translateX(100%); }
|
|
43
|
+
/* Tucked away, the panel's controls leave the tab order once it has slid out. */
|
|
44
|
+
.pn--collapsed .pn__panel { visibility: hidden; transition: visibility 0s .25s; }
|
|
45
|
+
@media (prefers-reduced-motion: reduce) { .pn.pn--dock { transition: none; } }
|
|
46
|
+
.pn__tab {
|
|
47
|
+
position: absolute;
|
|
48
|
+
top: 50%;
|
|
49
|
+
right: 100%;
|
|
50
|
+
transform: translateY(-50%);
|
|
51
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
52
|
+
flex-direction: column; gap: 10px;
|
|
53
|
+
width: 36px; height: 96px; padding: 0;
|
|
54
|
+
border: 0;
|
|
55
|
+
border-radius: var(--radius-sm, 8px) 0 0 var(--radius-sm, 8px);
|
|
56
|
+
background: var(--bg-card);
|
|
57
|
+
color: var(--craft, var(--ink));
|
|
58
|
+
box-shadow: 0 0 0 1px var(--craft, var(--rule)), var(--pn-shadow-pop);
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
}
|
|
61
|
+
.pn__tab:hover { background: var(--pn-glass); }
|
|
62
|
+
.pn__tab:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
|
|
63
|
+
.pn__tab-icon { width: 18px; height: 18px; }
|
|
64
|
+
.pn__tab-chev { width: 12px; height: 12px; transition: transform .25s ease; }
|
|
65
|
+
.pn--collapsed .pn__tab-chev { transform: rotate(180deg); }
|
|
66
|
+
@media (prefers-reduced-motion: reduce) { .pn__tab-chev { transition: none; } }
|
|
67
|
+
/* The Review mode row: which experience the hall wears while reviewed. */
|
|
68
|
+
.pn__modes { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 12px; }
|
|
69
|
+
.pn__modes-h {
|
|
70
|
+
width: 100%;
|
|
71
|
+
font-family: var(--label-face);
|
|
72
|
+
font-size: 11px;
|
|
73
|
+
letter-spacing: .12em;
|
|
74
|
+
text-transform: uppercase;
|
|
75
|
+
color: var(--ink-faint);
|
|
76
|
+
}
|
|
77
|
+
.pn__mode {
|
|
78
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
79
|
+
min-height: 30px; padding: 0 12px;
|
|
80
|
+
border: 1px solid var(--rule);
|
|
81
|
+
border-radius: var(--radius-pill);
|
|
82
|
+
background: none;
|
|
83
|
+
color: var(--ink-soft);
|
|
84
|
+
font: inherit; font-size: 13px;
|
|
85
|
+
cursor: pointer;
|
|
86
|
+
}
|
|
87
|
+
.pn__mode:hover { color: var(--ink); background: var(--pn-glass); }
|
|
88
|
+
.pn__mode[aria-pressed="true"] { color: var(--ink); border-color: var(--craft, var(--ink)); background: var(--pn-glass); }
|
|
89
|
+
.pn__mode:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; }
|
|
90
|
+
.pn__mode-icon { width: 14px; height: 14px; }
|
|
91
|
+
.pn__mode--stop { border-style: dashed; }
|
|
29
92
|
.pn__panel {
|
|
30
93
|
position: relative;
|
|
31
94
|
width: min(560px, 100%);
|