openvibers/openvibe.sites Commits

openvibe-shared v2.20.4 (was v2.20.3) (#40)

OP OpenVibers committed 10 Oct 2026 · 4b9f6f3a05 · 5 files, +28 −27

* openvibe-shared v2.20.4 (was v2.20.3)

Opened by scripts/pin-bump.js in OpenVibers/OpenVibe.Shared after the v2.20.4 release.

* Rebuild dist/_shared for openvibe-shared v2.20.4 (ring icons without SVG-internal transforms)

Changes

ddiff --git a/README.md b/README.md
iindex 1e45bc3..df10a2d 100644
---- a/README.md
++++ b/README.md
@@@ -72,7 +72,7 @@ The pinned `openvibe-contracts` release supplies the catalog `sites.json` mirror
## Depends on
- `openvibe-contracts` v0.127.0 — `scripts/sync-catalog.js` reads `openvibe-contracts/lib/products` (`catalog()`) to generate `sites.json`.
--- `openvibe-shared` v2.20.3 — `build.js` uses `legal` for the terms, privacy and dmca pages, `files` for the `BROWSER` list copied to `dist/_shared/`, and `app-icon` for the manifest and head tags; `test/run.js` uses `test-runner`.
++- `openvibe-shared` v2.20.4 — `build.js` uses `legal` for the terms, privacy and dmca pages, `files` for the `BROWSER` list copied to `dist/_shared/`, and `app-icon` for the manifest and head tags; `test/run.js` uses `test-runner`.
- OpenVibe.Host — the deploy wrapper calls `ovhost deploy sites --restart`, or `ovhost plan sites --restart` with `DRY_RUN=1`.
- OpenVibe.Network at `https://openvibe.network` — the frozen pages and shared browser files load its assets and API in the browser; `NET.networkUrl` in `build.js` is that base.
@@@ -108,5 +108,5 @@ Report security issues through [SECURITY.md](SECURITY.md).
<!-- versions:start -->
- openvibe-contracts: v0.127.0
--- openvibe-shared: v2.20.3
++- openvibe-shared: v2.20.4
<!-- versions:end -->
ddiff --git a/STATUS.json b/STATUS.json
iindex 03cc35a..42a559b 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -23,7 +23,7 @@
"status.openvibe.network (status notice)"
],
"contracts": "openvibe-contracts v0.127.0: sites.json mirrors products.catalog() (scripts/sync-catalog.js)",
-- "shared": "openvibe-shared v2.20.3",
++ "shared": "openvibe-shared v2.20.4",
"deployed": true,
"deployment": "Deployed with `sudo ovhost deploy sites --restart` (OpenVibe.Host strategy static-build since the 2026-09-27 cutover): /opt/openvibe.sites, nginx serves dist/<domain>/ through deploy/nginx/<domain>.conf; no unit, no port, no env file",
"features": [
ddiff --git a/dist/_shared/ov-icons.js b/dist/_shared/ov-icons.js
iindex fd8a4e0..19c8d20 100644
---- a/dist/_shared/ov-icons.js
++++ b/dist/_shared/ov-icons.js
@@@ -15,8 +15,11 @@
* OpenVibeIcons.set(el, { progress, state, icon })
* OpenVibeIcons.svg(name, size) markup string, for server-side / templates
*
-- * Effects (data-fx): orbit (default) · pulse · draw · bounce · none. Motion is transform/opacity/
-- * stroke only and stops under prefers-reduced-motion. Unknown names fall back to the OV mark "V".
++ * Effects (data-fx): orbit (default) · pulse · draw · bounce · none. Motion is opacity/stroke inside the
++ * SVG and transform on the <svg> element only: no CSS transform ever touches an element inside the SVG
++ * (transform-box/transform-origin on SVG children left glyphs and whole icons unpainted on phones while
++ * scrolling), and hover effects apply only where a pointer can hover, so a thumb scrolling over a link
++ * starts nothing. Motion stops under prefers-reduced-motion. Unknown names fall back to the OV mark "V".
*/
(function (root) {
'use strict';
@@@ -83,26 +86,25 @@
const CSS = `
.ov-icon{--ovi-size:32px;display:inline-grid;place-items:center;width:var(--ovi-size);height:var(--ovi-size);flex:none;vertical-align:middle;line-height:0;color:var(--ovi-accent,var(--accent,#3b82f6));position:relative}
--.ov-icon svg{width:100%;height:100%;overflow:visible;display:block}
++.ov-icon svg{width:100%;height:100%;overflow:visible;display:block;transition:transform .25s cubic-bezier(.2,1.4,.3,1)}
.ov-icon .ovi-bg{fill:currentColor;opacity:.1;transition:opacity .25s}
.ov-icon .ovi-ring{fill:none;stroke:currentColor;stroke-width:2.4;opacity:.26}
--.ov-icon .ovi-comet{fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-dasharray:26 100;transform-origin:24px 24px;animation:oviSpin 3.6s linear infinite}
--.ov-icon .ovi-prog{fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;transform:rotate(-90deg);transform-origin:24px 24px;stroke-dasharray:125.7;stroke-dashoffset:125.7;transition:stroke-dashoffset .35s cubic-bezier(.2,.8,.2,1),opacity .2s;opacity:0}
--.ov-icon .ovi-glyph{fill:none;stroke:var(--ovi-glyph,var(--text-primary,#e6edf7));stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;transform-box:view-box;transform-origin:12px 12px;transition:transform .25s cubic-bezier(.2,1.4,.3,1),stroke .2s}
--.ov-icon .ovi-glyph--solid{fill:var(--ovi-glyph,var(--text-primary,#e6edf7));stroke:none;transition:transform .25s cubic-bezier(.2,1.4,.3,1),fill .2s}
++.ov-icon .ovi-comet{fill:none;stroke:currentColor;stroke-width:2.4;stroke-linecap:round;stroke-dasharray:26 99.66;animation:oviSpin 3.6s linear infinite}
++.ov-icon .ovi-prog{fill:none;stroke:currentColor;stroke-width:3;stroke-linecap:round;stroke-dasharray:125.7;stroke-dashoffset:125.7;transition:stroke-dashoffset .35s cubic-bezier(.2,.8,.2,1),opacity .2s;opacity:0}
++.ov-icon .ovi-glyph{fill:none;stroke:var(--ovi-glyph,var(--text-primary,#e6edf7));stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round;transition:stroke .2s}
++.ov-icon .ovi-glyph--solid{fill:var(--ovi-glyph,var(--text-primary,#e6edf7));stroke:none;transition:fill .2s}
.ov-icon .ovi-glyph circle[r=".6"],.ov-icon .ovi-glyph circle[r=".7"]{fill:currentColor;stroke:none}
.ov-icon[style*="--ovi-size:2"] .ovi-glyph,.ov-icon[data-size^="2"] .ovi-glyph{stroke-width:2.3}
.ov-icon[style*="--ovi-size:2"] .ovi-ring,.ov-icon[data-size^="2"] .ovi-ring{stroke-width:3;opacity:.34}
--.ov-icon:hover .ovi-glyph,a:hover>.ov-icon .ovi-glyph,button:hover>.ov-icon .ovi-glyph{transform:scale(1.1)}
--.ov-icon:hover .ovi-bg,a:hover>.ov-icon .ovi-bg{opacity:.18}
++@media (hover:hover){.ov-icon:hover svg,a:hover>.ov-icon svg,button:hover>.ov-icon svg{transform:scale(1.06)}.ov-icon:hover .ovi-bg,a:hover>.ov-icon .ovi-bg{opacity:.18}}
.ov-icon[data-fx=none] .ovi-comet{display:none}
.ov-icon[data-fx=pulse] .ovi-comet{display:none}
--.ov-icon[data-fx=pulse] .ovi-bg{animation:oviPulse 2s ease-in-out infinite;transform-origin:24px 24px}
++.ov-icon[data-fx=pulse] .ovi-bg{animation:oviPulse 2s ease-in-out infinite}
.ov-icon[data-fx=draw] .ovi-glyph path{stroke-dasharray:80;animation:oviDraw 1.2s cubic-bezier(.2,.8,.2,1) both}
.ov-icon[data-fx=bounce] svg{animation:oviBounce 1.8s cubic-bezier(.3,1.4,.4,1) infinite}
.ov-icon[data-progress] .ovi-prog{opacity:1}
.ov-icon[data-progress] .ovi-comet{opacity:0}
--.ov-icon[data-state=busy] .ovi-comet{animation-duration:1.1s;opacity:1;stroke-dasharray:40 86}
++.ov-icon[data-state=busy] .ovi-comet{animation-duration:1.1s;opacity:1;stroke-dasharray:40 85.66}
.ov-icon[data-state=busy][data-progress] .ovi-comet{opacity:.35}
.ov-icon[data-state=ok]{--ovi-accent:var(--success,#22c55e)}
.ov-icon[data-state=error]{--ovi-accent:var(--danger,#ef4444)}
@@@ -110,8 +112,8 @@
.ov-icon[data-state=ok] .ovi-ring,.ov-icon[data-state=error] .ovi-ring{opacity:.9}
.ov-icon[data-state=ok] svg{animation:oviPop .5s cubic-bezier(.2,1.6,.3,1)}
.ov-icon[data-state=error] svg{animation:oviShake .4s ease}
--@keyframes oviSpin{to{transform:rotate(360deg)}}
--@keyframes oviPulse{0%,100%{transform:scale(.9);opacity:.08}50%{transform:scale(1.06);opacity:.22}}
++@keyframes oviSpin{from{stroke-dashoffset:0}to{stroke-dashoffset:-125.66}}
++@keyframes oviPulse{0%,100%{opacity:.08}50%{opacity:.24}}
@keyframes oviDraw{from{stroke-dashoffset:80}to{stroke-dashoffset:0}}
@keyframes oviBounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-6%)}}
@keyframes oviPop{0%{transform:scale(.8)}60%{transform:scale(1.12)}100%{transform:scale(1)}}
@@@ -174,11 +176,10 @@
if (!solid) return forSprite ? def.glyph : `<g class="ovi-glyph">${def.glyph}</g>`;
const [w, d] = solid; const sc = 18 / 512; const tx = (24 - w * sc) / 2, ty = 3;
const paint = forSprite ? ' fill="currentColor" stroke="none" style="fill:var(--ovi-glyph,var(--text-primary,#e6edf7))"' : '';
-- // The position/scale transform must live on a plain (unclassed) <g>, not on the .ovi-glyph
-- // element itself: that class sets transform-box/transform-origin for the hover-scale
-- // animation, and browsers resolve a "transform" *attribute* through the same CSS transform
-- // pipeline — so pairing it with a non-default transform-origin pivots the glyph around
-- // (12,12) instead of applying it in place, throwing every solid glyph off-centre.
++ // The position/scale transform lives on a plain (unclassed) <g>, and no stylesheet rule sets a
++ // CSS transform, transform-box or transform-origin on anything inside the SVG: browsers resolve a
++ // "transform" *attribute* through the same CSS transform pipeline, so a styled origin once pivoted
++ // every solid glyph off-centre, and transforms on SVG children left icons unpainted on phones.
return `<g class="ovi-glyph ovi-glyph--solid"${paint}><g transform="translate(${tx.toFixed(2)} ${ty}) scale(${sc.toFixed(5)})"><path d="${d}"/></g></g>`;
}
@@@ -192,7 +193,7 @@
const key = resolve(name); const def = registry[key];
const off = def.offset || OFFSETS[key] || [0, 0]; // measured optical centring, see scripts/measure-icons.js
const s = size ? ` width="${size}" height="${size}"` : '';
-- return `<svg viewBox="0 0 48 48"${s} aria-hidden="true" focusable="false"><circle class="ovi-bg" cx="24" cy="24" r="21"/><circle class="ovi-ring" cx="24" cy="24" r="20"/><circle class="ovi-comet" cx="24" cy="24" r="20"/><circle class="ovi-prog" cx="24" cy="24" r="20"/><g transform="translate(${(13 + off[0] * .9167).toFixed(2)} ${(13 + off[1] * .9167).toFixed(2)}) scale(.9167)">${glyphMarkup(key, def, false)}</g></svg>`;
++ return `<svg viewBox="0 0 48 48"${s} aria-hidden="true" focusable="false"><circle class="ovi-bg" cx="24" cy="24" r="21"/><circle class="ovi-ring" cx="24" cy="24" r="20"/><circle class="ovi-comet" cx="24" cy="24" r="20"/><path class="ovi-prog" d="M24 4a20 20 0 1 1 0 40a20 20 0 1 1 0-40"/><g transform="translate(${(13 + off[0] * .9167).toFixed(2)} ${(13 + off[1] * .9167).toFixed(2)}) scale(.9167)">${glyphMarkup(key, def, false)}</g></svg>`;
}
// ── Sprite mode (server-rendered lists) ──────────────────────────────────────────────
ddiff --git a/package-lock.json b/package-lock.json
iindex 68d867a..590c86d 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -7,7 +7,7 @@
"name": "openvibe-sites",
"dependencies": {
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.127.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.20.3"
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.20.4"
},
"engines": {
"node": ">=20"
@@@ -197,9 +197,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "2.20.3",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.20.3",
-- "integrity": "sha512-SXw6W4oHxbSHXG0+YFF4ATyz6xbNXD5gq/7GiS9fxE0zuYXCDri+dMpj+NTN8eytp57liBTAC4RMEluFsulbqw==",
++ "version": "2.20.4",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.20.4",
++ "integrity": "sha512-F1D3Za0++7OVwFCV/FLkBBa5coYHCNCrqsTf2oE5uZcBuccKPzdQEJ9irZn2sEA9Xn4c0X4AALDzYgJqKb2tRw==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0"
ddiff --git a/package.json b/package.json
iindex 99ce9e5..2ed284d 100644
---- a/package.json
++++ b/package.json
@@@ -8,7 +8,7 @@
},
"dependencies": {
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.127.0",
-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.20.3"
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v2.20.4"
},
"engines": {
"node": ">=20"