@bongos/core 1.19.392 → 1.19.394

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.
@@ -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:9;min-height:24px;padding:11px 18px;border-radius:999px;
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
- /* ===================== top bar: three pill groups on one line ===================== */
152
- .topbar{position:relative;z-index:3;height:var(--top-h);display:flex;align-items:center;padding:0 26px}
153
- .topbar::before{content:"";position:absolute;z-index:-1;left:0;right:0;top:0;height:128px;
154
- background:var(--sky-topscrim);pointer-events:none}
155
- .topnav{display:flex;align-items:center;gap:12px;width:100%}
156
- .pill{display:inline-flex;align-items:center;justify-content:center;min-height:46px;padding:0 22px;
157
- border-radius:var(--radius-pill);background:var(--pill-bg);border:1px solid var(--pill-edge);
158
- backdrop-filter:blur(var(--glass-blur));-webkit-backdrop-filter:blur(var(--glass-blur));
159
- font-family:inherit;font-size:16px;color:var(--ink);cursor:pointer;
160
- transition:background .25s cubic-bezier(.2,.7,.3,1),color .2s ease}
161
- .pill:hover{background:var(--pill-bg-hi)}
162
- .mark-pill{gap:10px;padding:0 24px;background:var(--pill-bg-hi);letter-spacing:-.012em}
163
- .mark-pill svg{width:23px;height:18px;display:block;flex:none}
164
- .nav-pill{gap:0;padding:0 6px;margin:0 auto}
165
- .nav-pill a{padding:13px 22px;border-radius:var(--radius-pill);font-size:16px;text-transform:lowercase;
166
- color:var(--ink-soft);transition:color .2s ease}
167
- .nav-pill a:hover{color:var(--ink)}
168
- .right-pills{display:flex;align-items:center;gap:10px}
169
- .mode-toggle{width:46px;min-width:46px;padding:0}
170
- .mode-toggle svg{width:18px;height:18px}
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
- .signin{background:color-mix(in srgb,var(--ink) 92%,var(--bg));color:var(--bg);border-color:transparent;
173
- text-transform:lowercase;font-weight:500;padding:0 30px}
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
- .topnav{flex-wrap:nowrap;gap:6px}
558
- .pill{min-height:42px;font-size:13px;padding:0 12px}
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
- .mark-pill{padding:0 12px;gap:0}
594
+ .barMark{min-width:44px;justify-content:center}
561
595
  .mark-word{display:none}
562
- .nav-pill{flex:1 1 auto;min-width:0;margin:0;padding:0 3px}
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
- #who img{width:22px;height:22px}
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
- <header class="topbar">
768
- <nav class="topnav" aria-label="Primary">
769
- <a class="pill mark-pill" href="/" aria-label="{{productName}}">
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
- <span class="pill nav-pill">
780
- <a href="/projects">Projects</a>
781
- <a data-link-hall href="{{buildersOrigin}}">Hall</a>
782
- <a href="/docs">Docs</a>
783
- </span>
784
- <span class="right-pills">
785
- <button class="mode-toggle pill" id="mode" type="button" aria-label="Toggle light or dark">
786
- <svg class="moon" viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor"
787
- stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round">
788
- <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"/>
789
- </svg>
790
- <svg class="sun" viewBox="0 0 20 20" aria-hidden="true" fill="none" stroke="currentColor"
791
- stroke-width="1.4" stroke-linecap="round">
792
- <circle cx="10" cy="10" r="3.9"/>
793
- <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"/>
794
- </svg>
795
- </button>
796
- <span class="right-pills" id="who">
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
- </nav>
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&amp;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
- slot.appendChild(img);
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
- slot.appendChild(name);
1125
- var out = document.createElement('a');
1126
- out.className = 'pill signout';
1127
- out.href = API + '/auth/logout'; /* honest hover target; the click upgrades it to the real POST */
1128
- out.textContent = 'Sign out';
1129
- out.addEventListener('click', function (e) { e.preventDefault(); doLogout(); });
1130
- slot.appendChild(out);
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