openvibers/openvibe.shared Commits
4.107.2: the last small touch targets in the shared bar and footer reach 44px (#278)
Changes
@@@ -4,6 +4,19 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semvebreaking change to any exported module, browser global or served file name is a new major.A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).++## 4.107.2 — 2026-11-03++++- **The last small touch targets in the shared bar and footer reach 44px.** A re-run of the tap audit over every++ site's home (11-03: 31 of 45 sites with a small target, down from 40 of 42 on 10-29) found these:++ - Sign In was 40px tall; its invisible hit area now reaches 45px;++ - the OpenVibe menu's orb was 36.9px wide on a phone; it grows to the right, toward the open bar, so it measures++ 44px while the site's name keeps the taps on its own last letters;++ - the footer's brand was 33px tall, now 45px;++ - the orbit's big nodes on a phone were 42px, now 44px.++ Nothing is drawn differently. `test/tap-areas-chrome.test.js` measures each to a finger with++ `elementFromPoint`, from 320 to 430px, signed in and out, and checks that the name and the right-hand group keep++ their own taps.++## 4.107.1 — 2026-11-03- **Legal pages draw the brand in its own font on every site.** `legal.page()` loaded the theme loader, the navbar and@@@ -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.107.1 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed++**Status:** alpha, v4.107.2 (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.107.1",++ "version": "4.107.2","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)",@@@ -384,8 +384,9 @@.ovf-col a:hover{color:var(--accent,#60a5fa);transform:translateX(2px)}@media (pointer:coarse){.ovf-col a{display:inline-flex;align-items:center;min-height:32px}.ovf-col .ovf-social a{width:44px;height:44px}.ovf-legal-inline a,.ovf-compact-links a{position:relative}.ovf-legal-inline a::after,.ovf-compact-links a::after{content:'';position:absolute;left:0;right:0;top:-12px;bottom:-12px}}.ovf-more{font-weight:600;opacity:.85}.ovf-ic{flex:none;vertical-align:-.14em}.ovf-more .ovf-ic{font-size:.85em;margin-left:3px;transition:transform .15s}.ovf-more:hover .ovf-ic{transform:translateX(3px)}.ovf-social .ovf-ic{font-size:17px}.ovf-account-links .ovf-ic{font-size:1.05em;opacity:.85}--.ovf-brand{display:inline-flex;align-items:center;gap:9px;color:var(--text-primary,#f1f4fb);font-family:'OV Brand',Nunito,Inter,-apple-system,BlinkMacSystemFont,sans-serif;font-size:1.12rem;font-weight:800;letter-spacing:-.1px;text-decoration:none}++.ovf-brand{position:relative;display:inline-flex;align-items:center;gap:9px;color:var(--text-primary,#f1f4fb);font-family:'OV Brand',Nunito,Inter,-apple-system,BlinkMacSystemFont,sans-serif;font-size:1.12rem;font-weight:800;letter-spacing:-.1px;text-decoration:none}.ovf-brand--sm{font-size:.9rem}++.ovf-brand::after{content:'';position:absolute;inset:-6px 0}.ovf-tagline{margin:0;font-size:.82rem;line-height:1.55;color:var(--text-muted,#8b93ad);max-width:46ch}.ovf-social{display:flex;gap:8px;margin-top:4px}.ovf-social a{width:36px;height:36px;border-radius:10px;display:grid;place-items:center;color:var(--text-secondary,#b6bdd2);@@@ -77,10 +77,13 @@.openvibe-navbar .openvibe-navbar-brand .flame::after { content: ''; position: absolute; inset: -8px; }/* 4.46.0 (the fleet tap audit): Sign In, the menu and the orb reach 44px to a finger, still drawn as before. */.openvibe-navbar .openvibe-navbar-login, .openvibe-navbar .ovnav-burger, .openvibe-navbar .ovnav-orb { position: relative; }-- .openvibe-navbar .openvibe-navbar-login::after { content: ''; position: absolute; inset: -6px 0; }++ /* 4.107.2 (the 11-03 re-audit): Sign In measured 40px tall; 9px above and below makes 45. */++ .openvibe-navbar .openvibe-navbar-login::after { content: ''; position: absolute; inset: -9px 0; }.openvibe-navbar .ovnav-burger::after { content: ''; position: absolute; inset: -3px -5px; }-- /* The orb grows up and down only a little sideways: the site's name sits 8px from it and keeps its own taps. */-- .openvibe-navbar .ovnav-orb::after { content: ''; position: absolute; inset: -6px -4px; }++ /* The orb grows up and down, and sideways mostly to the right: the site's name sits 8px to its left and keeps++ its own taps (4px there); the extra width goes to the open bar on its right (12px), so it measures 44px++ wide on a phone (it was 36.9 in the 11-03 re-audit). */++ .openvibe-navbar .ovnav-orb::after { content: ''; position: absolute; inset: -6px -12px -6px -4px; }}.openvibe-navbar .ovnav-dd { position: relative; display: inline-flex; flex: none; }.openvibe-navbar .ovnav-dd-menu { display: none; position: fixed; top: 56px; left: 0; min-width: 180px; padding: 5px; border-radius: 12px; z-index: 1002; background: var(--bg-elevated, var(--bg-secondary, #111826)); border: 1px solid var(--border, rgba(255,255,255,.12)); box-shadow: 0 18px 44px rgba(0,0,0,.45); flex-direction: column; gap: 2px; }@@@ -1,12 +1,12 @@{"name": "openvibe-shared",-- "version": "4.107.1",++ "version": "4.107.2","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-shared",-- "version": "4.107.1",++ "version": "4.107.2","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "4.107.1",++ "version": "4.107.2","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","bin": {@@@ -33,7 +33,7 @@ html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glow.sc-hero.sc-has-orbit .sc-hero-vis{order:-1;position:static;width:auto;opacity:1;pointer-events:auto;margin:0 0 6px}.sc-hero.sc-has-orbit .sc-vis{width:min(70vw,250px)}.sc-hero.sc-has-orbit .sc-orb-core .sc-vis-mark{width:50px!important;height:50px!important}--.sc-orb-node{width:32px;height:32px;margin:-16px}.sc-orb-node.is-big{width:42px;height:42px;margin:-21px}.sc-orb-name{font-size:10.5px}++.sc-orb-node{width:32px;height:32px;margin:-16px}.sc-orb-node.is-big{width:44px;height:44px;margin:-22px}.sc-orb-name{font-size:10.5px}}.sc-hero,.sc-sec,.sc-cta{max-width:1080px;margin:0 auto;padding:0 24px;font-family:var(--ov-font,'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif)}@@@ -461,7 +461,7 @@ html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glow.sc-hero.sc-has-orbit .sc-hero-vis{order:-1;position:static;width:auto;opacity:1;pointer-events:auto;margin:0 0 6px}.sc-hero.sc-has-orbit .sc-vis{width:min(70vw,250px)}.sc-hero.sc-has-orbit .sc-orb-core .sc-vis-mark{width:50px!important;height:50px!important}--.sc-orb-node{width:32px;height:32px;margin:-16px}.sc-orb-node.is-big{width:42px;height:42px;margin:-21px}.sc-orb-name{font-size:10.5px}++.sc-orb-node{width:32px;height:32px;margin:-16px}.sc-orb-node.is-big{width:44px;height:44px;margin:-22px}.sc-orb-name{font-size:10.5px}}.sc-hero,.sc-sec,.sc-cta{max-width:1080px;margin:0 auto;padding:0 24px;font-family:var(--ov-font,'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif)}nnew file mode 100644@@@ -0,0 +1,99 @@++'use strict';++// Touch targets measured to a finger, in a real headless Chrome (4.107.2, the 11-03 re-audit of every site's home:++// tools/tap-audit.js). A target's effective size is the run of points around its centre that land on it++// (document.elementFromPoint), so an invisible ::after hit area counts and a neighbour that covers it does not.++// From 320px to 430px, signed in and out: Sign In and the OpenVibe menu's orb measure 44px or more each way, and the++// footer's brand 44px tall. The orb's extra width goes right, so the site's name keeps the taps on its own last++// letters, and nothing in the bar's right-hand group loses its own. Skipped without Chrome.++const assert = require('assert');++const fs = require('fs');++const os = require('os');++const path = require('path');++const http = require('http');++const h = require('../browser-harness');++++if (process.env.OV_SKIP_BROWSER === '1' || !h.findChrome()) {++ console.log('tap-areas-chrome: skipped (no Chrome; set CHROME_BIN)');++ process.exit(0);++}++++const ROOT = path.join(__dirname, '..');++const PAGE = (signedIn) => `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Live · OpenVibe.Live</title><link rel="icon" href="data:,"></head>++<body><div id="navbar-mount"></div><main style="min-height:40px"><h1>Live</h1></main><footer id="footer-mount"></footer>++<script>++ localStorage.setItem('ov-wallet:42', JSON.stringify({ coins: 161000, vibes: 400 }));++ window.fetch = async () => new Response('{}', { status: 404 });++</script>++<script src="/navbar.js"></script><script src="/footer.js"></script>++<script>++ OpenVibeNavbar.init({ service: 'live', brandName: 'OpenVibe.Live', wallet: true, messages: { href: 'https://openvibe.chat/messages' }, ${signedIn ? "token: 'x', user: { id: 42, username: 'moosely', display_name: 'moosely', avatar_url: null }," : 'user: null,'}++ links: [{ label: 'Home', href: '/', icon: 'fa-house' }] });++ OpenVibeFooter.init({ service: 'live', mount: '#footer-mount', variant: 'full' });++</script></body></html>`;++const server = http.createServer((req, res) => {++ const u = new URL(req.url, 'http://x');++ if (u.pathname === '/in' || u.pathname === '/out') { res.writeHead(200, { 'content-type': 'text/html' }); return res.end(PAGE(u.pathname === '/in')); }++ const name = u.pathname.slice(1);++ if (/^[a-z-]+\.js$/.test(name) && fs.existsSync(path.join(ROOT, name))) { res.writeHead(200, { 'content-type': 'application/javascript' }); return res.end(fs.readFileSync(path.join(ROOT, name))); }++ res.writeHead(404); return res.end();++});++++// The effective size of a target: the longest run of points through its centre, 1px apart, that land on it.++const MEASURE = `(() => {++ const on = (el, x, y) => { const hit = document.elementFromPoint(x, y); return !!hit && (hit === el || el.contains(hit)); };++ const size = (el) => {++ if (!el) return null;++ el.scrollIntoView({ block: 'center' });++ const r = el.getBoundingClientRect();++ const cx = r.left + r.width / 2, cy = r.top + r.height / 2;++ const run = (dx, dy) => { let a = 0, b = 0; while (a < 60 && on(el, cx - (a + 1) * dx, cy - (a + 1) * dy)) a++; while (b < 60 && on(el, cx + (b + 1) * dx, cy + (b + 1) * dy)) b++; return a + b + 1; };++ return { w: run(1, 0), h: run(0, 1), box: [Math.round(r.left), Math.round(r.right)] };++ };++ const bar = document.querySelector('.openvibe-navbar');++ scrollTo(0, 0);++ const name = bar.querySelector('.openvibe-navbar-brand .name');++ const nameLinks = [...name.querySelectorAll('a')];++ const lastLink = nameLinks[nameLinks.length - 1];++ const lr = lastLink.getBoundingClientRect();++ // At 320px the name is cut short (it reads whole from 360px): its last link ends where the name's box does.++ const edge = Math.min(lr.right, name.getBoundingClientRect().right);++ const nameKeepsItsEnd = on(lastLink, edge - 2, lr.top + lr.height / 2) || !on(bar.querySelector('.ovnav-orb'), edge - 2, lr.top + lr.height / 2);++ const right = bar.querySelector('.openvibe-navbar-right');++ const firstRight = right && [...right.querySelectorAll('a, button')].find((el) => el.getBoundingClientRect().width > 0);++ const fr = firstRight && firstRight.getBoundingClientRect();++ const rightKeepsItsStart = !firstRight || on(firstRight, fr.left + 2, fr.top + fr.height / 2);++ const out = { signIn: size(bar.querySelector('#openvibe-login-btn')), orb: size(bar.querySelector('.ovnav-orb')), nameKeepsItsEnd, rightKeepsItsStart };++ out.footerBrand = size(document.querySelector('.ovf-brand'));++ return out;++})()`;++++(async () => {++ await new Promise((ok) => server.listen(0, '127.0.0.1', ok));++ const base = `http://127.0.0.1:${server.address().port}`;++ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-taps-'));++ const browser = await h.launch({ tmpDir });++ const wait = (ms) => new Promise((r) => setTimeout(r, ms));++ try {++ const context = await browser.newContext();++ for (const width of [320, 360, 393, 430]) {++ for (const who of ['out', 'in']) {++ const p = await h.openPage(browser, context, { width, height: 800 });++ await p.goto(`${base}/${who}`, { maxMs: 5000 });++ await wait(300);++ const v = await p.evaluate(MEASURE);++ const at = `${width}px signed ${who}`;++ if (who === 'out') assert.ok(v.signIn && v.signIn.h >= 44 && v.signIn.w >= 44, `${at}: Sign In ${JSON.stringify(v.signIn)}`);++ assert.ok(v.orb && v.orb.w >= 44 && v.orb.h >= 44, `${at}: the orb ${JSON.stringify(v.orb)}`);++ assert.ok(v.nameKeepsItsEnd, `${at}: the site's name keeps the taps on its last letters`);++ assert.ok(v.rightKeepsItsStart, `${at}: the right-hand group keeps its own first taps`);++ assert.ok(v.footerBrand && v.footerBrand.h >= 44, `${at}: the footer's brand ${JSON.stringify(v.footerBrand)}`);++ await p.close();++ }++ }++ console.log('tap-areas-chrome: Sign In, the orb and the footer brand reach 44px; the name and the right group keep their taps ok');++ } finally {++ await browser.close();++ server.close();++ fs.rmSync(tmpDir, { recursive: true, force: true });++ }++})().catch((e) => { console.error(e); process.exit(1); });
@@ -4,6 +4,19 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
@@ -4,6 +4,19 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
breaking change to any exported module, browser global or served file name is a new major.
breaking change to any exported module, browser global or served file name is a new major.
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
+## 4.107.2 — 2026-11-03
+
+- **The last small touch targets in the shared bar and footer reach 44px.** A re-run of the tap audit over every
+ site's home (11-03: 31 of 45 sites with a small target, down from 40 of 42 on 10-29) found these:
+ - Sign In was 40px tall; its invisible hit area now reaches 45px;
+ - the OpenVibe menu's orb was 36.9px wide on a phone; it grows to the right, toward the open bar, so it measures
+ 44px while the site's name keeps the taps on its own last letters;
+ - the footer's brand was 33px tall, now 45px;
+ - the orbit's big nodes on a phone were 42px, now 44px.
+ Nothing is drawn differently. `test/tap-areas-chrome.test.js` measures each to a finger with
+ `elementFromPoint`, from 320 to 430px, signed in and out, and checks that the name and the right-hand group keep
+ their own taps.
+
## 4.107.1 — 2026-11-03
## 4.107.1 — 2026-11-03
- **Legal pages draw the brand in its own font on every site.** `legal.page()` loaded the theme loader, the navbar and
- **Legal pages draw the brand in its own font on every site.** `legal.page()` loaded the theme loader, the navbar and
@@ -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.107.1 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
+**Status:** alpha, v4.107.2 (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.107.1",
+ "version": "4.107.2",
"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)",
@@ -384,8 +384,9 @@
@@ -384,8 +384,9 @@
.ovf-col a:hover{color:var(--accent,#60a5fa);transform:translateX(2px)}
.ovf-col a:hover{color:var(--accent,#60a5fa);transform:translateX(2px)}
@media (pointer:coarse){.ovf-col a{display:inline-flex;align-items:center;min-height:32px}.ovf-col .ovf-social a{width:44px;height:44px}.ovf-legal-inline a,.ovf-compact-links a{position:relative}.ovf-legal-inline a::after,.ovf-compact-links a::after{content:'';position:absolute;left:0;right:0;top:-12px;bottom:-12px}}
@media (pointer:coarse){.ovf-col a{display:inline-flex;align-items:center;min-height:32px}.ovf-col .ovf-social a{width:44px;height:44px}.ovf-legal-inline a,.ovf-compact-links a{position:relative}.ovf-legal-inline a::after,.ovf-compact-links a::after{content:'';position:absolute;left:0;right:0;top:-12px;bottom:-12px}}
.ovf-more{font-weight:600;opacity:.85}.ovf-ic{flex:none;vertical-align:-.14em}.ovf-more .ovf-ic{font-size:.85em;margin-left:3px;transition:transform .15s}.ovf-more:hover .ovf-ic{transform:translateX(3px)}.ovf-social .ovf-ic{font-size:17px}.ovf-account-links .ovf-ic{font-size:1.05em;opacity:.85}
.ovf-more{font-weight:600;opacity:.85}.ovf-ic{flex:none;vertical-align:-.14em}.ovf-more .ovf-ic{font-size:.85em;margin-left:3px;transition:transform .15s}.ovf-more:hover .ovf-ic{transform:translateX(3px)}.ovf-social .ovf-ic{font-size:17px}.ovf-account-links .ovf-ic{font-size:1.05em;opacity:.85}
-.ovf-brand{display:inline-flex;align-items:center;gap:9px;color:var(--text-primary,#f1f4fb);font-family:'OV Brand',Nunito,Inter,-apple-system,BlinkMacSystemFont,sans-serif;font-size:1.12rem;font-weight:800;letter-spacing:-.1px;text-decoration:none}
+.ovf-brand{position:relative;display:inline-flex;align-items:center;gap:9px;color:var(--text-primary,#f1f4fb);font-family:'OV Brand',Nunito,Inter,-apple-system,BlinkMacSystemFont,sans-serif;font-size:1.12rem;font-weight:800;letter-spacing:-.1px;text-decoration:none}
.ovf-brand--sm{font-size:.9rem}
.ovf-brand--sm{font-size:.9rem}
+.ovf-brand::after{content:'';position:absolute;inset:-6px 0}
.ovf-tagline{margin:0;font-size:.82rem;line-height:1.55;color:var(--text-muted,#8b93ad);max-width:46ch}
.ovf-tagline{margin:0;font-size:.82rem;line-height:1.55;color:var(--text-muted,#8b93ad);max-width:46ch}
.ovf-social{display:flex;gap:8px;margin-top:4px}
.ovf-social{display:flex;gap:8px;margin-top:4px}
.ovf-social a{width:36px;height:36px;border-radius:10px;display:grid;place-items:center;color:var(--text-secondary,#b6bdd2);
.ovf-social a{width:36px;height:36px;border-radius:10px;display:grid;place-items:center;color:var(--text-secondary,#b6bdd2);
@@ -77,10 +77,13 @@
@@ -77,10 +77,13 @@
.openvibe-navbar .openvibe-navbar-brand .flame::after { content: ''; position: absolute; inset: -8px; }
.openvibe-navbar .openvibe-navbar-brand .flame::after { content: ''; position: absolute; inset: -8px; }
/* 4.46.0 (the fleet tap audit): Sign In, the menu and the orb reach 44px to a finger, still drawn as before. */
/* 4.46.0 (the fleet tap audit): Sign In, the menu and the orb reach 44px to a finger, still drawn as before. */
.openvibe-navbar .openvibe-navbar-login, .openvibe-navbar .ovnav-burger, .openvibe-navbar .ovnav-orb { position: relative; }
.openvibe-navbar .openvibe-navbar-login, .openvibe-navbar .ovnav-burger, .openvibe-navbar .ovnav-orb { position: relative; }
- .openvibe-navbar .openvibe-navbar-login::after { content: ''; position: absolute; inset: -6px 0; }
+ /* 4.107.2 (the 11-03 re-audit): Sign In measured 40px tall; 9px above and below makes 45. */
+ .openvibe-navbar .openvibe-navbar-login::after { content: ''; position: absolute; inset: -9px 0; }
.openvibe-navbar .ovnav-burger::after { content: ''; position: absolute; inset: -3px -5px; }
.openvibe-navbar .ovnav-burger::after { content: ''; position: absolute; inset: -3px -5px; }
- /* The orb grows up and down only a little sideways: the site's name sits 8px from it and keeps its own taps. */
- .openvibe-navbar .ovnav-orb::after { content: ''; position: absolute; inset: -6px -4px; }
+ /* The orb grows up and down, and sideways mostly to the right: the site's name sits 8px to its left and keeps
+ its own taps (4px there); the extra width goes to the open bar on its right (12px), so it measures 44px
+ wide on a phone (it was 36.9 in the 11-03 re-audit). */
+ .openvibe-navbar .ovnav-orb::after { content: ''; position: absolute; inset: -6px -12px -6px -4px; }
}
}
.openvibe-navbar .ovnav-dd { position: relative; display: inline-flex; flex: none; }
.openvibe-navbar .ovnav-dd { position: relative; display: inline-flex; flex: none; }
.openvibe-navbar .ovnav-dd-menu { display: none; position: fixed; top: 56px; left: 0; min-width: 180px; padding: 5px; border-radius: 12px; z-index: 1002; background: var(--bg-elevated, var(--bg-secondary, #111826)); border: 1px solid var(--border, rgba(255,255,255,.12)); box-shadow: 0 18px 44px rgba(0,0,0,.45); flex-direction: column; gap: 2px; }
.openvibe-navbar .ovnav-dd-menu { display: none; position: fixed; top: 56px; left: 0; min-width: 180px; padding: 5px; border-radius: 12px; z-index: 1002; background: var(--bg-elevated, var(--bg-secondary, #111826)); border: 1px solid var(--border, rgba(255,255,255,.12)); box-shadow: 0 18px 44px rgba(0,0,0,.45); flex-direction: column; gap: 2px; }
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.107.1",
+ "version": "4.107.2",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.107.1",
+ "version": "4.107.2",
"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.107.1",
+ "version": "4.107.2",
"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": {
@@ -33,7 +33,7 @@ html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glow
@@ -33,7 +33,7 @@ html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glow
.sc-hero.sc-has-orbit .sc-hero-vis{order:-1;position:static;width:auto;opacity:1;pointer-events:auto;margin:0 0 6px}
.sc-hero.sc-has-orbit .sc-hero-vis{order:-1;position:static;width:auto;opacity:1;pointer-events:auto;margin:0 0 6px}
.sc-hero.sc-has-orbit .sc-vis{width:min(70vw,250px)}
.sc-hero.sc-has-orbit .sc-vis{width:min(70vw,250px)}
.sc-hero.sc-has-orbit .sc-orb-core .sc-vis-mark{width:50px!important;height:50px!important}
.sc-hero.sc-has-orbit .sc-orb-core .sc-vis-mark{width:50px!important;height:50px!important}
-.sc-orb-node{width:32px;height:32px;margin:-16px}.sc-orb-node.is-big{width:42px;height:42px;margin:-21px}.sc-orb-name{font-size:10.5px}
+.sc-orb-node{width:32px;height:32px;margin:-16px}.sc-orb-node.is-big{width:44px;height:44px;margin:-22px}.sc-orb-name{font-size:10.5px}
}
}
.sc-hero,.sc-sec,.sc-cta{max-width:1080px;margin:0 auto;padding:0 24px;font-family:var(--ov-font,'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif)}
.sc-hero,.sc-sec,.sc-cta{max-width:1080px;margin:0 auto;padding:0 24px;font-family:var(--ov-font,'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif)}
@@ -461,7 +461,7 @@ html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glow
@@ -461,7 +461,7 @@ html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glow
.sc-hero.sc-has-orbit .sc-hero-vis{order:-1;position:static;width:auto;opacity:1;pointer-events:auto;margin:0 0 6px}
.sc-hero.sc-has-orbit .sc-hero-vis{order:-1;position:static;width:auto;opacity:1;pointer-events:auto;margin:0 0 6px}
.sc-hero.sc-has-orbit .sc-vis{width:min(70vw,250px)}
.sc-hero.sc-has-orbit .sc-vis{width:min(70vw,250px)}
.sc-hero.sc-has-orbit .sc-orb-core .sc-vis-mark{width:50px!important;height:50px!important}
.sc-hero.sc-has-orbit .sc-orb-core .sc-vis-mark{width:50px!important;height:50px!important}
-.sc-orb-node{width:32px;height:32px;margin:-16px}.sc-orb-node.is-big{width:42px;height:42px;margin:-21px}.sc-orb-name{font-size:10.5px}
+.sc-orb-node{width:32px;height:32px;margin:-16px}.sc-orb-node.is-big{width:44px;height:44px;margin:-22px}.sc-orb-name{font-size:10.5px}
}
}
.sc-hero,.sc-sec,.sc-cta{max-width:1080px;margin:0 auto;padding:0 24px;font-family:var(--ov-font,'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif)}
.sc-hero,.sc-sec,.sc-cta{max-width:1080px;margin:0 auto;padding:0 24px;font-family:var(--ov-font,'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif)}
new file mode 100644
new file mode 100644
@@ -0,0 +1,99 @@
@@ -0,0 +1,99 @@
+'use strict';
+// Touch targets measured to a finger, in a real headless Chrome (4.107.2, the 11-03 re-audit of every site's home:
+// tools/tap-audit.js). A target's effective size is the run of points around its centre that land on it
+// (document.elementFromPoint), so an invisible ::after hit area counts and a neighbour that covers it does not.
+// From 320px to 430px, signed in and out: Sign In and the OpenVibe menu's orb measure 44px or more each way, and the
+// footer's brand 44px tall. The orb's extra width goes right, so the site's name keeps the taps on its own last
+// letters, and nothing in the bar's right-hand group loses its own. Skipped without Chrome.
+const assert = require('assert');
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+const http = require('http');
+const h = require('../browser-harness');
+
+if (process.env.OV_SKIP_BROWSER === '1' || !h.findChrome()) {
+ console.log('tap-areas-chrome: skipped (no Chrome; set CHROME_BIN)');
+ process.exit(0);
+}
+
+const ROOT = path.join(__dirname, '..');
+const PAGE = (signedIn) => `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Live · OpenVibe.Live</title><link rel="icon" href="data:,"></head>
+<body><div id="navbar-mount"></div><main style="min-height:40px"><h1>Live</h1></main><footer id="footer-mount"></footer>
+<script>
+ localStorage.setItem('ov-wallet:42', JSON.stringify({ coins: 161000, vibes: 400 }));
+ window.fetch = async () => new Response('{}', { status: 404 });
+</script>
+<script src="/navbar.js"></script><script src="/footer.js"></script>
+<script>
+ OpenVibeNavbar.init({ service: 'live', brandName: 'OpenVibe.Live', wallet: true, messages: { href: 'https://openvibe.chat/messages' }, ${signedIn ? "token: 'x', user: { id: 42, username: 'moosely', display_name: 'moosely', avatar_url: null }," : 'user: null,'}
+ links: [{ label: 'Home', href: '/', icon: 'fa-house' }] });
+ OpenVibeFooter.init({ service: 'live', mount: '#footer-mount', variant: 'full' });
+</script></body></html>`;
+const server = http.createServer((req, res) => {
+ const u = new URL(req.url, 'http://x');
+ if (u.pathname === '/in' || u.pathname === '/out') { res.writeHead(200, { 'content-type': 'text/html' }); return res.end(PAGE(u.pathname === '/in')); }
+ const name = u.pathname.slice(1);
+ if (/^[a-z-]+\.js$/.test(name) && fs.existsSync(path.join(ROOT, name))) { res.writeHead(200, { 'content-type': 'application/javascript' }); return res.end(fs.readFileSync(path.join(ROOT, name))); }
+ res.writeHead(404); return res.end();
+});
+
+// The effective size of a target: the longest run of points through its centre, 1px apart, that land on it.
+const MEASURE = `(() => {
+ const on = (el, x, y) => { const hit = document.elementFromPoint(x, y); return !!hit && (hit === el || el.contains(hit)); };
+ const size = (el) => {
+ if (!el) return null;
+ el.scrollIntoView({ block: 'center' });
+ const r = el.getBoundingClientRect();
+ const cx = r.left + r.width / 2, cy = r.top + r.height / 2;
+ const run = (dx, dy) => { let a = 0, b = 0; while (a < 60 && on(el, cx - (a + 1) * dx, cy - (a + 1) * dy)) a++; while (b < 60 && on(el, cx + (b + 1) * dx, cy + (b + 1) * dy)) b++; return a + b + 1; };
+ return { w: run(1, 0), h: run(0, 1), box: [Math.round(r.left), Math.round(r.right)] };
+ };
+ const bar = document.querySelector('.openvibe-navbar');
+ scrollTo(0, 0);
+ const name = bar.querySelector('.openvibe-navbar-brand .name');
+ const nameLinks = [...name.querySelectorAll('a')];
+ const lastLink = nameLinks[nameLinks.length - 1];
+ const lr = lastLink.getBoundingClientRect();
+ // At 320px the name is cut short (it reads whole from 360px): its last link ends where the name's box does.
+ const edge = Math.min(lr.right, name.getBoundingClientRect().right);
+ const nameKeepsItsEnd = on(lastLink, edge - 2, lr.top + lr.height / 2) || !on(bar.querySelector('.ovnav-orb'), edge - 2, lr.top + lr.height / 2);
+ const right = bar.querySelector('.openvibe-navbar-right');
+ const firstRight = right && [...right.querySelectorAll('a, button')].find((el) => el.getBoundingClientRect().width > 0);
+ const fr = firstRight && firstRight.getBoundingClientRect();
+ const rightKeepsItsStart = !firstRight || on(firstRight, fr.left + 2, fr.top + fr.height / 2);
+ const out = { signIn: size(bar.querySelector('#openvibe-login-btn')), orb: size(bar.querySelector('.ovnav-orb')), nameKeepsItsEnd, rightKeepsItsStart };
+ out.footerBrand = size(document.querySelector('.ovf-brand'));
+ return out;
+})()`;
+
+(async () => {
+ await new Promise((ok) => server.listen(0, '127.0.0.1', ok));
+ const base = `http://127.0.0.1:${server.address().port}`;
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ov-taps-'));
+ const browser = await h.launch({ tmpDir });
+ const wait = (ms) => new Promise((r) => setTimeout(r, ms));
+ try {
+ const context = await browser.newContext();
+ for (const width of [320, 360, 393, 430]) {
+ for (const who of ['out', 'in']) {
+ const p = await h.openPage(browser, context, { width, height: 800 });
+ await p.goto(`${base}/${who}`, { maxMs: 5000 });
+ await wait(300);
+ const v = await p.evaluate(MEASURE);
+ const at = `${width}px signed ${who}`;
+ if (who === 'out') assert.ok(v.signIn && v.signIn.h >= 44 && v.signIn.w >= 44, `${at}: Sign In ${JSON.stringify(v.signIn)}`);
+ assert.ok(v.orb && v.orb.w >= 44 && v.orb.h >= 44, `${at}: the orb ${JSON.stringify(v.orb)}`);
+ assert.ok(v.nameKeepsItsEnd, `${at}: the site's name keeps the taps on its last letters`);
+ assert.ok(v.rightKeepsItsStart, `${at}: the right-hand group keeps its own first taps`);
+ assert.ok(v.footerBrand && v.footerBrand.h >= 44, `${at}: the footer's brand ${JSON.stringify(v.footerBrand)}`);
+ await p.close();
+ }
+ }
+ console.log('tap-areas-chrome: Sign In, the orb and the footer brand reach 44px; the name and the right group keep their taps ok');
+ } finally {
+ await browser.close();
+ server.close();
+ fs.rmSync(tmpDir, { recursive: true, force: true });
+ }
+})().catch((e) => { console.error(e); process.exit(1); });