openvibers/openvibe.shared Commits
4.107.3: on a phone, a site's own room picture sits below the hero (#279)
Changes
@@@ -4,6 +4,15 @@ 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.3 — 2026-11-03++++- **On a phone, a site's own room picture sits below the hero, not behind its text.** Under 760px the room drew its++ picture behind the hero's text at 0.28 opacity. For the default motif (the orbits) that is a backdrop. A site's own++ picture is usually a sample card of words and numbers, and on 11-03 it ran through the headline on Report ("OpenVibe,++ honest" over "2 h 14 m"), Domains, Surf, Shoes and Tours. Now a picture that is not the default motif sits below the++ hero, whole and at full strength; the default motif stays a faint backdrop. `test/room-pic-phone-chrome.test.js`++ checks both at 360, 390 and 700px.++## 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@@@ -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.2 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed++**Status:** alpha, v4.107.3 (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.2",++ "version": "4.107.3","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)",@@@ -1,12 +1,12 @@{"name": "openvibe-shared",-- "version": "4.107.2",++ "version": "4.107.3","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-shared",-- "version": "4.107.2",++ "version": "4.107.3","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "4.107.2",++ "version": "4.107.3","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","bin": {@@@ -229,6 +229,10 @@ html.ov-motion [data-ov-reveal].is-in{opacity:1;transform:none}@media (max-width:759px){.sc-room-hero{grid-template-columns:minmax(0,1fr);margin-inline:-16px;padding-inline:16px;overflow-x:clip}.sc-room-pic{position:absolute;z-index:-1;top:-4px;right:-30px;width:168px;opacity:.28;pointer-events:none}++/* 4.107.3: a site's own picture (anything but the default motif) is a sample card of words and numbers more often than a++ backdrop: behind the hero's text it ran through the headline (Report, Domains, Surf, Shoes, Tours on 11-03). On a phone++ it sits below the hero, whole; the default motif stays a faint backdrop. */++.sc-room-pic:not(:has(> .sc-room-motif)){position:static;z-index:auto;width:auto;opacity:1;margin-top:24px;pointer-events:auto}.sc-scene,.sc-scene.is-flip{grid-template-columns:minmax(0,1fr)}.sc-scene.is-flip .sc-scene-text{order:0}}@media (max-width:599px){.sc-room-chips{flex-wrap:nowrap;overflow-x:auto;margin-inline:-16px;padding:2px 16px;scrollbar-width:none}.sc-room-chips::-webkit-scrollbar{display:none}.sc-room-box input{height:52px;padding-left:18px}}nnew file mode 100644@@@ -0,0 +1,64 @@++'use strict';++// A room's picture on a phone (4.107.3), in a real headless Chrome. Under 760px the room drew its picture behind the++// hero's text at 0.28 opacity. For the default motif (orbits) that is a backdrop; a site's own picture is usually a++// sample card of words and numbers, and on 11-03 it ran through the headline on Report, Domains, Surf, Shoes and Tours.++// Now a site's own picture sits below the hero, whole; the default motif stays a faint backdrop. 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');++const sc = require('../showcase');++++if (process.env.OV_SKIP_BROWSER === '1' || !h.findChrome()) {++ console.log('room-pic-phone-chrome: skipped (no Chrome; set CHROME_BIN)');++ process.exit(0);++}++++const ROOT = path.join(__dirname, '..');++const css = ['kit.css', 'room.css', 'showcase.css'].filter((f) => fs.existsSync(path.join(ROOT, f))).map((f) => fs.readFileSync(path.join(ROOT, f), 'utf8')).join('\n');++const page = (motif) => `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Room</title><style>${css}</style></head>++<body style="margin:0;padding:0 16px">${sc.room({ room: 'surf', hero: { title: 'Every number on OpenVibe, honest', lede: 'The network’s numbers, open to everyone.', ...(motif ? { motif } : {}) } })}</body></html>`;++const CARD = '<div class="x-card" aria-hidden="true"><b>Tuesday stream · 2 h 14 m</b><p>1,204 chat messages · 186 people came by</p></div>';++const server = http.createServer((req, res) => {++ res.writeHead(200, { 'content-type': 'text/html' });++ res.end(page(req.url === '/card' ? CARD : null));++});++++const MEASURE = `(() => {++ const pic = document.querySelector('.sc-room-pic'), h1 = document.querySelector('.sc-room-hero h1');++ const p = pic.getBoundingClientRect(), t = h1.getBoundingClientRect(), cs = getComputedStyle(pic);++ return { position: cs.position, opacity: Number(cs.opacity), picTop: p.top, h1Bottom: t.bottom, overlaps: p.left < t.right && p.right > t.left && p.top < t.bottom && p.bottom > t.top, width: document.documentElement.scrollWidth };++})()`;++++(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-roompic-'));++ const browser = await h.launch({ tmpDir });++ try {++ const context = await browser.newContext();++ for (const width of [360, 390, 700]) {++ const card = await h.openPage(browser, context, { width, height: 800 });++ await card.goto(`${base}/card`, { maxMs: 5000 });++ const c = await card.evaluate(MEASURE);++ assert.strictEqual(c.position, 'static', `${width}px: a site's own picture is in the flow`);++ assert.strictEqual(c.opacity, 1, `${width}px: and at full strength`);++ assert.ok(!c.overlaps && c.picTop >= c.h1Bottom, `${width}px: it sits below the headline, not behind it (${JSON.stringify(c)})`);++ assert.ok(c.width <= width, `${width}px: no sideways scroll`);++ await card.close();++ const motif = await h.openPage(browser, context, { width, height: 800 });++ await motif.goto(`${base}/default`, { maxMs: 5000 });++ const m = await motif.evaluate(MEASURE);++ assert.strictEqual(m.position, 'absolute', `${width}px: the default motif stays a backdrop`);++ assert.ok(m.opacity < 0.5, `${width}px: a faint one`);++ await motif.close();++ }++ console.log('room-pic-phone-chrome: a site’s own picture sits below the hero on a phone; the default motif stays a backdrop 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,15 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
@@ -4,6 +4,15 @@ 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.3 — 2026-11-03
+
+- **On a phone, a site's own room picture sits below the hero, not behind its text.** Under 760px the room drew its
+ picture behind the hero's text at 0.28 opacity. For the default motif (the orbits) that is a backdrop. A site's own
+ picture is usually a sample card of words and numbers, and on 11-03 it ran through the headline on Report ("OpenVibe,
+ honest" over "2 h 14 m"), Domains, Surf, Shoes and Tours. Now a picture that is not the default motif sits below the
+ hero, whole and at full strength; the default motif stays a faint backdrop. `test/room-pic-phone-chrome.test.js`
+ checks both at 360, 390 and 700px.
+
## 4.107.2 — 2026-11-03
## 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
- **The last small touch targets in the shared bar and footer reach 44px.** A re-run of the tap audit over every
@@ -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.2 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
+**Status:** alpha, v4.107.3 (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.2",
+ "version": "4.107.3",
"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)",
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.107.2",
+ "version": "4.107.3",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.107.2",
+ "version": "4.107.3",
"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.2",
+ "version": "4.107.3",
"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": {
@@ -229,6 +229,10 @@ html.ov-motion [data-ov-reveal].is-in{opacity:1;transform:none}
@@ -229,6 +229,10 @@ html.ov-motion [data-ov-reveal].is-in{opacity:1;transform:none}
@media (max-width:759px){
@media (max-width:759px){
.sc-room-hero{grid-template-columns:minmax(0,1fr);margin-inline:-16px;padding-inline:16px;overflow-x:clip}
.sc-room-hero{grid-template-columns:minmax(0,1fr);margin-inline:-16px;padding-inline:16px;overflow-x:clip}
.sc-room-pic{position:absolute;z-index:-1;top:-4px;right:-30px;width:168px;opacity:.28;pointer-events:none}
.sc-room-pic{position:absolute;z-index:-1;top:-4px;right:-30px;width:168px;opacity:.28;pointer-events:none}
+/* 4.107.3: a site's own picture (anything but the default motif) is a sample card of words and numbers more often than a
+ backdrop: behind the hero's text it ran through the headline (Report, Domains, Surf, Shoes, Tours on 11-03). On a phone
+ it sits below the hero, whole; the default motif stays a faint backdrop. */
+.sc-room-pic:not(:has(> .sc-room-motif)){position:static;z-index:auto;width:auto;opacity:1;margin-top:24px;pointer-events:auto}
.sc-scene,.sc-scene.is-flip{grid-template-columns:minmax(0,1fr)}.sc-scene.is-flip .sc-scene-text{order:0}
.sc-scene,.sc-scene.is-flip{grid-template-columns:minmax(0,1fr)}.sc-scene.is-flip .sc-scene-text{order:0}
}
}
@media (max-width:599px){.sc-room-chips{flex-wrap:nowrap;overflow-x:auto;margin-inline:-16px;padding:2px 16px;scrollbar-width:none}.sc-room-chips::-webkit-scrollbar{display:none}.sc-room-box input{height:52px;padding-left:18px}}
@media (max-width:599px){.sc-room-chips{flex-wrap:nowrap;overflow-x:auto;margin-inline:-16px;padding:2px 16px;scrollbar-width:none}.sc-room-chips::-webkit-scrollbar{display:none}.sc-room-box input{height:52px;padding-left:18px}}
new file mode 100644
new file mode 100644
@@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
+'use strict';
+// A room's picture on a phone (4.107.3), in a real headless Chrome. Under 760px the room drew its picture behind the
+// hero's text at 0.28 opacity. For the default motif (orbits) that is a backdrop; a site's own picture is usually a
+// sample card of words and numbers, and on 11-03 it ran through the headline on Report, Domains, Surf, Shoes and Tours.
+// Now a site's own picture sits below the hero, whole; the default motif stays a faint backdrop. 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');
+const sc = require('../showcase');
+
+if (process.env.OV_SKIP_BROWSER === '1' || !h.findChrome()) {
+ console.log('room-pic-phone-chrome: skipped (no Chrome; set CHROME_BIN)');
+ process.exit(0);
+}
+
+const ROOT = path.join(__dirname, '..');
+const css = ['kit.css', 'room.css', 'showcase.css'].filter((f) => fs.existsSync(path.join(ROOT, f))).map((f) => fs.readFileSync(path.join(ROOT, f), 'utf8')).join('\n');
+const page = (motif) => `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Room</title><style>${css}</style></head>
+<body style="margin:0;padding:0 16px">${sc.room({ room: 'surf', hero: { title: 'Every number on OpenVibe, honest', lede: 'The network’s numbers, open to everyone.', ...(motif ? { motif } : {}) } })}</body></html>`;
+const CARD = '<div class="x-card" aria-hidden="true"><b>Tuesday stream · 2 h 14 m</b><p>1,204 chat messages · 186 people came by</p></div>';
+const server = http.createServer((req, res) => {
+ res.writeHead(200, { 'content-type': 'text/html' });
+ res.end(page(req.url === '/card' ? CARD : null));
+});
+
+const MEASURE = `(() => {
+ const pic = document.querySelector('.sc-room-pic'), h1 = document.querySelector('.sc-room-hero h1');
+ const p = pic.getBoundingClientRect(), t = h1.getBoundingClientRect(), cs = getComputedStyle(pic);
+ return { position: cs.position, opacity: Number(cs.opacity), picTop: p.top, h1Bottom: t.bottom, overlaps: p.left < t.right && p.right > t.left && p.top < t.bottom && p.bottom > t.top, width: document.documentElement.scrollWidth };
+})()`;
+
+(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-roompic-'));
+ const browser = await h.launch({ tmpDir });
+ try {
+ const context = await browser.newContext();
+ for (const width of [360, 390, 700]) {
+ const card = await h.openPage(browser, context, { width, height: 800 });
+ await card.goto(`${base}/card`, { maxMs: 5000 });
+ const c = await card.evaluate(MEASURE);
+ assert.strictEqual(c.position, 'static', `${width}px: a site's own picture is in the flow`);
+ assert.strictEqual(c.opacity, 1, `${width}px: and at full strength`);
+ assert.ok(!c.overlaps && c.picTop >= c.h1Bottom, `${width}px: it sits below the headline, not behind it (${JSON.stringify(c)})`);
+ assert.ok(c.width <= width, `${width}px: no sideways scroll`);
+ await card.close();
+ const motif = await h.openPage(browser, context, { width, height: 800 });
+ await motif.goto(`${base}/default`, { maxMs: 5000 });
+ const m = await motif.evaluate(MEASURE);
+ assert.strictEqual(m.position, 'absolute', `${width}px: the default motif stays a backdrop`);
+ assert.ok(m.opacity < 0.5, `${width}px: a faint one`);
+ await motif.close();
+ }
+ console.log('room-pic-phone-chrome: a site’s own picture sits below the hero on a phone; the default motif stays a backdrop ok');
+ } finally {
+ await browser.close();
+ server.close();
+ fs.rmSync(tmpDir, { recursive: true, force: true });
+ }
+})().catch((e) => { console.error(e); process.exit(1); });