@bongos/core 1.19.393 → 1.19.395
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 +108 -58
- package/.claude/skills/design/SKILL.md +68 -0
- package/.claude/skills/design-sync/SKILL.md +2 -2
- package/.claude/skills/figma-design-sync/SKILL.md +3 -3
- package/config/design-tokens.neutral.json +1 -1
- package/config/design-tokens.schema.json +1 -1
- package/docs/adr/0197-the-ui-design-module.md +52 -0
- package/docs/adr/README.md +1 -0
- package/docs/architecture.md +1 -1
- package/docs/copy-inventory.md +249 -236
- package/docs/copy-registry.json +543 -324
- package/docs/file-map.md +2 -0
- package/docs/modules-contract.md +1 -0
- package/docs/onboarding/slash-commands.md +1 -1
- package/docs/recipes/claude-design-loop.md +3 -3
- package/docs/recipes/figma-design-loop.md +4 -4
- package/modules/ui-design/CLAUDE.md +15 -0
- package/{src/ui → modules/ui-design}/adapters/claude-design/index.js +1 -1
- package/{src/ui → modules/ui-design}/adapters/figma/index.js +3 -3
- package/{docs → modules/ui-design/docs}/design-contract.md +12 -12
- package/modules/ui-design/kit/README.md +3 -0
- package/modules/ui-design/module.json +11 -0
- package/modules/ui-design/scripts/design-sync.js +65 -0
- package/modules/ui-design/scripts/figma-design-sync.js +64 -0
- package/modules/ui-design/scripts/validate-design.js +258 -0
- package/modules/ui-design/skills/README.md +3 -0
- package/modules/ui-design/styles/README.md +3 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/public-landing/assets/cosmos.css +235 -103
- package/public-landing/index.html +228 -75
- package/public-landing/projects.html +256 -140
- package/scripts/gds/copy-inventory.js +7 -5
- package/scripts/gds/design-sync.js +9 -59
- package/scripts/gds/doc-cli-guard.js +11 -5
- package/scripts/gds/figma-design-sync.js +9 -59
- package/scripts/gds/fitness-checks-identity.js +5 -3
- package/scripts/gds/publish-manifest.js +1 -1
- package/scripts/gds/validate-design.js +7 -249
- package/src/bongos/module-scope-map.js +18 -0
- package/src/module-api.js +5 -1
- package/tests/design_tokens_sync.mjs +1 -1
- package/tests/doc_cli_guard.mjs +19 -0
- package/tests/figma_adapter.mjs +1 -1
- package/tests/landing_page.mjs +72 -2
- package/tests/module_loader.mjs +4 -1
- package/tests/projects_hub.mjs +52 -73
- package/tests/projects_hub_pre_uat.mjs +10 -8
|
@@ -68,7 +68,7 @@ ol,ul{list-style:none;padding:0}
|
|
|
68
68
|
It rides on box-shadow, so any control carrying a shadow of its own would otherwise win on
|
|
69
69
|
source order: the primary door re-states the pair below, glow first so dark keeps its lift. */
|
|
70
70
|
:focus-visible{outline:2px solid var(--focus);outline-offset:3px;box-shadow:0 0 0 8px var(--bg)}
|
|
71
|
-
.skip{position:absolute;left:-9999px;top:0;z-index:
|
|
71
|
+
.skip{position:absolute;left:-9999px;top:0;z-index:100;min-height:24px;padding:11px 18px;border-radius:999px;
|
|
72
72
|
background:var(--bg-card);color:var(--ink);border:1px solid var(--rule);font-size:15px}
|
|
73
73
|
.skip:focus{left:14px;top:14px}
|
|
74
74
|
/* the skip link's target: focusable so the skip actually lands, but never ringed */
|
|
@@ -148,39 +148,76 @@ main:focus{outline:none;box-shadow:none}
|
|
|
148
148
|
.scrim{position:absolute;top:calc(var(--top-h) * -1);left:0;right:0;bottom:0;z-index:1;
|
|
149
149
|
background:var(--sky-scrim);pointer-events:none}
|
|
150
150
|
|
|
151
|
-
/* =====================
|
|
152
|
-
.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
.
|
|
163
|
-
|
|
164
|
-
.
|
|
165
|
-
|
|
166
|
-
color:var(--ink-soft);transition:color .2s
|
|
167
|
-
.
|
|
168
|
-
.
|
|
169
|
-
|
|
170
|
-
.
|
|
151
|
+
/* ===================== the bar: no pill, one hairline (task 1003300) =====================
|
|
152
|
+
The hub's chrome on the front door. The ground is the page ground closed by one --rule
|
|
153
|
+
hairline; the sculpture starts under it, so the top wash (--sky-topscrim) is gone. The one
|
|
154
|
+
filled shape is the sign-in door. Same names as the hub's bar so the two pages are one bar. */
|
|
155
|
+
.topbar{position:relative;z-index:50;height:var(--top-h);display:flex;align-items:center;gap:22px;
|
|
156
|
+
padding:0 26px;background:var(--bg);border-bottom:1px solid var(--rule)}
|
|
157
|
+
.barLeft{display:flex;align-items:center;gap:26px;flex:1 1 auto;min-width:0}
|
|
158
|
+
.barRight{display:flex;align-items:center;gap:20px;flex:none}
|
|
159
|
+
.barMark{display:inline-flex;align-items:center;gap:10px;min-height:44px;flex:none;
|
|
160
|
+
font-family:var(--font-display);font-size:17px;font-weight:500;letter-spacing:-.015em;
|
|
161
|
+
color:var(--ink);text-shadow:var(--type-halo)}
|
|
162
|
+
.barMark svg{width:23px;height:18px;display:block;flex:none}
|
|
163
|
+
/* the right group is lowercase text; the OPEN trigger is underlined in the accent */
|
|
164
|
+
.trig{display:inline-flex;align-items:center;gap:7px;min-height:44px;padding:0;cursor:pointer;
|
|
165
|
+
background:none;border:0;border-radius:var(--radius-sm);font-family:var(--font-body);font-size:15px;
|
|
166
|
+
font-weight:400;text-transform:lowercase;color:var(--ink-soft);transition:color .2s cubic-bezier(.2,.7,.3,1)}
|
|
167
|
+
.trig:hover{color:var(--ink)}
|
|
168
|
+
.trig[aria-expanded="true"]{color:var(--ink);text-decoration:underline;text-decoration-color:var(--accent);
|
|
169
|
+
text-decoration-thickness:2px;text-underline-offset:7px}
|
|
170
|
+
.trig .cv{width:13px;height:13px;flex:none;fill:none;stroke:currentColor;stroke-width:1.4;
|
|
171
|
+
stroke-linecap:round;stroke-linejoin:round;transition:transform .2s cubic-bezier(.2,.7,.3,1)}
|
|
172
|
+
.trig[aria-expanded="true"] .cv{transform:rotate(180deg)}
|
|
173
|
+
.whoSlot{display:inline-flex;align-items:center}
|
|
174
|
+
.who{gap:9px}
|
|
175
|
+
.who img{width:26px;height:26px;border-radius:var(--radius-pill);object-fit:cover;flex:none;border:1px solid var(--rule)}
|
|
176
|
+
.who-name{max-width:14ch;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
177
|
+
.modeBtn{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;cursor:pointer;
|
|
178
|
+
background:none;border:0;border-radius:var(--radius-pill);color:var(--ink-soft);transition:color .2s cubic-bezier(.2,.7,.3,1)}
|
|
179
|
+
.modeBtn:hover{color:var(--ink)}
|
|
180
|
+
.modeBtn svg{width:20px;height:20px}
|
|
171
181
|
.sun{display:none}
|
|
172
|
-
|
|
173
|
-
|
|
182
|
+
/* the sign-in door: the one filled shape on a bar with no pills — inverted ink, lowercase */
|
|
183
|
+
.pill{display:inline-flex;align-items:center;justify-content:center;min-height:44px;padding:0 22px;
|
|
184
|
+
border-radius:var(--radius-pill);font-family:inherit;font-size:15px;color:var(--ink);cursor:pointer;
|
|
185
|
+
transition:background .25s cubic-bezier(.2,.7,.3,1),color .2s ease}
|
|
186
|
+
.signin{background:color-mix(in srgb,var(--ink) 92%,var(--bg));color:var(--bg);border:0;
|
|
187
|
+
text-transform:lowercase;font-weight:500;padding:0 26px}
|
|
174
188
|
.signin:hover{background:var(--ink);color:var(--bg)}
|
|
175
|
-
/* the signed-in chip replaces the sign-in door in the same slot */
|
|
176
|
-
#who img{width:26px;height:26px;border-radius:var(--radius-pill);flex:none;object-fit:cover}
|
|
177
|
-
.who-name{font-size:15px;color:var(--ink);text-shadow:var(--type-halo);white-space:nowrap}
|
|
178
|
-
.signout{text-transform:lowercase;padding:0 18px}
|
|
179
189
|
:root[data-mode="dark"] .sun{display:block}
|
|
180
190
|
:root[data-mode="dark"] .moon{display:none}
|
|
181
191
|
@media (prefers-color-scheme:dark){:root:not([data-mode="light"]) .sun{display:block}
|
|
182
192
|
:root:not([data-mode="light"]) .moon{display:none}}
|
|
183
193
|
|
|
194
|
+
/* the menu bands (.menuBand — .band is this page's content sections): not floating cards. Full width on the card ground, closed by one hairline, pushing
|
|
195
|
+
the page down. Cells in a hairline-separated row, a lowercase display title over one clause;
|
|
196
|
+
the whole cell is the target. One authored move, dead under world.css's kill switch. */
|
|
197
|
+
.menuBand{position:relative;z-index:49;background:var(--bg-card);border-bottom:1px solid var(--rule);
|
|
198
|
+
animation:bandIn .22s cubic-bezier(.16,1,.3,1) both}
|
|
199
|
+
@keyframes bandIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
|
|
200
|
+
.bandIn{padding:20px 26px 28px}
|
|
201
|
+
.bandRow{display:grid;grid-template-columns:repeat(4,1fr);gap:0 34px;list-style:none;margin:0;padding:0}
|
|
202
|
+
.bandCell{position:relative;border-top:1px solid var(--rule-soft);padding:15px 0 0}
|
|
203
|
+
.bandT{display:inline-block;padding:2px 0;font-family:var(--font-display);font-size:24px;font-weight:300;
|
|
204
|
+
line-height:1.14;letter-spacing:-.03em;text-transform:lowercase;color:var(--ink-soft);
|
|
205
|
+
transition:color .2s cubic-bezier(.2,.7,.3,1)}
|
|
206
|
+
.bandT::after{content:"";position:absolute;inset:0}
|
|
207
|
+
.bandCell:hover .bandT{color:var(--ink)}
|
|
208
|
+
.bandCell:hover{border-top-color:var(--ink-faint)}
|
|
209
|
+
.bandC{display:block;margin-top:5px;font-size:13.5px;line-height:1.45;color:var(--ink-soft)}
|
|
210
|
+
.bandMore{margin-top:18px}
|
|
211
|
+
.bandMore .bandT{font-size:17px;font-weight:400;min-height:24px;display:inline-flex;align-items:center}
|
|
212
|
+
.bandProfile{display:flex;align-items:flex-end;justify-content:space-between;gap:34px;flex-wrap:wrap}
|
|
213
|
+
.bandWho{display:flex;align-items:center;gap:10px;padding-top:15px;border-top:1px solid var(--rule-soft);
|
|
214
|
+
flex:0 1 auto;font-family:var(--label-face);font-size:var(--label-size);letter-spacing:var(--label-space);
|
|
215
|
+
text-transform:uppercase;color:var(--ink-faint)}
|
|
216
|
+
.bandWho img{width:30px;height:30px;border-radius:var(--radius-pill);object-fit:cover;border:1px solid var(--rule)}
|
|
217
|
+
.bandProfile .bandRow{display:flex;justify-content:flex-end;gap:30px;flex:0 1 auto}
|
|
218
|
+
.bandProfile .bandCell,.bandProfile .bandSlot{flex:0 0 168px;text-align:right}
|
|
219
|
+
.bandSlot{border:0}
|
|
220
|
+
|
|
184
221
|
/* ===================== hero ===================== */
|
|
185
222
|
.hero{position:relative;min-height:calc(var(--stage) - var(--top-h));padding:0 26px}
|
|
186
223
|
.layer{position:var(--layer-pos);top:calc(var(--top-h) * -1);left:0;right:0;bottom:0;z-index:2;
|
|
@@ -372,7 +409,6 @@ body{
|
|
|
372
409
|
--sky-shadow:drop-shadow(0 26px 30px color-mix(in srgb,var(--ink) 20%,transparent));
|
|
373
410
|
--sky-mask:none;
|
|
374
411
|
--sky-scrim:none;
|
|
375
|
-
--sky-topscrim:none;
|
|
376
412
|
--pill-bg:color-mix(in srgb,var(--bg) 88%,transparent);
|
|
377
413
|
--pill-bg-hi:color-mix(in srgb,var(--bg-card) 94%,transparent);
|
|
378
414
|
--pill-edge:var(--rule-soft);
|
|
@@ -431,7 +467,6 @@ body{
|
|
|
431
467
|
|
|
432
468
|
--sky-mask:none;
|
|
433
469
|
--sky-scrim:radial-gradient(30% 13% at 15% 42%,rgba(0,0,0,.94),rgba(0,0,0,.6) 58%,rgba(0,0,0,0) 100%),radial-gradient(60% 55% at 26% 32%,rgba(0,0,0,.88),rgba(0,0,0,.42) 48%,rgba(0,0,0,0) 76%);
|
|
434
|
-
--sky-topscrim:linear-gradient(to bottom,rgba(0,0,0,.84) 0,rgba(0,0,0,.5) 46%,rgba(0,0,0,0) 100%);
|
|
435
470
|
--pill-bg:var(--void-glass);
|
|
436
471
|
--pill-bg-hi:var(--void-glass-strong);
|
|
437
472
|
--pill-edge:var(--void-glass-edge);
|
|
@@ -499,7 +534,6 @@ body{
|
|
|
499
534
|
|
|
500
535
|
--sky-mask:none;
|
|
501
536
|
--sky-scrim:radial-gradient(30% 13% at 15% 42%,rgba(0,0,0,.94),rgba(0,0,0,.6) 58%,rgba(0,0,0,0) 100%),radial-gradient(60% 55% at 26% 32%,rgba(0,0,0,.88),rgba(0,0,0,.42) 48%,rgba(0,0,0,0) 76%);
|
|
502
|
-
--sky-topscrim:linear-gradient(to bottom,rgba(0,0,0,.84) 0,rgba(0,0,0,.5) 46%,rgba(0,0,0,0) 100%);
|
|
503
537
|
--pill-bg:var(--void-glass);
|
|
504
538
|
--pill-bg-hi:var(--void-glass-strong);
|
|
505
539
|
--pill-edge:var(--void-glass-edge);
|
|
@@ -553,21 +587,25 @@ body{
|
|
|
553
587
|
|
|
554
588
|
/* ===================== narrow: one column, the sculpture behind ===================== */
|
|
555
589
|
@media (max-width:900px){
|
|
556
|
-
.topbar{height:var(--top-h);padding:0 10px}
|
|
557
|
-
.
|
|
558
|
-
.
|
|
590
|
+
.topbar{height:var(--top-h);padding:0 12px;gap:10px}
|
|
591
|
+
.barLeft{gap:12px}
|
|
592
|
+
.barRight{gap:12px}
|
|
559
593
|
/* the wordmark folds away so the bar holds ONE row down to 320; the link keeps its name */
|
|
560
|
-
.
|
|
594
|
+
.barMark{min-width:44px;justify-content:center}
|
|
561
595
|
.mark-word{display:none}
|
|
562
|
-
.
|
|
563
|
-
.nav-pill a{flex:1 1 auto;min-width:24px;padding:12px clamp(4px,1.6vw,8px);text-align:center;
|
|
564
|
-
white-space:nowrap;font-size:clamp(11.5px,3.4vw,13px)}
|
|
565
|
-
.right-pills{flex:none;gap:6px}
|
|
566
|
-
.mode-toggle{width:38px;min-width:38px;padding:0}
|
|
567
|
-
.signin{padding:0 12px}
|
|
568
|
-
.signout{padding:0 10px}
|
|
596
|
+
.trig{font-size:13.5px}
|
|
569
597
|
.who-name{display:none}
|
|
570
|
-
|
|
598
|
+
.modeBtn{width:40px;height:40px}
|
|
599
|
+
.modeBtn svg{width:18px;height:18px}
|
|
600
|
+
.pill{min-height:42px;font-size:13.5px}
|
|
601
|
+
.signin{padding:0 16px}
|
|
602
|
+
.bandIn{padding:0 12px 16px}
|
|
603
|
+
.bandRow{grid-template-columns:1fr;gap:0}
|
|
604
|
+
.bandT{font-size:20px}
|
|
605
|
+
.bandProfile{display:block}
|
|
606
|
+
.bandProfile .bandRow{display:block;text-align:left}
|
|
607
|
+
.bandProfile .bandCell,.bandProfile .bandSlot{flex:none;text-align:left}
|
|
608
|
+
.bandWho{padding-bottom:14px}
|
|
571
609
|
.hero{min-height:0;padding-bottom:36px}
|
|
572
610
|
.layer{display:flex;flex-direction:column;align-items:center;text-align:center;
|
|
573
611
|
top:0;left:auto;right:auto;bottom:auto;padding-top:12px;pointer-events:auto}
|
|
@@ -764,9 +802,15 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
764
802
|
|
|
765
803
|
<a class="skip" href="#main">Skip to content</a>
|
|
766
804
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
805
|
+
<!-- the bar: the hub's grammar on the front door too (task 1003300; v2 "the hairline",
|
|
806
|
+
docs/design/projects-hub-direction-v2.md §7). No pill: the bar's ground is the page ground
|
|
807
|
+
closed by one hairline and the sculpture begins under it; the only filled shape it holds is
|
|
808
|
+
the sign-in door. The Projects band holds the hub's views as plain deep links — no item is
|
|
809
|
+
ever marked current here, because this page is none of them — and the identity chip is the
|
|
810
|
+
profile band's trigger. The hall link lives in the footer. -->
|
|
811
|
+
<header class="topbar bar">
|
|
812
|
+
<div class="barLeft">
|
|
813
|
+
<a class="barMark" href="/" aria-label="{{productName}}">
|
|
770
814
|
<svg viewBox="0 0 26 20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.4"
|
|
771
815
|
stroke-linecap="round" stroke-linejoin="round">
|
|
772
816
|
<ellipse cx="8.4" cy="5.6" rx="6.6" ry="2.9"/>
|
|
@@ -776,30 +820,61 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
776
820
|
</svg>
|
|
777
821
|
<span class="mark-word">{{productName}}</span>
|
|
778
822
|
</a>
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
<
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
<
|
|
797
|
-
<a class="pill signin" href="/api/bongos/auth/web/start?return=/">Sign in</a>
|
|
798
|
-
</span>
|
|
823
|
+
</div>
|
|
824
|
+
<div class="barRight">
|
|
825
|
+
<button class="trig" type="button" id="projTrig" data-menu="projects" aria-expanded="false" aria-controls="menuProjects">Projects<svg class="cv" viewBox="0 0 20 20" aria-hidden="true"><path d="m5.6 8 4.4 4.4L14.4 8"/></svg></button>
|
|
826
|
+
<button class="modeBtn" id="mode" type="button" aria-label="Toggle light or dark">
|
|
827
|
+
<svg class="moon" viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor"
|
|
828
|
+
stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
|
|
829
|
+
<path d="M16.2 12.4A6.8 6.8 0 0 1 7.6 3.8a6.8 6.8 0 1 0 8.6 8.6z"/>
|
|
830
|
+
</svg>
|
|
831
|
+
<svg class="sun" viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor"
|
|
832
|
+
stroke-width="1.4" stroke-linecap="round">
|
|
833
|
+
<circle cx="10" cy="10" r="3.9"/>
|
|
834
|
+
<path d="M10 1.6v2.1M10 16.3v2.1M1.6 10h2.1M16.3 10h2.1M4.1 4.1l1.5 1.5M14.4 14.4l1.5 1.5M15.9 4.1l-1.5 1.5M5.6 14.4l-1.5 1.5"/>
|
|
835
|
+
</svg>
|
|
836
|
+
</button>
|
|
837
|
+
<!-- the identity slot: the sign-in door signed out; the chip (the profile trigger) once
|
|
838
|
+
the session resolves — swapped client-side, never a wrong-state flash -->
|
|
839
|
+
<span class="whoSlot" id="who">
|
|
840
|
+
<a class="pill signin" href="/api/bongos/auth/web/start?return=/">Sign in</a>
|
|
799
841
|
</span>
|
|
800
|
-
</
|
|
842
|
+
</div>
|
|
801
843
|
</header>
|
|
802
844
|
|
|
845
|
+
<!-- the two bands: full-width, on the card ground, closed by one hairline; they unfold under the
|
|
846
|
+
bar's edge and push the page down. One disclosure component, two instances (the script at the
|
|
847
|
+
foot of the page): Escape closes and returns focus to the trigger, the arrows walk the items,
|
|
848
|
+
Tab out closes, a click outside closes, opening one closes the other. -->
|
|
849
|
+
<div class="menuBand" id="menuProjects" data-menu="projects" hidden>
|
|
850
|
+
<div class="bandIn">
|
|
851
|
+
<ul class="bandRow">
|
|
852
|
+
<li class="bandCell"><a class="bandT" href="/projects?view=map">Sky</a><span class="bandC">the sky of every project</span></li>
|
|
853
|
+
<li class="bandCell"><a class="bandT" href="/projects?view=map&sub=table">Table</a><span class="bandC">the same projects as rows</span></li>
|
|
854
|
+
<li class="bandCell" id="mineCell" hidden><a class="bandT" href="/projects?view=mine">Your projects</a><span class="bandC">the ones you own or build on</span></li>
|
|
855
|
+
<li class="bandCell"><a class="bandT" href="/projects?view=new">New project</a><span class="bandC">five questions and a home</span></li>
|
|
856
|
+
</ul>
|
|
857
|
+
<!-- under a hairline: what this is, and the docs (§5's About cell; goal 1000063's About page
|
|
858
|
+
takes the anchor over when it lands) -->
|
|
859
|
+
<ul class="bandRow bandMore">
|
|
860
|
+
<li class="bandCell"><a class="bandT" href="#about">About</a></li>
|
|
861
|
+
<li class="bandCell"><a class="bandT" href="/docs">Docs</a></li>
|
|
862
|
+
</ul>
|
|
863
|
+
</div>
|
|
864
|
+
</div>
|
|
865
|
+
<div class="menuBand" id="menuProfile" data-menu="profile" hidden>
|
|
866
|
+
<div class="bandIn bandProfile">
|
|
867
|
+
<p class="bandWho" id="bandWho"></p>
|
|
868
|
+
<ul class="bandRow">
|
|
869
|
+
<li class="bandCell"><a class="bandT" href="/projects?view=mine">My projects</a></li>
|
|
870
|
+
<!-- the deferred Settings slot: a place it would go, rendering nothing at all -->
|
|
871
|
+
<li class="bandSlot" aria-hidden="true"></li>
|
|
872
|
+
<!-- the honest hover target; the script upgrades the click to the POST -->
|
|
873
|
+
<li class="bandCell"><a class="bandT signout" href="/api/bongos/auth/logout">Sign out</a></li>
|
|
874
|
+
</ul>
|
|
875
|
+
</div>
|
|
876
|
+
</div>
|
|
877
|
+
|
|
803
878
|
<main id="main" tabindex="-1">
|
|
804
879
|
<section class="hero">
|
|
805
880
|
<div class="field" aria-hidden="true">
|
|
@@ -880,7 +955,7 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
880
955
|
</section>
|
|
881
956
|
<!--apex-only:end-->
|
|
882
957
|
|
|
883
|
-
<section class="band">
|
|
958
|
+
<section class="band" id="about">
|
|
884
959
|
<div class="wrap">
|
|
885
960
|
<h2>A project as a codebase, regardless of function.</h2>
|
|
886
961
|
<p class="sub">A laddered beat progresses all tasks.</p>
|
|
@@ -1112,22 +1187,100 @@ composition A; landing composition v3, picked by the owner 2026-08-27. No roll,
|
|
|
1112
1187
|
var slot = document.getElementById('who');
|
|
1113
1188
|
if (!slot) return;
|
|
1114
1189
|
slot.textContent = '';
|
|
1190
|
+
/* the chip IS the profile band's trigger (task 1003300): avatar + login + a chevron;
|
|
1191
|
+
the band below names the person and carries Sign out */
|
|
1192
|
+
var chip = document.createElement('button');
|
|
1193
|
+
chip.type = 'button';
|
|
1194
|
+
chip.className = 'trig who';
|
|
1195
|
+
chip.setAttribute('data-menu', 'profile');
|
|
1196
|
+
chip.setAttribute('aria-expanded', 'false');
|
|
1197
|
+
chip.setAttribute('aria-controls', 'menuProfile');
|
|
1198
|
+
chip.setAttribute('aria-label', 'Account, ' + me.github_login);
|
|
1115
1199
|
var art = safeHttpUrl(me.avatar_url);
|
|
1200
|
+
var who = document.getElementById('bandWho');
|
|
1116
1201
|
if (art) {
|
|
1117
1202
|
var img = document.createElement('img');
|
|
1118
1203
|
img.src = art; img.alt = ''; img.width = 26; img.height = 26;
|
|
1119
|
-
|
|
1204
|
+
chip.appendChild(img);
|
|
1205
|
+
if (who) who.appendChild(img.cloneNode(true));
|
|
1120
1206
|
}
|
|
1121
1207
|
var name = document.createElement('span');
|
|
1122
1208
|
name.className = 'who-name';
|
|
1123
1209
|
name.textContent = me.github_login;
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1210
|
+
chip.appendChild(name);
|
|
1211
|
+
if (who) who.appendChild(document.createTextNode(me.github_login));
|
|
1212
|
+
var cv = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
1213
|
+
cv.setAttribute('class', 'cv'); cv.setAttribute('viewBox', '0 0 20 20'); cv.setAttribute('aria-hidden', 'true');
|
|
1214
|
+
var pth = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
1215
|
+
pth.setAttribute('d', 'm5.6 8 4.4 4.4L14.4 8');
|
|
1216
|
+
cv.appendChild(pth);
|
|
1217
|
+
chip.appendChild(cv);
|
|
1218
|
+
slot.appendChild(chip);
|
|
1219
|
+
wireTrigger(chip);
|
|
1220
|
+
/* a member sees the Your-projects cell of the Projects band */
|
|
1221
|
+
var mine = document.getElementById('mineCell');
|
|
1222
|
+
if (mine) mine.hidden = false;
|
|
1223
|
+
});
|
|
1224
|
+
/* the sign-out anchor keeps its honest href; the click is the real POST */
|
|
1225
|
+
var signout = document.querySelector('#menuProfile a.signout');
|
|
1226
|
+
if (signout) signout.addEventListener('click', function (e) { e.preventDefault(); doLogout(); });
|
|
1227
|
+
|
|
1228
|
+
/* ── the bands: one disclosure component, two instances (the same contract as the hub's,
|
|
1229
|
+
task 1003300). Plain links in a disclosure — aria-expanded + aria-controls, no menu role —
|
|
1230
|
+
so what is announced is what ships. Escape closes and RETURNS FOCUS to the trigger, the
|
|
1231
|
+
arrows move between items, Enter on a trigger lands on the first item, Tab out closes, a
|
|
1232
|
+
click outside closes, opening one closes the other. The open move is CSS, so world.css's
|
|
1233
|
+
kill switch owns it. No item is ever current here: this page is none of those views. ── */
|
|
1234
|
+
function openBand() { return document.querySelector('.menuBand:not([hidden])'); }
|
|
1235
|
+
function closeMenus() {
|
|
1236
|
+
var open = openBand();
|
|
1237
|
+
if (!open) return;
|
|
1238
|
+
var trig = document.querySelector('.trig[aria-expanded="true"]');
|
|
1239
|
+
var inside = open.contains(document.activeElement);
|
|
1240
|
+
open.hidden = true;
|
|
1241
|
+
if (trig) trig.setAttribute('aria-expanded', 'false');
|
|
1242
|
+
if (inside && trig) trig.focus();
|
|
1243
|
+
}
|
|
1244
|
+
function openMenu(trig, byKeyboard) {
|
|
1245
|
+
closeMenus();
|
|
1246
|
+
trig.setAttribute('aria-expanded', 'true');
|
|
1247
|
+
var band = document.getElementById(trig.getAttribute('aria-controls'));
|
|
1248
|
+
if (!band) return;
|
|
1249
|
+
band.hidden = false;
|
|
1250
|
+
if (byKeyboard) { var first = band.querySelector('.bandCell:not([hidden]) a'); if (first) first.focus(); }
|
|
1251
|
+
}
|
|
1252
|
+
function wireTrigger(t) {
|
|
1253
|
+
t.addEventListener('click', function (ev) {
|
|
1254
|
+
if (t.getAttribute('aria-expanded') === 'true') closeMenus();
|
|
1255
|
+
else openMenu(t, ev.detail === 0);
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
document.querySelectorAll('.trig').forEach(wireTrigger);
|
|
1259
|
+
document.addEventListener('click', function (ev) {
|
|
1260
|
+
if (!openBand()) return;
|
|
1261
|
+
if (ev.target.closest && (ev.target.closest('.menuBand') || ev.target.closest('.trig'))) return;
|
|
1262
|
+
closeMenus();
|
|
1263
|
+
});
|
|
1264
|
+
document.addEventListener('keydown', function (ev) {
|
|
1265
|
+
var open = openBand();
|
|
1266
|
+
if (!open) return;
|
|
1267
|
+
if (ev.key === 'Escape') { closeMenus(); return; }
|
|
1268
|
+
if (ev.key !== 'ArrowDown' && ev.key !== 'ArrowUp') return;
|
|
1269
|
+
var items = Array.prototype.filter.call(open.querySelectorAll('a'), function (a) { return !a.closest('[hidden]'); });
|
|
1270
|
+
if (!items.length) return;
|
|
1271
|
+
var at = items.indexOf(document.activeElement);
|
|
1272
|
+
ev.preventDefault();
|
|
1273
|
+
items[(at + (ev.key === 'ArrowDown' ? 1 : items.length - 1) + items.length) % items.length].focus();
|
|
1274
|
+
});
|
|
1275
|
+
document.querySelectorAll('.menuBand').forEach(function (b) {
|
|
1276
|
+
b.addEventListener('click', function (ev) {
|
|
1277
|
+
if (ev.target.closest && ev.target.closest('a')) closeMenus();
|
|
1278
|
+
});
|
|
1279
|
+
b.addEventListener('focusout', function (ev) {
|
|
1280
|
+
var to = ev.relatedTarget;
|
|
1281
|
+
if (!to || b.contains(to) || (to.closest && to.closest('.trig'))) return;
|
|
1282
|
+
closeMenus();
|
|
1283
|
+
});
|
|
1131
1284
|
});
|
|
1132
1285
|
|
|
1133
1286
|
/* ── the live ledger. Every figure and row on this page is baked as a real
|