spectoflow 0.17.1 → 0.18.0

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.
@@ -54,6 +54,15 @@ html[data-design="console"] nav.tabs#tabs {
54
54
  }
55
55
  html[data-design="console"] .topbar { padding-left:calc(var(--cx-rail-w) + 16px); }
56
56
  html[data-design="console"] .stage { margin-left:var(--cx-rail-w); }
57
+ /* .app-footer is a sibling of .stage, not inside it — the rail is fixed to the viewport (not just
58
+ .stage), so without this it clips the footer's left edge (the brand name loses its first letters)
59
+ at every scroll position, not just at the top. */
60
+ html[data-design="console"] .app-footer { margin-left:var(--cx-rail-w); }
61
+ /* The header brand mark reads too small at the base 26px against this design's darker, denser
62
+ topbar — give it more presence. The footer's own (deliberately smaller) logo keeps its existing,
63
+ more specific rule and is unaffected. */
64
+ html[data-design="console"] .topbar .brand-logo { height:34px; }
65
+ html[data-design="console"] .topbar .brand-logo-img { height:34px; }
57
66
  html[data-design="console"] nav.tabs#tabs .tab {
58
67
  position:relative; flex-direction:column; justify-content:center; gap:0;
59
68
  width:52px; height:48px; padding:0; border-radius:12px; flex-shrink:0;
@@ -179,6 +188,7 @@ html[data-design="console"][data-theme="light"] .cx-cmdk { background:rgba(15,23
179
188
  }
180
189
  html[data-design="console"] .topbar { padding-left:16px; }
181
190
  html[data-design="console"] .stage { margin-left:0; margin-bottom:64px; }
191
+ html[data-design="console"] .app-footer { margin-left:0; }
182
192
  html[data-design="console"] nav.tabs#tabs .tab-label { display:none !important; }
183
193
  }
184
194
 
@@ -45,6 +45,12 @@ html[data-design="orbit"] .kpi-num { font-weight:700; }
45
45
 
46
46
  /* navigation is the radial menu — the tab bar is hidden, content runs full width */
47
47
  html[data-design="orbit"] nav.tabs { display:none; }
48
+
49
+ /* The hub (orbit.js) takes over the brand mark's visual role, so the original logo link is hidden;
50
+ the "spectoflow" text becomes the dashboard link in its place (see orbit.js's brand-name handler). */
51
+ html[data-design="orbit"] .brand-logo { display:none; }
52
+ html[data-design="orbit"] .brand-name { cursor:pointer; transition:opacity .15s; }
53
+ html[data-design="orbit"] .brand-name:hover { opacity:.72; text-decoration:underline; text-underline-offset:3px; }
48
54
  html[data-design="orbit"] .topbar { grid-template-columns:auto 1fr auto; }
49
55
 
50
56
  /* pills + rounder cards */
