openvibers/openvibe.shared Commits

4.100.0: a card's day as its cover; the aura's mark leaves the heading alone on a phone (#260)

OP OpenVibers committed 12 days ago · d9ca665951 · 10 files, +44 −9

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 1df593d..78936b8 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,16 @@ 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.100.0 — 2026-11-02
++
++From a phone sweep of every site's home (11-02):
++- **A card's day as its cover.** `kit.card({ date })`: a thing without a picture that happens on a day (a drop, an
++ event, a deadline) shows the weekday and month with the day large, instead of the same calendar icon on every card.
++ The day stays clear of the corner's price. Shoes' "Dropping this week" was a row of identical calendar icons.
++- **The aura's mark leaves the heading alone on a phone.** On a phone the aura hero (`sc-has-vis` without an orbit) is a
++ watermark behind the heading, and its mark landed at the end of a long heading: AI's read "one API.V", Download's
++ and Watch's the same. The glow stays; the mark goes there (the navbar shows it). Computers keep it in its column.
++
## 4.99.1 — 2026-11-02
- **The conversation's head follows the navbar on a phone.** The navbar slides away while you read down. The thread's
ddiff --git a/README.md b/README.md
iindex 52ab177..a6c6318 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.99.1 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
++**Status:** alpha, v4.100.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 398e486..09300d0 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -4,7 +4,7 @@
"code": true,
"wave": "W2",
"package": "openvibe-shared",
-- "version": "4.99.1",
++ "version": "4.100.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/docs/kit.md b/docs/kit.md
iindex bb7da29..c35078a 100644
---- a/docs/kit.md
++++ b/docs/kit.md
@@@ -199,7 +199,7 @@ kit.badge(text, 'accent' | 'ok' | 'warn' | 'bad' | 'live') · kit.dot(n) · kit.
### Cards and rails (4.28.0)
```js
--kit.card({ href, title, type, ratio, image, alt, video, badge, live, corner, peek, who, whoHtml, avatar, sub, preview,
++kit.card({ href, title, type, ratio, image, alt, video, badge, live, corner, peek, who, whoHtml, avatar, sub, preview, date,
count: [1832, 'views'], when, level, save: { href }, remind: { href }, share, more: [items] })
kit.rail({ id, title, more: { href, label }, items: [kit.card(…), …], size: 's' | 'm' | 'l', wrap }))
```
@@@ -208,7 +208,7 @@ One card for every kind of thing (plan 2026-10-28 §3.4):
| Part | What it does |
| --- | --- |
--| Media | In the type's shape (`kit.CARD_RATIO`): 16:9 streams, clips, runs, maps, cams, events; 1:1 memes, shoes, listings, deals; 4:3 reports, themes, games; 2:1 domains. `ratio` overrides it. Without a picture, the type's icon on a wash of the room's accent |
++| Media | In the type's shape (`kit.CARD_RATIO`): 16:9 streams, clips, runs, maps, cams, events; 1:1 memes, shoes, listings, deals; 4:3 reports, themes, games; 2:1 domains. `ratio` overrides it. Without a picture, the type's icon on a wash of the room's accent; a person's face when `avatar` is given; the first lines when `preview` is; and the day itself (weekday and month, the day large) when `date` is (a drop, an event, a deadline) |
| Badge | Top left, one word: NEW, −24 %, WR. `live: true` draws LIVE with a pulse |
| Corner | Bottom right: a length or a price |
| Peek | One line over the media on hover or focus |
ddiff --git a/kit.js b/kit.js
iindex ea3427f..b3bc11e 100644
---- a/kit.js
++++ b/kit.js
@@@ -411,13 +411,25 @@
* name as markup the caller escaped (a name with its decorations); who stays the plain name for the letters
* - preview: for a thing without a picture whose text is the point (a code paste, a note), its first lines drawn in
* the media instead of the type's icon
++ * - date: for a thing without a picture that happens on a day (a drop, an event, a deadline), the day itself drawn in
++ * the media (weekday and month, the day large; UTC) instead of a calendar icon repeated on every card
* - ⋯ with Save and Remind when given ({ href } or { action, hidden }), Share (always: the system share sheet, else
* the link copied) and any `more` items (kit.more's)
* The card is one link, the title's, stretched over it; ⋯ sits above it. level: the title's heading level (3).
*/
++ // A day as a card's cover (card({ date })): the weekday and month on a line, the day large under it (clear of the
++ // corner's price). UTC, as the sites' dates are.
++ function dateCover(value) {
++ if (value == null || value === '') return '';
++ const t = value instanceof Date ? value.getTime() : typeof value === 'number' ? value : Date.parse(String(value));
++ if (!Number.isFinite(t)) return '';
++ const d = new Date(t);
++ return `<span class="ovk-card-noimg ovk-card-noimg--date" aria-hidden="true"><span class="ovk-card-date-m"><span class="ovk-card-date-w">${WEEKDAYS[d.getUTCDay()]}</span> ${MONTHS[d.getUTCMonth()]}</span>`
++ + `<b class="ovk-card-date-d">${d.getUTCDate()}</b></span>`;
++ }
function card({ href = '#', title = '', type = '', ratio = '', image = '', alt = '', video = '', badge: bd = '', live = false, corner = '',
peek = '', who = '', whoHtml = null, avatar = '', count = null, when: at = null, level = 3, save = null, remind = null, share = true,
-- more: extra = null, sub = '', preview = '' } = {}) {
++ more: extra = null, sub = '', preview = '', date = null } = {}) {
const ar = /^\d{1,2}\/\d{1,2}$/.test(String(ratio)) ? ratio : CARD_RATIO[type] || '16/9';
const link = url(href);
const first = String(preview || '').split('\n').slice(0, 9).join('\n').slice(0, 480).trimEnd();
@@@ -427,7 +439,7 @@
// 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>`;
++ : dateCover(date) || `<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 b9652dd..c178a9e 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-shared",
-- "version": "4.99.1",
++ "version": "4.100.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 b534d50..eb13bc1 100644
---- a/room.css
++++ b/room.css
@@@ -100,6 +100,11 @@
.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; }
++/* A day as the cover (kit.card({ date })): the month in the accent, the day large, the weekday quiet. */
++.ovk-card-noimg--date { container-type: inline-size; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0; padding-bottom: 8%; line-height: 1; font-variant-numeric: tabular-nums; }
++.ovk-card-date-m { font-size: clamp(11px, 6cqi, 15px); font-weight: 750; letter-spacing: .12em; text-transform: uppercase; }
++.ovk-card-date-d { margin: 4px 0 0; font-size: clamp(30px, 23cqi, 64px); font-weight: 800; letter-spacing: -.03em; color: var(--ovk-text, var(--text-primary, #e6edf7)); }
++.ovk-card-date-w { font-weight: 650; color: var(--ovk-muted, var(--text-muted, #8b97ab)); }
.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; }
ddiff --git a/showcase.css b/showcase.css
iindex 7eb0a1f..0409118 100644
---- a/showcase.css
++++ b/showcase.css
@@@ -28,7 +28,7 @@ html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glow
@media (max-width:860px){
.sc-hero.sc-has-vis{display:block}
.sc-hero.sc-has-vis .sc-hero-vis{position:absolute;z-index:-1;top:-26px;right:0;width:180px;opacity:.36;pointer-events:none}
--.sc-hero.sc-has-vis .sc-aura .sc-vis-mark{width:46px!important;height:46px!important}
++.sc-hero.sc-has-vis .sc-aura .sc-vis-mark{display:none!important}
.sc-hero.sc-has-orbit{display:flex;flex-direction:column}
.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)}
ddiff --git a/showcase.js b/showcase.js
iindex 08101b6..45b828e 100644
---- a/showcase.js
++++ b/showcase.js
@@@ -456,7 +456,7 @@ html[data-ov-motion=reduced] .sc-ring,html[data-ov-motion=reduced] .sc-aura-glow
@media (max-width:860px){
.sc-hero.sc-has-vis{display:block}
.sc-hero.sc-has-vis .sc-hero-vis{position:absolute;z-index:-1;top:-26px;right:0;width:180px;opacity:.36;pointer-events:none}
--.sc-hero.sc-has-vis .sc-aura .sc-vis-mark{width:46px!important;height:46px!important}
++.sc-hero.sc-has-vis .sc-aura .sc-vis-mark{display:none!important}
.sc-hero.sc-has-orbit{display:flex;flex-direction:column}
.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)}
ddiff --git a/test/kit.test.js b/test/kit.test.js
iindex 653f974..9f173e7 100644
---- a/test/kit.test.js
++++ b/test/kit.test.js
@@@ -286,4 +286,12 @@ const kit = require('../kit');
assert.ok(/ov:boost:before', \(\) => Array\.prototype\.forEach\.call\(document\.querySelectorAll\('\.ovk-sidebar\.is-open'\)/.test(web), 'and closed by a page move');
}
++// card({ date }) (4.100.0): a thing without a picture that happens on a day shows the day, not a repeated calendar icon.
++{
++ const dated = kit.card({ href: '/drops#d1', title: 'Nike Air Max Dolce', type: 'event', corner: '$170.00', date: '2026-11-04T15:00:00Z' });
++ assert.ok(dated.includes('<span class="ovk-card-noimg ovk-card-noimg--date" aria-hidden="true"><span class="ovk-card-date-m"><span class="ovk-card-date-w">Wed</span> Nov</span><b class="ovk-card-date-d">4</b></span>'), 'the day as the cover, in UTC');
++ assert.ok(!kit.card({ title: 'x', type: 'event', date: 'not a date' }).includes('--date') && !kit.card({ title: 'x', type: 'event' }).includes('--date'), 'no date, or not a date: the type icon as before');
++ assert.ok(!kit.card({ title: 'x', type: 'event', image: 'https://example.com/a.jpg', date: '2026-11-04' }).includes('--date'), 'a picture wins over the date');
++}
++
console.log('kit: builders, escaping, tabs, rows, settings, find/dock, icons, time and Kit 2 (steps, table, chart, rank, meter, drop, receipt, page), the tool frame and the sidebar ok');