openvibers/openvibe.shared Commits

4.89.0: a card with no picture but a person shows their face (#244)

OP OpenVibers committed 11 days ago · 083b638f15 · 7 files, +25 −4

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 232f700..5e2281e 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -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.
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
++## 4.89.0 — 2026-11-01
++
++- **A card with no picture but a person shows their face** (`kit.card`, `room.css`).
++ - A card with no `image` (and no `preview`) but an `avatar` draws the avatar large in a circle over a soft blur of it.
++ A lone type icon in an empty box read as a missing image: Report's episodes and Tours' recordings of streams with no
++ public recording.
++ - The avatar is fetched at 160 px (`mediaW`), is `aria-hidden` (the body's mark and the title already name the
++ person), and a script or bad URL falls back to the icon as before.
++
## 4.88.1 — 2026-11-01
- **An aborted view transition is silent on the account hub.**
ddiff --git a/README.md b/README.md
iindex cf27d11..1be0c7a 100644
---- a/README.md
++++ b/README.md
@@@ -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.88.1 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
++**Status:** alpha, v4.89.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
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
the tag it pins.
ddiff --git a/STATUS.json b/STATUS.json
iindex da12401..f63e978 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -4,7 +4,7 @@
"code": true,
"wave": "W2",
"package": "openvibe-shared",
-- "version": "4.88.1",
++ "version": "4.89.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)",
ddiff --git a/kit.js b/kit.js
iindex 4401d52..76fb37e 100644
---- a/kit.js
++++ b/kit.js
@@@ -402,7 +402,10 @@
const pic = media(image)
? `<img src="${esc(mediaW(image, 640))}" alt="${esc(alt)}" loading="lazy" decoding="async">`
: first ? `<pre class="ovk-card-pre" aria-hidden="true">${esc(first)}</pre>`
-- : `<span class="ovk-card-noimg">${icon(CARD_ICON[type] || 'image')}</span>`;
++ // No picture but a person (2026-11-01): their face, large, over a soft blur of it, where a lone type icon
++ // in an empty box read as a missing image (Report's episodes, Tours' recordings).
++ : media(avatar) ? `<span class="ovk-card-noimg ovk-card-noimg--who" aria-hidden="true"><img class="ovk-card-noimg-bg" src="${esc(mediaW(avatar, 160))}" alt="" loading="lazy" decoding="async"><img class="ovk-card-noimg-face" src="${esc(mediaW(avatar, 160))}" alt="" loading="lazy" decoding="async"></span>`
++ : `<span class="ovk-card-noimg">${icon(CARD_ICON[type] || 'image')}</span>`;
const loop = media(video) ? `<video class="ovk-card-video" data-src="${esc(video)}" muted loop playsinline preload="none" aria-hidden="true" tabindex="-1"></video>` : '';
const tag = live ? '<span class="ovk-card-badge is-live">LIVE</span>' : bd ? `<span class="ovk-card-badge">${esc(bd)}</span>` : '';
const n = Array.isArray(count) ? Number(count[0]) : Number(count);
ddiff --git a/package.json b/package.json
iindex 46d76cc..7e27076 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "4.88.1",
++ "version": "4.89.0",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"bin": {
ddiff --git a/room.css b/room.css
iindex f4b56ff..b534d50 100644
---- a/room.css
++++ b/room.css
@@@ -98,6 +98,10 @@
.ovk-card.is-playing .ovk-card-video { opacity: 1; }
.ovk-card-noimg { position: absolute; inset: 0; display: grid; place-items: center; color: var(--room-accent, var(--ovk-accent)); background: radial-gradient(120% 90% at 28% 18%, color-mix(in srgb, var(--room-accent, var(--ovk-accent)) 24%, transparent), transparent 72%), var(--ovk-panel-2); }
.ovk-card-noimg .ovk-i { width: 30%; max-width: 60px; height: auto; aspect-ratio: 1; stroke-width: 1.5; }
++/* No picture but a person (2026-11-01): their face, large, over a soft blur of it, not a lone icon in an empty box. */
++.ovk-card-noimg--who { overflow: hidden; }
++.ovk-card-noimg--who .ovk-card-noimg-bg { position: absolute; inset: -15%; width: 130%; height: 130%; object-fit: cover; filter: blur(24px) saturate(1.15); opacity: .5; }
++.ovk-card-noimg--who .ovk-card-noimg-face { position: relative; width: 28%; max-width: 88px; aspect-ratio: 1; border-radius: 50%; object-fit: cover; box-shadow: 0 0 0 3px color-mix(in srgb, var(--ovk-panel, #111a2c) 70%, transparent), 0 10px 28px rgb(0 0 0 / .28); }
.ovk-card-badge { position: absolute; top: 8px; left: 8px; z-index: 1; display: inline-flex; align-items: center; gap: 5px; padding: 2px 8px; border-radius: 6px; background: rgba(0, 0, 0, .74); color: #fff; font: 800 11.5px/1.5 var(--ovk-font); letter-spacing: .04em; }
.ovk-card-badge.is-live { background: #d4202b; }
.ovk-card-badge.is-live::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: #fff; animation: ovk-pulse 1.6s ease-in-out infinite; }
ddiff --git a/test/room-kit.test.js b/test/room-kit.test.js
iindex e0a650e..853f891 100644
---- a/test/room-kit.test.js
++++ b/test/room-kit.test.js
@@@ -45,6 +45,11 @@ const sc = require('../showcase');
assert.ok(c.includes('href="/save/1"') && c.includes('Save'), 'Save when given');
assert.ok(!c.includes('Remind me'), 'no Remind unless given');
assert.ok(c.includes('class="ovk-card-noimg"'), 'no picture: the type\'s icon');
++ // No picture but a person: their face over a soft blur of it (2026-11-01), at the avatar width, never a script URL.
++ const who = kit.card({ title: 'x', type: 'stream', who: 'Ann', avatar: 'https://openvibe.media/o/med_01ABCDEF' });
++ assert.ok(who.includes('class="ovk-card-noimg ovk-card-noimg--who" aria-hidden="true"'), 'no picture, a person: their face');
++ assert.ok(who.includes('<img class="ovk-card-noimg-face" src="https://openvibe.media/o/med_01ABCDEF?w=160"'), 'the face at 160 px');
++ assert.ok(kit.card({ title: 'x', avatar: 'javascript:x' }).includes('class="ovk-card-noimg"><'), 'a bad avatar URL falls back to the icon');
assert.ok(!kit.card({ title: 'x', count: [0, 'views'] }).includes('0 views'), 'a count of 0 is left out');
assert.ok(!kit.card({ title: 'x', count: null }).includes('ovk-card-meta'), 'nothing to say: no meta line');
assert.ok(kit.card({ title: 'x', type: 'shoe' }).includes('--ovk-ar:1/1'), 'a shoe is square');