@@ -88,24 +94,30 @@ html[data-design="orbit"] body.booting .card { animation: ob-rise .45s cubic-bez
88
94
  * THE HUB — round brand button in the header; shows live global progress %, doubles as the
89
95
  * radial-menu trigger. Injected by orbit.js as the first child of .brand.
90
96
  * ============================================================================================ */
97
+ /* The hub IS the brand mark: the original .brand-logo link is hidden (see below) and this button
98
+ takes over both roles it used to split — visual identity AND, now, the menu trigger. The ring is a
99
+ conic-gradient driven by --ob-pct (set in orbit.js on every progress update): a live "how much of
100
+ the project shipped" reading at a glance, without needing a text percentage on the closed hub. */
91
101
  html[data-design="orbit"] .ob-hub {
92
- position:relative; width:46px; height:46px; border-radius:50%; flex-shrink:0;
93
- background:var(--signal); color:var(--on-accent); display:grid; place-items:center;
94
- box-shadow:0 8px 20px color-mix(in srgb, var(--signal) 40%, transparent);
95
- transition:transform .18s; cursor:pointer; border:0; font:inherit;
96
- }
97
- html[data-design="orbit"] .ob-hub:hover { transform:scale(1.06); }
98
- html[data-design="orbit"] .ob-hub .ob-pct { font-family:var(--display); font-size:12px; font-weight:700; letter-spacing:-.02em; }
99
- html[data-design="orbit"] .ob-hub::after {
100
- content:""; position:absolute; inset:-5px; border-radius:50%; border:2px solid var(--signal); opacity:.35;
101
- animation: ob-breathe 2.6s ease-in-out infinite;
102
+ position:relative; width:46px; height:46px; border-radius:50%; flex-shrink:0; padding:3px;
103
+ background:conic-gradient(var(--signal) calc(var(--ob-pct,0) * 1%), var(--surface-3) 0);
104
+ display:grid; place-items:center; box-shadow:0 6px 16px color-mix(in srgb, var(--signal) 28%, transparent);
105
+ transition:transform .18s,box-shadow .18s; cursor:pointer; border:0; font:inherit;
102
106
  }
103
- @keyframes ob-breathe { 0%,100%{ transform:scale(1); opacity:.35; } 50%{ transform:scale(1.08); opacity:.12; } }
107
+ html[data-design="orbit"] .ob-hub::before {
108
+ content:""; position:absolute; inset:3px; border-radius:50%; background:var(--surface); z-index:0;
109
+ }
110
+ /* No `display` here: the theme-toggle rule (.brand-logo-img.is-dark/.is-light in styles.css) must stay
111
+ in control of which of the two cloned <img>s is shown — an explicit display:block on this class
112
+ used to outrank it (higher specificity) and forced BOTH clones visible at once, a ghosted double
113
+ logo most visible in dark mode (the two assets differ more there than in light mode). */
114
+ html[data-design="orbit"] .ob-hub .ob-hub-logo { position:relative; z-index:1; height:21px; width:auto; }
115
+ html[data-design="orbit"] .ob-hub:hover { transform:scale(1.07); box-shadow:0 8px 20px color-mix(in srgb, var(--signal) 38%, transparent); }
104
116
 
105
117
  /* ---- fixed bottom-left FAB on small screens ---- */
106
118
  @media (max-width:900px) {
107
- html[data-design="orbit"] .ob-hub { position:fixed; left:16px; bottom:18px; z-index:42; width:56px; height:56px; }
108
- html[data-design="orbit"] .ob-hub .ob-pct { font-size:14px; }
119
+ html[data-design="orbit"] .ob-hub { position:fixed; left:16px; bottom:18px; z-index:42; width:56px; height:56px; padding:4px; }
120
+ html[data-design="orbit"] .ob-hub .ob-hub-logo { height:25px; }
109
121
  }
110
122
 
111
123
  /* ============================================================================================
@@ -149,21 +161,26 @@ html[data-design="orbit"] .ob-chev.ob-left:hover { transform:translateY(-50%) sc
149
161
  html[data-design="orbit"] .ob-chev.ob-right { top:50%; right:-4px; transform:translateY(-50%); }
150
162
  html[data-design="orbit"] .ob-chev.ob-right:hover { transform:translateY(-50%) scale(1.12); }
151
163
 
164
+ /* The dial's center repeats the brand mark alone — the same logo as the hub, at a size that makes it
165
+ the clear focal point of the whole dial, so identity carries through from closed button to open
166
+ menu. The % is already encoded by the ring around it (.ob-prog); repeating it as text here would
167
+ only compete with the logo instead of framing it. */
152
168
  html[data-design="orbit"] .ob-center {
153
169
  position:absolute; left:50%; top:50%; width:76px; height:76px; transform:translate(-50%,-50%);
154
- border-radius:50%; display:grid; place-items:center; background:var(--signal); color:var(--on-accent);
155
- box-shadow:0 10px 24px color-mix(in srgb, var(--signal) 40%, transparent); text-align:center; line-height:1;
170
+ border-radius:50%; display:grid; place-items:center;
171
+ background:var(--signal); color:var(--on-accent);
172
+ box-shadow:0 10px 24px color-mix(in srgb, var(--signal) 40%, transparent);
156
173
  border:0; cursor:pointer;
157
174
  }
158
- html[data-design="orbit"] .ob-center .ob-pct { font-family:var(--display); font-size:22px; font-weight:700; letter-spacing:-.03em; }
159
- html[data-design="orbit"] .ob-center .ob-lbl { font:500 9.5px var(--mono); opacity:.85; margin-top:2px; letter-spacing:.06em; text-transform:uppercase; }
175
+ html[data-design="orbit"] .ob-center .ob-center-logo { height:34px; width:auto; }
160
176
  html[data-design="orbit"] .ob-center::after {
161
177
  content:""; position:absolute; inset:-6px; border-radius:50%; border:2px solid var(--signal); opacity:.35;
162
178
  animation: ob-breathe 2.6s ease-in-out infinite;
163
179
  }
180
+ @keyframes ob-breathe { 0%,100%{ transform:scale(1); opacity:.35; } 50%{ transform:scale(1.08); opacity:.12; } }
164
181
  @media (max-width:900px) {
165
182
  html[data-design="orbit"] .ob-center { width:60px; height:60px; }
166
- html[data-design="orbit"] .ob-center .ob-pct { font-size:17px; }
183
+ html[data-design="orbit"] .ob-center .ob-center-logo { height:27px; }
167
184
  }
168
185
 
169
186
  html[data-design="orbit"] .ob-item {
@@ -20,7 +20,21 @@
20
20
  return '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="' + d + '"/></svg>';
21
21
  }
22
22
 
23
- /* ---- hub button (injected into .brand) ---- */
23
+ /* ---- the brand mark, reused wherever Orbit needs a logo ----
24
+ Clones the header's own theme-aware <img> pair (.brand-logo-img.is-dark/.is-light) rather than
25
+ hardcoding a filename, so a custom-uploaded logo or a future asset change is picked up for free.
26
+ The light/dark swap rule is written purely against the shared class names (no ancestor scoping),
27
+ so it keeps working no matter where these clones end up (hub button, dial center). */
28
+ function logoImgsHtml(extraClass) {
29
+ var imgs = document.querySelectorAll('.brand > .brand-logo .brand-logo-img');
30
+ if (!imgs.length) return '';
31
+ return Array.prototype.map.call(imgs, function (img) {
32
+ var variant = img.className.replace('brand-logo-img', '').trim();
33
+ return '<img class="brand-logo-img' + (variant ? ' ' + variant : '') + (extraClass ? ' ' + extraClass : '') + '" src="' + img.getAttribute('src') + '" alt="" />';
34
+ }).join('');
35
+ }
36
+
37
+ /* ---- hub button (injected into .brand) — the logo itself, ringed with live progress ---- */
24
38
  function buildHub() {
25
39
  if (hubEl) return;
26
40
  var brand = document.querySelector('.brand');
@@ -30,21 +44,28 @@
30
44
  hubEl.className = 'ob-hub';
31
45
  hubEl.title = 'Menu (M)';
32
46
  hubEl.setAttribute('aria-haspopup', 'dialog');
33
- hubEl.innerHTML = '<span class="ob-pct">0%</span>';
47
+ hubEl.setAttribute('aria-label', 'Open menu');
48
+ hubEl.innerHTML = logoImgsHtml('ob-hub-logo');
34
49
  brand.insertBefore(hubEl, brand.firstChild);
35
50
  hubEl.addEventListener('click', toggleOverlay);
36
51
  }
37
52
  function removeHub() { if (hubEl) { hubEl.remove(); hubEl = null; } }
38
53
 
54
+ /* ---- "spectoflow" text next to the hub opens the dashboard (the hub itself opens the menu) ---- */
55
+ function onBrandNameClick() {
56
+ var board = tabs().filter(function (t) { return t.dataset.tab === 'board'; })[0];
57
+ if (board) board.click();
58
+ }
59
+
39
60
  /* ---- global progress % — read #globalMeterFill's width, kept live ---- */
40
61
  function updatePct() {
41
62
  var fill = document.getElementById('globalMeterFill');
42
63
  var pct = 0;
43
64
  if (fill && fill.style && fill.style.width) pct = parseFloat(fill.style.width) || 0;
44
65
  pct = Math.max(0, Math.min(100, Math.round(pct)));
45
- if (hubEl) { var hp = hubEl.querySelector('.ob-pct'); if (hp) hp.textContent = pct + '%'; }
66
+ if (hubEl) hubEl.style.setProperty('--ob-pct', pct); // drives the hub's conic-gradient ring
46
67
  if (ovEl) {
47
- var cp = ovEl.querySelector('.ob-center .ob-pct'); if (cp) cp.textContent = pct + '%';
68
+ // the center is the logo alone; progress is read from the ring around it, not repeated as text
48
69
  var prog = ovEl.querySelector('.ob-prog');
49
70
  if (prog) { var C = 804.2, len = (pct / 100 * C).toFixed(1); prog.setAttribute('stroke-dasharray', len + ' ' + C); }
50
71
  }
@@ -80,7 +101,7 @@
80
101
  '<button type="button" class="ob-chev ob-down" title="Next (Down)">' + chevSvg('down') + '</button>' +
81
102
  '<button type="button" class="ob-chev ob-left" title="Search / Board">' + chevSvg('left') + '</button>' +
82
103
  '<button type="button" class="ob-chev ob-right" title="Run">' + chevSvg('right') + '</button>' +
83
- '<button type="button" class="ob-center" title="Close (Esc)"><span class="ob-pct">0%</span><span class="ob-lbl">Delivered</span></button>' +
104
+ '<button type="button" class="ob-center" title="Close (Esc)" aria-label="Close menu">' + logoImgsHtml('ob-center-logo') + '</button>' +
84
105
  items +
85
106
  '</div>';
86
107
  document.body.appendChild(ovEl);
@@ -179,12 +200,16 @@
179
200
  fillObserver = new MutationObserver(updatePct);
180
201
  fillObserver.observe(fill, { attributes: true, attributeFilter: ['style'] });
181
202
  }
203
+ var bn = document.querySelector('.brand-name');
204
+ if (bn) bn.addEventListener('click', onBrandNameClick);
182
205
  }
183
206
  function leave() {
184
207
  closeOverlay();
185
208
  removeHub();
186
209
  if (pctInterval) { clearInterval(pctInterval); pctInterval = null; }
187
210
  if (fillObserver) { fillObserver.disconnect(); fillObserver = null; }
211
+ var bn = document.querySelector('.brand-name');
212
+ if (bn) bn.removeEventListener('click', onBrandNameClick);
188
213
  }
189
214
  function sync() {
190
215
  var on = isOrbit();