openvibers/openvibe.shared Commits
4.174.0: Home fills its width and gets an Edit button; the site's tab matches Feed and You (#394)
Changes
@@@ -1,5 +1,19 @@# Changelog++## 4.174.0 — 2026-11-09++++- **Home fills its width** (`ov-home.js`; the owner, 11-09: the icons were "offset and need to utilize more of the++ space"). The apps on the menu's Home grow with their cell up to 72 px (they were a fixed 56 px in a wider cell), the++ glyph is about half the tile, folders scale their four minis with the tile, and the grid lines up with the cards++ above it. The names are a touch larger and brighter.++- **An Edit button on Home** (`ov-home.js`). A small pencil beside "Your home" starts arranging: move apps, remove++ them, make folders. Press and hold still works. While arranging, the button is a Done pill with a check, and Home++ stays where it was: the hint is under the grid and the cards above it stay (quieted), so a long-press that turns into++ a drag never has the grid move under the finger.++- **The site's tab matches Feed and You** (`search.js`). The first tab of the menu showed the site's coloured app mark++ (a disc and ring around the glyph); it now shows the glyph alone, in the label's colour and at the other tabs'++ weight. On a phone the three tabs are tighter, so a long name like "Community" fits whole.++## 4.173.0 — 2026-11-09- **The wallet works on sites that keep the access cookie from page scripts** (`navbar.js`, `ov-wallet.js`). 4.170's@@@ -2,7 +2,7 @@> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.--**Status:** alpha, v4.173.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed++**Status:** alpha, v4.174.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployedconsumer installs a tagged release and none keeps a vendored copy([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` namesthe tag it pins.@@@ -4,7 +4,7 @@"code": true,"wave": "W2","package": "openvibe-shared",-- "version": "4.173.0",++ "version": "4.174.0","plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)","deployed": false,"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",@@@ -572,7 +572,7 @@const NAV_ICONS_REV = '1091daee9c7e'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)// END generated// The files loaded from beside this one, by content (scripts/sibling-revs.js): a release changes their URLs.-- const SIBLING_REV = { 'ov-mark.js': 'd7e87def8fff', 'ov-glyphs.js': '6f8c8dd2a10d', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2d3a47d3535c', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '20f4ef41d127', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': '8e55bfe93238', 'ov-share.css': 'cbd18e6ce991', 'search.js': '8d00547b413c', 'dock.js': '7ff462f31951', 'presence.js': '1b675c37d888', 'panels.js': 'd90bfe987d81', 'surfaces.js': 'e72f5a304f2d', 'notification-ui.js': 'e06f1388b696', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980', 'overlay.js': 'd18308ab8a63', 'ov-home.js': 'fddb428eb75a', 'nav-slots.js': 'a5af310d62e0', 'ov-wallet.js': '5c4d7fdb448d', 'ov-wallet.css': 'e46f653fa59e' };++ const SIBLING_REV = { 'ov-mark.js': 'd7e87def8fff', 'ov-glyphs.js': '6f8c8dd2a10d', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2d3a47d3535c', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '20f4ef41d127', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': '8e55bfe93238', 'ov-share.css': 'cbd18e6ce991', 'search.js': 'c65d87484b9c', 'dock.js': '7ff462f31951', 'presence.js': '1b675c37d888', 'panels.js': 'd90bfe987d81', 'surfaces.js': 'e72f5a304f2d', 'notification-ui.js': 'e06f1388b696', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980', 'overlay.js': 'd18308ab8a63', 'ov-home.js': '631efe800b13', 'nav-slots.js': 'a5af310d62e0', 'ov-wallet.js': '5c4d7fdb448d', 'ov-wallet.css': 'e46f653fa59e' };// The navbar's icons are filled paths (no Font Awesome CSS needed): the ones above ship here, the rest// (nav-icons.js) load after first paint when a link, chip or menu row names one, into an empty <svg> of their// size. Anything unknown, or nav-icons.js failing, falls back to <i class="fa-solid">.@@@ -57,6 +57,25 @@@media (min-width: 400px) { .ovh-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); } }@media (min-width: 861px) { .ovh-grid { grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); } }.ovh-grid { position: relative; }++/* Home fills its width (owner, 11-09: "offset … use more of the space"): the tile grows with its cell up to 72 px,++ its glyph is about half the tile, folders scale their four with it, and the grid lines up with the cards above. */++.ovh-grid:not(.ovh-recent) { gap: 4px 2px; padding: 2px 0 0; justify-items: stretch; }++.ovh-grid:not(.ovh-recent) .ovh-app { min-height: 0; padding: 6px 0 8px; gap: 7px; }++.ovh-grid:not(.ovh-recent) .ovh-ic { width: min(100% - 12px, 72px); height: auto; aspect-ratio: 1; border-radius: 29%; }++.ovh-grid:not(.ovh-recent) .ovh-app:not(.ovh-folder) .ovh-ic > .ov-icon { display: grid; place-items: center; width: 104%; height: 104%; }++.ovh-grid:not(.ovh-recent) .ovh-app:not(.ovh-folder) .ovh-ic > .ov-icon svg { width: 100%; height: 100%; }++.ovh-grid:not(.ovh-recent) .ovh-folder .ovh-ic { grid-template-columns: repeat(2, 36%); grid-auto-rows: 36%; gap: 7%; place-content: center; }++.ovh-grid:not(.ovh-recent) .ovh-folder .ovh-ic > span { width: 100%; height: 100%; border-radius: 30%; }++.ovh-grid:not(.ovh-recent) .ovh-folder .ovh-ic > span .ov-icon { display: grid; place-items: center; width: 100%; height: 100%; }++.ovh-grid:not(.ovh-recent) .ovh-folder .ovh-ic > span .ov-icon svg { width: 150%; height: 150%; }++.ovh-grid:not(.ovh-recent) .ovh-name { font-size: 12.5px; color: var(--text-primary, #e8ecf3); opacity: .86; }++.ovh-sec .ovh-edit { display: inline-grid; place-items: center; width: 36px; min-height: 36px; padding: 0; border-radius: 50%;++ background: color-mix(in srgb, var(--text-primary, #e8ecf3) 8%, transparent); color: var(--text-secondary, #a8b3c4); }++.ovh-sec .ovh-edit svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }++.ovh-sec .ovh-edit:hover { color: var(--text-primary, #e8ecf3); background: color-mix(in srgb, var(--text-primary, #e8ecf3) 13%, transparent); }++.ovh-sec .ovh-edit.is-done { display: inline-flex; gap: 6px; width: auto; padding: 0 14px 0 11px; border-radius: 999px; background: var(--accent-strong, var(--accent, #2563eb)); color: var(--on-accent-strong, #fff); }++.ovh-sec .ovh-edit:focus-visible { outline: 2px solid var(--accent, #3b82f6); outline-offset: 2px; }++@media (pointer: coarse) { .ovh-sec .ovh-edit { width: 44px; min-height: 44px; } .ovh-sec .ovh-edit.is-done { width: auto; } }.ovh.is-edit .ovh-grid:not(.ovh-recent) { touch-action: none; -webkit-user-select: none; user-select: none; }.ovh-recent .ovh-app { min-height: 80px; }.ovh-app { position: relative; display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 0; min-height: 88px; padding: 8px 2px 6px; border: 0; border-radius: 16px;@@@ -91,7 +110,9 @@ html[data-ov-motion=reduced] .ovh.is-edit .ovh-grid:not(.ovh-recent) .ovh-ic { a.ovh-ghost { position: fixed; z-index: 10040; pointer-events: none; width: 64px; height: 64px; margin: -32px 0 0 -32px; border-radius: 19px; display: grid; place-items: center;background: var(--bg-secondary, #141a24); box-shadow: 0 12px 30px rgba(0,0,0,.35), inset 0 0 0 1px var(--border, rgba(255,255,255,.14)); transform: scale(1.08); }@keyframes ovh-wiggle { from { transform: rotate(-1.6deg); } to { transform: rotate(1.6deg); } }--.ovh-hint { margin: 2px 6px 6px; font-size: 12.5px; color: var(--text-muted, #93a0b4); }++.ovh-hint { margin: 10px 6px 4px; font-size: 12.5px; color: var(--text-muted, #93a0b4); }++.ovh-calm { transition: opacity .2s; }++.ovh.is-edit .ovh-calm { opacity: .38; pointer-events: none; }.ovh-all[hidden], .ovh[hidden] { display: none !important; }.ovh-all { display: flex; align-items: center; justify-content: center; gap: 8px; width: calc(100% - 8px); min-height: 44px; margin: 6px 4px 2px; border: 1px solid var(--border, rgba(255,255,255,.12));border-radius: 14px; background: transparent; color: var(--text-primary, #e8ecf3); font: inherit; font-size: 14px; font-weight: 700; cursor: pointer; }@@@ -299,6 +320,8 @@ html[data-ov-motion=reduced] .ovh-card.is-live .ovh-ic { animation-duration: 4s;// Line icons for the buttons beside your name (24-unit boxes, optically centred), built as nodes.const ICON = {++ pencil: [['path', { d: 'M4 20h4L19 9a2.8 2.8 0 0 0-4-4L4 16z' }], ['path', { d: 'M13.5 6.5l4 4' }]],++ check: [['path', { d: 'M5 12.5l4.5 4.5L19 7.5' }]],look: [['path', { d: 'M12 3a9 9 0 1 0 0 18c1 0 1.7-.8 1.7-1.7 0-.5-.2-.9-.5-1.2-.3-.3-.4-.7-.4-1.1 0-1 .8-1.7 1.7-1.7h2.1A4.4 4.4 0 0 0 21 11c0-4.4-4-8-9-8Z' }],['circle', { cx: 7.6, cy: 11.4, r: 1.1, fill: 'currentColor', stroke: 'none' }], ['circle', { cx: 10.4, cy: 7.4, r: 1.1, fill: 'currentColor', stroke: 'none' }], ['circle', { cx: 15, cy: 7.6, r: 1.1, fill: 'currentColor', stroke: 'none' }]],gear: [['circle', { cx: 12, cy: 12, r: 3 }], ['path', { d: 'M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1Z' }]],@@@ -451,20 +474,28 @@ html[data-ov-motion=reduced] .ovh-card.is-live .ovh-ic { animation-duration: 4s;function paintHome() {el.textContent = '';el.classList.toggle('is-edit', state.edit);-- if (signedIn(o) && !state.edit) el.append(me());++ // Edit keeps everything where it was: a long-press goes straight on to drag the app under the finger, so++ // nothing above the grid may come or go (the macOS run of #394 dropped onto the wrong place when the hint++ // pushed the grid down mid-drag). The cards above stay, quiet and inert; the hint goes under the grid.++ const calm = (x) => { x.classList.add('ovh-calm'); x.inert = state.edit; return x; };++ if (signedIn(o)) el.append(calm(me()));if (signedIn(o)) { actorBox = actorBox || h('div'); el.append(actorBox); }-- if (!state.edit) { const b = backRail(); if (b) el.append(b); const u = usage(); if (u) el.append(u); }++ { const b = backRail(); if (b) el.append(calm(b)); const u = usage(); if (u) el.append(calm(u)); }const head = sec(L.arranged || signedIn(o) ? 'Your home' : 'Popular on OpenVibe');-- const edit = h('button', state.edit ? 'is-done' : '', state.edit ? 'Done' : 'Edit');++ const edit = h('button', state.edit ? 'ovh-edit is-done' : 'ovh-edit');edit.type = 'button';++ edit.append(svgIcon(state.edit ? 'check' : 'pencil'));++ if (state.edit) edit.append(h('span', '', 'Done'));++ edit.setAttribute('aria-label', state.edit ? 'Done arranging Home' : 'Edit Home');++ edit.title = state.edit ? 'Done' : 'Edit Home: move apps, remove them, add folders';edit.setAttribute('aria-pressed', state.edit ? 'true' : 'false');edit.addEventListener('click', () => setEdit(!state.edit));head.append(edit);el.append(head);-- if (state.edit) el.append(h('p', 'ovh-hint', 'Drag an app to move it, or use the arrow keys. − takes it off Home; All apps puts it back.'));const grid = h('div', 'ovh-grid'); grid.setAttribute('aria-label', 'Your home');L.home.forEach((id, i) => grid.append(homeTile(id, i)));el.append(grid);++ if (state.edit) el.append(h('p', 'ovh-hint', 'Drag an app to move it, or use the arrow keys. − takes it off Home; All apps puts it back.'));const total = L.byId.size;const all = h('button', 'ovh-all', `All apps (${total})`); all.type = 'button';all.addEventListener('click', () => { state.view = 'drawer'; paint(); });@@@ -1,12 +1,12 @@{"name": "openvibe-shared",-- "version": "4.173.0",++ "version": "4.174.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-shared",-- "version": "4.173.0",++ "version": "4.174.0","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "4.173.0",++ "version": "4.174.0","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","bin": {@@@ -227,6 +227,12 @@.ovs-starttabs .ovs-tab .ovs-tab-ic { position: relative; flex: none; display: grid; place-items: center; width: 22px; height: 22px; overflow: visible; }.ovs-tab-ic > svg { width: 20px; height: 20px; }.ovs-tab-ic .ov-icon { display: grid; place-items: center; }++/* The site's tab wears its glyph like Feed and You (owner, 11-09): no disc and no ring, the glyph at their weight and in++ the label's colour. The icon's box is drawn larger than the slot so the solid glyph itself is about 18 px. */++.ovs-tab-ic .ov-icon .ovi-bg, .ovs-tab-ic .ov-icon .ovi-ring, .ovs-tab-ic .ov-icon .ovi-comet, .ovs-tab-ic .ov-icon .ovi-prog { display: none; }++.ovs-tab-ic .ov-icon { --ovi-glyph: currentColor; color: currentColor; width: 22px; height: 22px; overflow: visible; }++.ovs-tab-ic .ov-icon svg { width: 40px; height: 40px; margin: -9px; flex: none; }++@media (max-width: 420px) { .ovs-starttabs .ovs-tab { padding: 0 7px; gap: 6px; font-size: 13.5px; } }.ovs-tab-av { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; display: block; box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--text-primary, #e6edf7) 18%, transparent); }.ovs-tab[aria-selected="true"] .ovs-tab-av { box-shadow: 0 0 0 2px var(--accent, #3b82f6); }.ovs-tab-on { position: absolute; right: -2px; bottom: -2px; width: 8px; height: 8px; border-radius: 50%; background: var(--success, #22c55e); box-shadow: 0 0 0 2px var(--bg-secondary, #111826); }
@@ -1,5 +1,19 @@
@@ -1,5 +1,19 @@
# Changelog
# Changelog
+## 4.174.0 — 2026-11-09
+
+- **Home fills its width** (`ov-home.js`; the owner, 11-09: the icons were "offset and need to utilize more of the
+ space"). The apps on the menu's Home grow with their cell up to 72 px (they were a fixed 56 px in a wider cell), the
+ glyph is about half the tile, folders scale their four minis with the tile, and the grid lines up with the cards
+ above it. The names are a touch larger and brighter.
+- **An Edit button on Home** (`ov-home.js`). A small pencil beside "Your home" starts arranging: move apps, remove
+ them, make folders. Press and hold still works. While arranging, the button is a Done pill with a check, and Home
+ stays where it was: the hint is under the grid and the cards above it stay (quieted), so a long-press that turns into
+ a drag never has the grid move under the finger.
+- **The site's tab matches Feed and You** (`search.js`). The first tab of the menu showed the site's coloured app mark
+ (a disc and ring around the glyph); it now shows the glyph alone, in the label's colour and at the other tabs'
+ weight. On a phone the three tabs are tighter, so a long name like "Community" fits whole.
+
## 4.173.0 — 2026-11-09
## 4.173.0 — 2026-11-09
- **The wallet works on sites that keep the access cookie from page scripts** (`navbar.js`, `ov-wallet.js`). 4.170's
- **The wallet works on sites that keep the access cookie from page scripts** (`navbar.js`, `ov-wallet.js`). 4.170's
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
-**Status:** alpha, v4.173.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
+**Status:** alpha, v4.174.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
consumer installs a tagged release and none keeps a vendored copy
consumer installs a tagged release and none keeps a vendored copy
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
the tag it pins.
the tag it pins.
@@ -4,7 +4,7 @@
@@ -4,7 +4,7 @@
"code": true,
"code": true,
"wave": "W2",
"wave": "W2",
"package": "openvibe-shared",
"package": "openvibe-shared",
- "version": "4.173.0",
+ "version": "4.174.0",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"deployed": false,
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
@@ -572,7 +572,7 @@
@@ -572,7 +572,7 @@
const NAV_ICONS_REV = '1091daee9c7e'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
const NAV_ICONS_REV = '1091daee9c7e'; // sha256(nav-icons.js), first 12 hex: its ?v= (cacheable forever)
// END generated
// END generated
// The files loaded from beside this one, by content (scripts/sibling-revs.js): a release changes their URLs.
// The files loaded from beside this one, by content (scripts/sibling-revs.js): a release changes their URLs.
- const SIBLING_REV = { 'ov-mark.js': 'd7e87def8fff', 'ov-glyphs.js': '6f8c8dd2a10d', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2d3a47d3535c', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '20f4ef41d127', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': '8e55bfe93238', 'ov-share.css': 'cbd18e6ce991', 'search.js': '8d00547b413c', 'dock.js': '7ff462f31951', 'presence.js': '1b675c37d888', 'panels.js': 'd90bfe987d81', 'surfaces.js': 'e72f5a304f2d', 'notification-ui.js': 'e06f1388b696', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980', 'overlay.js': 'd18308ab8a63', 'ov-home.js': 'fddb428eb75a', 'nav-slots.js': 'a5af310d62e0', 'ov-wallet.js': '5c4d7fdb448d', 'ov-wallet.css': 'e46f653fa59e' };
+ const SIBLING_REV = { 'ov-mark.js': 'd7e87def8fff', 'ov-glyphs.js': '6f8c8dd2a10d', 'ov-brand.woff2': '3b4e6a8a5878', 'ov-look.js': '2d3a47d3535c', 'ov-fx.js': '46fce1e2b8db', 'ov-labels.js': 'a30b178391f5', 'ov-forms.js': '20f4ef41d127', 'ov-forms.css': '98cf0f28bb68', 'ov-share.js': '8e55bfe93238', 'ov-share.css': 'cbd18e6ce991', 'search.js': 'c65d87484b9c', 'dock.js': '7ff462f31951', 'presence.js': '1b675c37d888', 'panels.js': 'd90bfe987d81', 'surfaces.js': 'e72f5a304f2d', 'notification-ui.js': 'e06f1388b696', 'history.js': '1e6598f78ba4', 'site-switcher.js': '292bea111aa8', 'appbar.js': '1edf7532f228', 'release-watch.js': '25f02a3f0684', 'ov-icons.js': 'ba3981246bc0', 'sso-client.js': '52439265b980', 'overlay.js': 'd18308ab8a63', 'ov-home.js': '631efe800b13', 'nav-slots.js': 'a5af310d62e0', 'ov-wallet.js': '5c4d7fdb448d', 'ov-wallet.css': 'e46f653fa59e' };
// The navbar's icons are filled paths (no Font Awesome CSS needed): the ones above ship here, the rest
// The navbar's icons are filled paths (no Font Awesome CSS needed): the ones above ship here, the rest
// (nav-icons.js) load after first paint when a link, chip or menu row names one, into an empty <svg> of their
// (nav-icons.js) load after first paint when a link, chip or menu row names one, into an empty <svg> of their
// size. Anything unknown, or nav-icons.js failing, falls back to <i class="fa-solid">.
// size. Anything unknown, or nav-icons.js failing, falls back to <i class="fa-solid">.
@@ -57,6 +57,25 @@
@@ -57,6 +57,25 @@
@media (min-width: 400px) { .ovh-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
@media (min-width: 400px) { .ovh-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
@media (min-width: 861px) { .ovh-grid { grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); } }
@media (min-width: 861px) { .ovh-grid { grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); } }
.ovh-grid { position: relative; }
.ovh-grid { position: relative; }
+/* Home fills its width (owner, 11-09: "offset … use more of the space"): the tile grows with its cell up to 72 px,
+ its glyph is about half the tile, folders scale their four with it, and the grid lines up with the cards above. */
+.ovh-grid:not(.ovh-recent) { gap: 4px 2px; padding: 2px 0 0; justify-items: stretch; }
+.ovh-grid:not(.ovh-recent) .ovh-app { min-height: 0; padding: 6px 0 8px; gap: 7px; }
+.ovh-grid:not(.ovh-recent) .ovh-ic { width: min(100% - 12px, 72px); height: auto; aspect-ratio: 1; border-radius: 29%; }
+.ovh-grid:not(.ovh-recent) .ovh-app:not(.ovh-folder) .ovh-ic > .ov-icon { display: grid; place-items: center; width: 104%; height: 104%; }
+.ovh-grid:not(.ovh-recent) .ovh-app:not(.ovh-folder) .ovh-ic > .ov-icon svg { width: 100%; height: 100%; }
+.ovh-grid:not(.ovh-recent) .ovh-folder .ovh-ic { grid-template-columns: repeat(2, 36%); grid-auto-rows: 36%; gap: 7%; place-content: center; }
+.ovh-grid:not(.ovh-recent) .ovh-folder .ovh-ic > span { width: 100%; height: 100%; border-radius: 30%; }
+.ovh-grid:not(.ovh-recent) .ovh-folder .ovh-ic > span .ov-icon { display: grid; place-items: center; width: 100%; height: 100%; }
+.ovh-grid:not(.ovh-recent) .ovh-folder .ovh-ic > span .ov-icon svg { width: 150%; height: 150%; }
+.ovh-grid:not(.ovh-recent) .ovh-name { font-size: 12.5px; color: var(--text-primary, #e8ecf3); opacity: .86; }
+.ovh-sec .ovh-edit { display: inline-grid; place-items: center; width: 36px; min-height: 36px; padding: 0; border-radius: 50%;
+ background: color-mix(in srgb, var(--text-primary, #e8ecf3) 8%, transparent); color: var(--text-secondary, #a8b3c4); }
+.ovh-sec .ovh-edit svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
+.ovh-sec .ovh-edit:hover { color: var(--text-primary, #e8ecf3); background: color-mix(in srgb, var(--text-primary, #e8ecf3) 13%, transparent); }
+.ovh-sec .ovh-edit.is-done { display: inline-flex; gap: 6px; width: auto; padding: 0 14px 0 11px; border-radius: 999px; background: var(--accent-strong, var(--accent, #2563eb)); color: var(--on-accent-strong, #fff); }
+.ovh-sec .ovh-edit:focus-visible { outline: 2px solid var(--accent, #3b82f6); outline-offset: 2px; }
+@media (pointer: coarse) { .ovh-sec .ovh-edit { width: 44px; min-height: 44px; } .ovh-sec .ovh-edit.is-done { width: auto; } }
.ovh.is-edit .ovh-grid:not(.ovh-recent) { touch-action: none; -webkit-user-select: none; user-select: none; }
.ovh.is-edit .ovh-grid:not(.ovh-recent) { touch-action: none; -webkit-user-select: none; user-select: none; }
.ovh-recent .ovh-app { min-height: 80px; }
.ovh-recent .ovh-app { min-height: 80px; }
.ovh-app { position: relative; display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 0; min-height: 88px; padding: 8px 2px 6px; border: 0; border-radius: 16px;
.ovh-app { position: relative; display: flex; flex-direction: column; align-items: center; gap: 6px; min-width: 0; min-height: 88px; padding: 8px 2px 6px; border: 0; border-radius: 16px;
@@ -91,7 +110,9 @@ html[data-ov-motion=reduced] .ovh.is-edit .ovh-grid:not(.ovh-recent) .ovh-ic { a
@@ -91,7 +110,9 @@ html[data-ov-motion=reduced] .ovh.is-edit .ovh-grid:not(.ovh-recent) .ovh-ic { a
.ovh-ghost { position: fixed; z-index: 10040; pointer-events: none; width: 64px; height: 64px; margin: -32px 0 0 -32px; border-radius: 19px; display: grid; place-items: center;
.ovh-ghost { position: fixed; z-index: 10040; pointer-events: none; width: 64px; height: 64px; margin: -32px 0 0 -32px; border-radius: 19px; display: grid; place-items: center;
background: var(--bg-secondary, #141a24); box-shadow: 0 12px 30px rgba(0,0,0,.35), inset 0 0 0 1px var(--border, rgba(255,255,255,.14)); transform: scale(1.08); }
background: var(--bg-secondary, #141a24); box-shadow: 0 12px 30px rgba(0,0,0,.35), inset 0 0 0 1px var(--border, rgba(255,255,255,.14)); transform: scale(1.08); }
@keyframes ovh-wiggle { from { transform: rotate(-1.6deg); } to { transform: rotate(1.6deg); } }
@keyframes ovh-wiggle { from { transform: rotate(-1.6deg); } to { transform: rotate(1.6deg); } }
-.ovh-hint { margin: 2px 6px 6px; font-size: 12.5px; color: var(--text-muted, #93a0b4); }
+.ovh-hint { margin: 10px 6px 4px; font-size: 12.5px; color: var(--text-muted, #93a0b4); }
+.ovh-calm { transition: opacity .2s; }
+.ovh.is-edit .ovh-calm { opacity: .38; pointer-events: none; }
.ovh-all[hidden], .ovh[hidden] { display: none !important; }
.ovh-all[hidden], .ovh[hidden] { display: none !important; }
.ovh-all { display: flex; align-items: center; justify-content: center; gap: 8px; width: calc(100% - 8px); min-height: 44px; margin: 6px 4px 2px; border: 1px solid var(--border, rgba(255,255,255,.12));
.ovh-all { display: flex; align-items: center; justify-content: center; gap: 8px; width: calc(100% - 8px); min-height: 44px; margin: 6px 4px 2px; border: 1px solid var(--border, rgba(255,255,255,.12));
border-radius: 14px; background: transparent; color: var(--text-primary, #e8ecf3); font: inherit; font-size: 14px; font-weight: 700; cursor: pointer; }
border-radius: 14px; background: transparent; color: var(--text-primary, #e8ecf3); font: inherit; font-size: 14px; font-weight: 700; cursor: pointer; }
@@ -299,6 +320,8 @@ html[data-ov-motion=reduced] .ovh-card.is-live .ovh-ic { animation-duration: 4s;
@@ -299,6 +320,8 @@ html[data-ov-motion=reduced] .ovh-card.is-live .ovh-ic { animation-duration: 4s;
// Line icons for the buttons beside your name (24-unit boxes, optically centred), built as nodes.
// Line icons for the buttons beside your name (24-unit boxes, optically centred), built as nodes.
const ICON = {
const ICON = {
+ pencil: [['path', { d: 'M4 20h4L19 9a2.8 2.8 0 0 0-4-4L4 16z' }], ['path', { d: 'M13.5 6.5l4 4' }]],
+ check: [['path', { d: 'M5 12.5l4.5 4.5L19 7.5' }]],
look: [['path', { d: 'M12 3a9 9 0 1 0 0 18c1 0 1.7-.8 1.7-1.7 0-.5-.2-.9-.5-1.2-.3-.3-.4-.7-.4-1.1 0-1 .8-1.7 1.7-1.7h2.1A4.4 4.4 0 0 0 21 11c0-4.4-4-8-9-8Z' }],
look: [['path', { d: 'M12 3a9 9 0 1 0 0 18c1 0 1.7-.8 1.7-1.7 0-.5-.2-.9-.5-1.2-.3-.3-.4-.7-.4-1.1 0-1 .8-1.7 1.7-1.7h2.1A4.4 4.4 0 0 0 21 11c0-4.4-4-8-9-8Z' }],
['circle', { cx: 7.6, cy: 11.4, r: 1.1, fill: 'currentColor', stroke: 'none' }], ['circle', { cx: 10.4, cy: 7.4, r: 1.1, fill: 'currentColor', stroke: 'none' }], ['circle', { cx: 15, cy: 7.6, r: 1.1, fill: 'currentColor', stroke: 'none' }]],
['circle', { cx: 7.6, cy: 11.4, r: 1.1, fill: 'currentColor', stroke: 'none' }], ['circle', { cx: 10.4, cy: 7.4, r: 1.1, fill: 'currentColor', stroke: 'none' }], ['circle', { cx: 15, cy: 7.6, r: 1.1, fill: 'currentColor', stroke: 'none' }]],
gear: [['circle', { cx: 12, cy: 12, r: 3 }], ['path', { d: 'M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1Z' }]],
gear: [['circle', { cx: 12, cy: 12, r: 3 }], ['path', { d: 'M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1Z' }]],
@@ -451,20 +474,28 @@ html[data-ov-motion=reduced] .ovh-card.is-live .ovh-ic { animation-duration: 4s;
@@ -451,20 +474,28 @@ html[data-ov-motion=reduced] .ovh-card.is-live .ovh-ic { animation-duration: 4s;
function paintHome() {
function paintHome() {
el.textContent = '';
el.textContent = '';
el.classList.toggle('is-edit', state.edit);
el.classList.toggle('is-edit', state.edit);
- if (signedIn(o) && !state.edit) el.append(me());
+ // Edit keeps everything where it was: a long-press goes straight on to drag the app under the finger, so
+ // nothing above the grid may come or go (the macOS run of #394 dropped onto the wrong place when the hint
+ // pushed the grid down mid-drag). The cards above stay, quiet and inert; the hint goes under the grid.
+ const calm = (x) => { x.classList.add('ovh-calm'); x.inert = state.edit; return x; };
+ if (signedIn(o)) el.append(calm(me()));
if (signedIn(o)) { actorBox = actorBox || h('div'); el.append(actorBox); }
if (signedIn(o)) { actorBox = actorBox || h('div'); el.append(actorBox); }
- if (!state.edit) { const b = backRail(); if (b) el.append(b); const u = usage(); if (u) el.append(u); }
+ { const b = backRail(); if (b) el.append(calm(b)); const u = usage(); if (u) el.append(calm(u)); }
const head = sec(L.arranged || signedIn(o) ? 'Your home' : 'Popular on OpenVibe');
const head = sec(L.arranged || signedIn(o) ? 'Your home' : 'Popular on OpenVibe');
- const edit = h('button', state.edit ? 'is-done' : '', state.edit ? 'Done' : 'Edit');
+ const edit = h('button', state.edit ? 'ovh-edit is-done' : 'ovh-edit');
edit.type = 'button';
edit.type = 'button';
+ edit.append(svgIcon(state.edit ? 'check' : 'pencil'));
+ if (state.edit) edit.append(h('span', '', 'Done'));
+ edit.setAttribute('aria-label', state.edit ? 'Done arranging Home' : 'Edit Home');
+ edit.title = state.edit ? 'Done' : 'Edit Home: move apps, remove them, add folders';
edit.setAttribute('aria-pressed', state.edit ? 'true' : 'false');
edit.setAttribute('aria-pressed', state.edit ? 'true' : 'false');
edit.addEventListener('click', () => setEdit(!state.edit));
edit.addEventListener('click', () => setEdit(!state.edit));
head.append(edit);
head.append(edit);
el.append(head);
el.append(head);
- if (state.edit) el.append(h('p', 'ovh-hint', 'Drag an app to move it, or use the arrow keys. − takes it off Home; All apps puts it back.'));
const grid = h('div', 'ovh-grid'); grid.setAttribute('aria-label', 'Your home');
const grid = h('div', 'ovh-grid'); grid.setAttribute('aria-label', 'Your home');
L.home.forEach((id, i) => grid.append(homeTile(id, i)));
L.home.forEach((id, i) => grid.append(homeTile(id, i)));
el.append(grid);
el.append(grid);
+ if (state.edit) el.append(h('p', 'ovh-hint', 'Drag an app to move it, or use the arrow keys. − takes it off Home; All apps puts it back.'));
const total = L.byId.size;
const total = L.byId.size;
const all = h('button', 'ovh-all', `All apps (${total})`); all.type = 'button';
const all = h('button', 'ovh-all', `All apps (${total})`); all.type = 'button';
all.addEventListener('click', () => { state.view = 'drawer'; paint(); });
all.addEventListener('click', () => { state.view = 'drawer'; paint(); });
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.173.0",
+ "version": "4.174.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.173.0",
+ "version": "4.174.0",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.173.0",
+ "version": "4.174.0",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"main": "index.js",
"bin": {
"bin": {
@@ -227,6 +227,12 @@
@@ -227,6 +227,12 @@
.ovs-starttabs .ovs-tab .ovs-tab-ic { position: relative; flex: none; display: grid; place-items: center; width: 22px; height: 22px; overflow: visible; }
.ovs-starttabs .ovs-tab .ovs-tab-ic { position: relative; flex: none; display: grid; place-items: center; width: 22px; height: 22px; overflow: visible; }
.ovs-tab-ic > svg { width: 20px; height: 20px; }
.ovs-tab-ic > svg { width: 20px; height: 20px; }
.ovs-tab-ic .ov-icon { display: grid; place-items: center; }
.ovs-tab-ic .ov-icon { display: grid; place-items: center; }
+/* The site's tab wears its glyph like Feed and You (owner, 11-09): no disc and no ring, the glyph at their weight and in
+ the label's colour. The icon's box is drawn larger than the slot so the solid glyph itself is about 18 px. */
+.ovs-tab-ic .ov-icon .ovi-bg, .ovs-tab-ic .ov-icon .ovi-ring, .ovs-tab-ic .ov-icon .ovi-comet, .ovs-tab-ic .ov-icon .ovi-prog { display: none; }
+.ovs-tab-ic .ov-icon { --ovi-glyph: currentColor; color: currentColor; width: 22px; height: 22px; overflow: visible; }
+.ovs-tab-ic .ov-icon svg { width: 40px; height: 40px; margin: -9px; flex: none; }
+@media (max-width: 420px) { .ovs-starttabs .ovs-tab { padding: 0 7px; gap: 6px; font-size: 13.5px; } }
.ovs-tab-av { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; display: block; box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--text-primary, #e6edf7) 18%, transparent); }
.ovs-tab-av { width: 22px; height: 22px; border-radius: 50%; object-fit: cover; display: block; box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--text-primary, #e6edf7) 18%, transparent); }
.ovs-tab[aria-selected="true"] .ovs-tab-av { box-shadow: 0 0 0 2px var(--accent, #3b82f6); }
.ovs-tab[aria-selected="true"] .ovs-tab-av { box-shadow: 0 0 0 2px var(--accent, #3b82f6); }
.ovs-tab-on { position: absolute; right: -2px; bottom: -2px; width: 8px; height: 8px; border-radius: 50%; background: var(--success, #22c55e); box-shadow: 0 0 0 2px var(--bg-secondary, #111826); }
.ovs-tab-on { position: absolute; right: -2px; bottom: -2px; width: 8px; height: 8px; border-radius: 50%; background: var(--success, #22c55e); box-shadow: 0 0 0 2px var(--bg-secondary, #111826); }