openvibers/openvibe.shared Commits
4.39.1: room.css stands on its own (the kit's tokens and numbers strip), so a front page without kit.css draws its cards and stats (#179)
Changes
@@@ -6,6 +6,14 @@ A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).## Unreleased++## 4.39.1 — 2026-10-29++++- **room.css stands on its own.** A front page that loads room.css without kit.css (Media's, to skip kit.css's 25 KB)++ had no `--ovk-*` tokens: cards lost their corners and colours, a card without a picture had no background, and its++ `kit.stats` strip was a bare list. room.css now carries the same tokens (as `:where(:root)`, so kit.css's and a++ site's own values always win) and the numbers strip. The copies are identical, so loading both files changes nothing, and++ test/room-kit.test.js keeps them that way. room.css: +2.8 KB raw.++## 4.39.0 — 2026-10-29What the front-page passes of 10-29 (Media, Quest, Actor, Rent, Services, Community) each had to add locally:@@@ -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.39.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed++**Status:** alpha, v4.39.1 (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.39.0",++ "version": "4.39.1","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.39.0",++ "version": "4.39.1","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-shared",-- "version": "4.39.0",++ "version": "4.39.1","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "4.39.0",++ "version": "4.39.1","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","bin": {@@@ -1,6 +1,48 @@/* openvibe-shared/room.css — the room kit's look (4.29.1; before, in kit.css): a room's accent (rooms.js), kit.card and* kit.rail. A page that shows cards or rails, or wears a room's accent (sc.room() does all three), loads it after-- * kit.css; pages that use only the kit's controls do not pay for it. Uses kit.css's tokens (--ovk-*). */++ * kit.css, or alone on a front page that draws only cards, rails and a numbers strip (it carries the tokens and the++ * strip since 4.39.1); pages that use only the kit's controls do not pay for it. */++++/* ── The kit's tokens and its numbers strip, the same as kit.css's (4.39.1): a front page that loads room.css without++ * kit.css (cards, rails and a kit.stats strip, nothing else) still has them. Identical copies, so loading both files++ * changes nothing (the tokens are :where(:root), so kit.css's and a site's own values always win);++ * test/room-kit.test.js keeps them identical. ── */++:where(:root) {++ --ovk-bg: var(--bg-primary, #0b0f17);++ --ovk-panel: var(--bg-card, var(--bg-secondary, #121826));++ --ovk-panel-2: var(--bg-hover, var(--bg-tertiary, #1a2233));++ --ovk-input: var(--bg-input, var(--bg-tertiary, #0f1522));++ --ovk-line: var(--border, rgba(255, 255, 255, .1));++ --ovk-text: var(--text-primary, #e8ecf3);++ --ovk-muted: var(--text-muted, var(--text-secondary, #93a0b4));++ --ovk-accent: var(--accent, #3b82f6);++ --ovk-fill: var(--accent-strong, var(--accent, #3b82f6));++ --ovk-on-fill: var(--on-accent-strong, var(--on-accent, #fff));++ --ovk-ok: var(--success, #22c55e);++ --ovk-warn: var(--warning, #f59e0b);++ --ovk-bad: var(--danger, var(--error, #ef4444));++ --ovk-live: var(--live-red, #ef4444);++ --ovk-radius: 14px;++ --ovk-radius-sm: 10px;++ --ovk-tap: 44px;++ --ovk-top: var(--ovnav-real-h, 52px); /* the navbar's height: sticky things stop under it */++ --ovk-bottom: var(--ovnav-appbar-h, 0px); /* the app bar's height on phones (navbar.js sets it) */++ --ovk-font: var(--ov-font, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif);++}++.ovk-stats { display: flex; gap: 8px; margin: 12px -16px; padding: 0 16px; overflow-x: auto; scrollbar-width: none; }++.ovk-stats::-webkit-scrollbar { display: none; }++.ovk-stats > div { display: flex; flex-direction: column-reverse; flex: 0 0 auto; min-width: 104px; box-sizing: border-box; padding: 8px 14px; border: 1px solid var(--ovk-line); border-radius: 12px; background: var(--ovk-panel); }++.ovk-stats dt { margin: 0; color: var(--ovk-muted); font-size: .76rem; line-height: 1.25; }++.ovk-stats dt a { color: inherit; text-decoration: none; }++.ovk-stat--link { position: relative; transition: border-color .15s; }++.ovk-stat--link dt a::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }++.ovk-stat--link:hover { border-color: var(--ovk-accent); }++.ovk-stat--link dt a:focus-visible { outline: none; }++.ovk-stat--link:focus-within { outline: 2px solid var(--ovk-accent); outline-offset: -2px; }++.ovk-stats dd { margin: 0; color: var(--ovk-text); font-size: 1.2rem; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.2; }++@media (min-width: 600px) {++ .ovk-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); margin-left: 0; margin-right: 0; padding: 0; overflow: visible; }++}/* ── Rooms (rooms.js): the room's accent in the theme's mode. rooms.style(id) sets --room-dark/--room-light on .ov-room;* without them the theme's accent stays. --room-accent: icons, outlines, motifs; --room-strong: text on the page;@@@ -88,6 +88,11 @@ const sc = require('../showcase');const kitCss = fs.readFileSync(path.join(__dirname, '..', 'kit.css'), 'utf8');assert.match(kitCss, /\.ovk-tabs a, \.ovk-list-head a \{ min-height: 44px; \}/, 'tab pills and list links are 44 px to the touch');assert.match(kitCss, /\.ovk-stats dt a \{ color: inherit; text-decoration: none; \}/, 'a stats label that is a link is not underlined');++ // 4.39.1: room.css stands alone (Media's front page loads it without kit.css): the same tokens and numbers strip.++ const tok = (css) => { const at = css.search(/^(:root|:where\(:root\)) \{/m); return at < 0 ? '' : css.slice(css.indexOf('{', at), css.indexOf('}', at) + 1); };++ assert.ok(tok(kitCss).length > 500 && tok(room) === tok(kitCss) && /^:where\(:root\) \{/m.test(room), 'room.css carries kit.css\'s tokens, identical, at zero specificity');++ const strip = (css) => css.split('\n').filter((l) => /^\.ovk-stats?[\s:>-]|^\.ovk-stat--link|^ {4}\.ovk-stats \{ display: grid;/.test(l)).join('\n');++ assert.ok(strip(kitCss).length > 400 && strip(room) === strip(kitCss), 'and the same numbers strip');}// ── sc.room: the front door's order, the accent, nothing invented ──
@@ -6,6 +6,14 @@ A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
@@ -6,6 +6,14 @@ A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
## Unreleased
## Unreleased
+## 4.39.1 — 2026-10-29
+
+- **room.css stands on its own.** A front page that loads room.css without kit.css (Media's, to skip kit.css's 25 KB)
+ had no `--ovk-*` tokens: cards lost their corners and colours, a card without a picture had no background, and its
+ `kit.stats` strip was a bare list. room.css now carries the same tokens (as `:where(:root)`, so kit.css's and a
+ site's own values always win) and the numbers strip. The copies are identical, so loading both files changes nothing, and
+ test/room-kit.test.js keeps them that way. room.css: +2.8 KB raw.
+
## 4.39.0 — 2026-10-29
## 4.39.0 — 2026-10-29
What the front-page passes of 10-29 (Media, Quest, Actor, Rent, Services, Community) each had to add locally:
What the front-page passes of 10-29 (Media, Quest, Actor, Rent, Services, Community) each had to add locally:
@@ -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.39.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
+**Status:** alpha, v4.39.1 (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.39.0",
+ "version": "4.39.1",
"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.39.0",
+ "version": "4.39.1",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.39.0",
+ "version": "4.39.1",
"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.39.0",
+ "version": "4.39.1",
"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": {
@@ -1,6 +1,48 @@
@@ -1,6 +1,48 @@
/* openvibe-shared/room.css — the room kit's look (4.29.1; before, in kit.css): a room's accent (rooms.js), kit.card and
/* openvibe-shared/room.css — the room kit's look (4.29.1; before, in kit.css): a room's accent (rooms.js), kit.card and
* kit.rail. A page that shows cards or rails, or wears a room's accent (sc.room() does all three), loads it after
* kit.rail. A page that shows cards or rails, or wears a room's accent (sc.room() does all three), loads it after
- * kit.css; pages that use only the kit's controls do not pay for it. Uses kit.css's tokens (--ovk-*). */
+ * kit.css, or alone on a front page that draws only cards, rails and a numbers strip (it carries the tokens and the
+ * strip since 4.39.1); pages that use only the kit's controls do not pay for it. */
+
+/* ── The kit's tokens and its numbers strip, the same as kit.css's (4.39.1): a front page that loads room.css without
+ * kit.css (cards, rails and a kit.stats strip, nothing else) still has them. Identical copies, so loading both files
+ * changes nothing (the tokens are :where(:root), so kit.css's and a site's own values always win);
+ * test/room-kit.test.js keeps them identical. ── */
+:where(:root) {
+ --ovk-bg: var(--bg-primary, #0b0f17);
+ --ovk-panel: var(--bg-card, var(--bg-secondary, #121826));
+ --ovk-panel-2: var(--bg-hover, var(--bg-tertiary, #1a2233));
+ --ovk-input: var(--bg-input, var(--bg-tertiary, #0f1522));
+ --ovk-line: var(--border, rgba(255, 255, 255, .1));
+ --ovk-text: var(--text-primary, #e8ecf3);
+ --ovk-muted: var(--text-muted, var(--text-secondary, #93a0b4));
+ --ovk-accent: var(--accent, #3b82f6);
+ --ovk-fill: var(--accent-strong, var(--accent, #3b82f6));
+ --ovk-on-fill: var(--on-accent-strong, var(--on-accent, #fff));
+ --ovk-ok: var(--success, #22c55e);
+ --ovk-warn: var(--warning, #f59e0b);
+ --ovk-bad: var(--danger, var(--error, #ef4444));
+ --ovk-live: var(--live-red, #ef4444);
+ --ovk-radius: 14px;
+ --ovk-radius-sm: 10px;
+ --ovk-tap: 44px;
+ --ovk-top: var(--ovnav-real-h, 52px); /* the navbar's height: sticky things stop under it */
+ --ovk-bottom: var(--ovnav-appbar-h, 0px); /* the app bar's height on phones (navbar.js sets it) */
+ --ovk-font: var(--ov-font, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif);
+}
+.ovk-stats { display: flex; gap: 8px; margin: 12px -16px; padding: 0 16px; overflow-x: auto; scrollbar-width: none; }
+.ovk-stats::-webkit-scrollbar { display: none; }
+.ovk-stats > div { display: flex; flex-direction: column-reverse; flex: 0 0 auto; min-width: 104px; box-sizing: border-box; padding: 8px 14px; border: 1px solid var(--ovk-line); border-radius: 12px; background: var(--ovk-panel); }
+.ovk-stats dt { margin: 0; color: var(--ovk-muted); font-size: .76rem; line-height: 1.25; }
+.ovk-stats dt a { color: inherit; text-decoration: none; }
+.ovk-stat--link { position: relative; transition: border-color .15s; }
+.ovk-stat--link dt a::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
+.ovk-stat--link:hover { border-color: var(--ovk-accent); }
+.ovk-stat--link dt a:focus-visible { outline: none; }
+.ovk-stat--link:focus-within { outline: 2px solid var(--ovk-accent); outline-offset: -2px; }
+.ovk-stats dd { margin: 0; color: var(--ovk-text); font-size: 1.2rem; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.2; }
+@media (min-width: 600px) {
+ .ovk-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); margin-left: 0; margin-right: 0; padding: 0; overflow: visible; }
+}
/* ── Rooms (rooms.js): the room's accent in the theme's mode. rooms.style(id) sets --room-dark/--room-light on .ov-room;
/* ── Rooms (rooms.js): the room's accent in the theme's mode. rooms.style(id) sets --room-dark/--room-light on .ov-room;
* without them the theme's accent stays. --room-accent: icons, outlines, motifs; --room-strong: text on the page;
* without them the theme's accent stays. --room-accent: icons, outlines, motifs; --room-strong: text on the page;
@@ -88,6 +88,11 @@ const sc = require('../showcase');
@@ -88,6 +88,11 @@ const sc = require('../showcase');
const kitCss = fs.readFileSync(path.join(__dirname, '..', 'kit.css'), 'utf8');
const kitCss = fs.readFileSync(path.join(__dirname, '..', 'kit.css'), 'utf8');
assert.match(kitCss, /\.ovk-tabs a, \.ovk-list-head a \{ min-height: 44px; \}/, 'tab pills and list links are 44 px to the touch');
assert.match(kitCss, /\.ovk-tabs a, \.ovk-list-head a \{ min-height: 44px; \}/, 'tab pills and list links are 44 px to the touch');
assert.match(kitCss, /\.ovk-stats dt a \{ color: inherit; text-decoration: none; \}/, 'a stats label that is a link is not underlined');
assert.match(kitCss, /\.ovk-stats dt a \{ color: inherit; text-decoration: none; \}/, 'a stats label that is a link is not underlined');
+ // 4.39.1: room.css stands alone (Media's front page loads it without kit.css): the same tokens and numbers strip.
+ const tok = (css) => { const at = css.search(/^(:root|:where\(:root\)) \{/m); return at < 0 ? '' : css.slice(css.indexOf('{', at), css.indexOf('}', at) + 1); };
+ assert.ok(tok(kitCss).length > 500 && tok(room) === tok(kitCss) && /^:where\(:root\) \{/m.test(room), 'room.css carries kit.css\'s tokens, identical, at zero specificity');
+ const strip = (css) => css.split('\n').filter((l) => /^\.ovk-stats?[\s:>-]|^\.ovk-stat--link|^ {4}\.ovk-stats \{ display: grid;/.test(l)).join('\n');
+ assert.ok(strip(kitCss).length > 400 && strip(room) === strip(kitCss), 'and the same numbers strip');
}
}
// ── sc.room: the front door's order, the accent, nothing invented ──
// ── sc.room: the front door's order, the accent, nothing invented ──