openvibers/openvibe.shared Commits
openvibe-shared 4.219.0: every plain element styled by the theme (radios, checkboxes, fields, details, section text) (#494)
Changes
@@@ -1,5 +1,27 @@# Changelog++## 4.219.0 — 2026-11-12++++Every plain element styled by the theme (the owner, 11-12, with circled screenshots of openvibe.exchange: "make sure all++basic elements are styled … custom with the theme system … update the little dots … reflect and improve all existing++elements … the spacing could be improved").++++- **Radios and checkboxes are drawn** under `.ov-base`: a ring in the theme's line colour, the site's fill when chosen,++ the radio's dot growing in and the checkbox's tick popping in (Calm keeps the change, Off removes the movement), a++ focus ring, and a label that holds its control is one tappable row.++- **Text fields**: the placeholder in the muted colour, an accent ring on focus, room under each field.++- **A plain details** (no class of its own) gets a chevron that turns when it opens, and no browser marker; a site's own++ details keeps its own.++- **A site's own questions in a showcase section** (a plain `<details>` straight in `.sc-sec`, as 20 homes have them)++ look like `sc.faq`'s: a card each, no browser marker, a chevron that turns, the answer padded (unstyled-sweep found++ them on Blog, Chat, Compare, Coupons, Deals, Download, Food, Games, Host, News, OpenRestream, Reviews, Run, Space, Tips,++ Trade, VIP, Vision, Wiki and Zone).++- **Words straight in a kit section** (`<p>`, `<a>`, `<details>`, lists, quotes, code) keep the section's padding; they sat++ flush against the box's edge.++++Test: `kit-layout-chrome` (the drawn radio and its dot, the filled checkbox, the plain summary's chevron, a section's++padding).++## 4.218.0 — 2026-11-12One composer, three verbs: **Post · Ask · Do** (research-2026-11-12/composer-post-ask-do.md; the owner, 11-12: "make it so@@@ -5,7 +5,7 @@**Docs:** https://openvibe.services · **For AI:** https://openvibe.network/llms.txt**Install and test:** `npm ci && npm test`--**Status:** alpha, v4.218.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed++**Status:** alpha, v4.219.0 (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.218.0",++ "version": "4.219.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)",@@@ -97,7 +97,38 @@box-sizing: border-box; inline-size: 100%; min-block-size: var(--ovk-tap); padding: .5em .8em; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius-sm);background: var(--ovk-input); color: var(--ovk-text); font: inherit; }:where(.ov-base) :where(textarea) { min-block-size: 6em; resize: vertical; }--:where(.ov-base) :where(input[type=checkbox], input[type=radio], input[type=range]) { accent-color: var(--ovk-fill); }++:where(.ov-base) :where(input:not([type=checkbox], [type=radio], [type=range], [type=color], [type=hidden]), select, textarea) { margin-block-end: 1em; }++:where(.ov-base) :where(input[type=range]) { accent-color: var(--ovk-fill); }++/* Radios and checkboxes, drawn (4.219.0; the owner, 11-12: "update the little dots … make sure all basic elements are++ styled … custom with the theme system"): the site's fill when chosen, a ring that grows its dot, a tick that pops. */++:where(.ov-base) :where(input[type=radio], input[type=checkbox]) { appearance: none; -webkit-appearance: none; display: inline-grid; place-items: center;++ inline-size: 1.25em; block-size: 1.25em; margin: 0 .45em 0 0; vertical-align: -.25em; flex: none;++ border: 2px solid color-mix(in srgb, var(--ovk-text) 32%, transparent); background: transparent; cursor: pointer;++ transition: border-color var(--ov-dur-2, .2s) ease, background-color var(--ov-dur-2, .2s) ease; }++:where(.ov-base) :where(input[type=radio]) { border-radius: 50%; }++:where(.ov-base) :where(input[type=checkbox]) { border-radius: .35em; }++:where(.ov-base) :where(input[type=radio])::before { content: ""; inline-size: .5em; block-size: .5em; border-radius: 50%; background: var(--ovk-fill);++ transform: scale(0); transition: transform var(--ov-dur-2, .2s) var(--ov-ease-out, cubic-bezier(.2, .9, .3, 1)); }++:where(.ov-base) :where(input[type=checkbox])::before { content: ""; inline-size: .72em; block-size: .72em; background: var(--ovk-on-fill);++ clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); transform: scale(0);++ transition: transform var(--ov-dur-2, .2s) var(--ov-ease-out, cubic-bezier(.2, .9, .3, 1)); }++:where(.ov-base) :where(input[type=radio], input[type=checkbox]):hover { border-color: color-mix(in srgb, var(--ovk-fill) 70%, transparent); }++:where(.ov-base) :where(input[type=radio]:checked) { border-color: var(--ovk-fill); }++:where(.ov-base) :where(input[type=checkbox]:checked) { border-color: var(--ovk-fill); background: var(--ovk-fill); }++:where(.ov-base) :where(input[type=radio]:checked, input[type=checkbox]:checked)::before { transform: scale(1); }++:where(.ov-base) :where(input[type=radio], input[type=checkbox]):focus-visible { outline: 2px solid var(--ovk-accent); outline-offset: 2px; }++:where(.ov-base) :where(label:has(> input[type=radio], > input[type=checkbox])) { display: flex; align-items: center; gap: .1em; font-weight: 500; cursor: pointer; }++:where(.ov-base) :where(input, textarea)::placeholder { color: var(--ovk-muted); opacity: .85; }++:where(.ov-base) :where(input:not([type=checkbox], [type=radio], [type=range], [type=color]), select, textarea):focus { outline: none; border-color: var(--ovk-accent);++ box-shadow: 0 0 0 3px color-mix(in srgb, var(--ovk-accent) 24%, transparent); }++/* A plain details (no class of its own) gets a chevron that turns; a site's own details keeps its own. */++:where(.ov-base) :where(details:not([class]) > summary:not([class])) { display: flex; align-items: center; justify-content: space-between; gap: .6em; list-style: none; }++:where(.ov-base) :where(details:not([class]) > summary:not([class]))::-webkit-details-marker { display: none; }++:where(.ov-base) :where(details:not([class]) > summary:not([class]))::after { content: ""; flex: none; inline-size: .5em; block-size: .5em; margin-inline-end: .2em;++ border-inline-end: 2px solid currentColor; border-block-end: 2px solid currentColor; opacity: .6; transform: rotate(45deg);++ transition: transform var(--ov-dur-2, .2s) var(--ov-ease-out, ease); }++:where(.ov-base) :where(details[open]:not([class]) > summary:not([class]))::after { transform: rotate(225deg); }++:where(.ov-base) :where(details[open]:not([class]) > summary:not([class])) { margin-block-end: .5em; }:where(.ov-base) :where(button, input[type=submit], input[type=button]) { min-block-size: var(--ovk-tap); padding: .5em 1.1em; border: 0; border-radius: 999px; cursor: pointer;background: var(--ovk-fill); color: var(--ovk-on-fill); font: inherit; font-weight: 700; }:where(.ov-base) :where(button:disabled, input:disabled, select:disabled, textarea:disabled) { opacity: .55; cursor: not-allowed; }@@@ -269,6 +269,10 @@ html[data-ov-motion=reduced] { --ov-move: .35; --ov-dur-1: .15s; --ov-dur-2: .25.ovk-section-actions .ovk-btn { min-height: 44px; white-space: normal; text-align: center; }.ovk-section-actions form { margin: 0; }.ovk-section > .ovk-empty { margin: 0 14px 14px; }++/* Words placed straight in a section keep its padding (4.219.0: a site's <p> and <a> sat flush against the box). */++.ovk-section.ovk-section > p:not([class]) { margin: 4px 14px 12px; line-height: 1.5; }++.ovk-section.ovk-section > a:not([class]) { display: inline-block; margin: 0 14px 14px; color: var(--ovk-accent); font-weight: 700; text-underline-offset: .18em; }++.ovk-section.ovk-section > :is(details, ul, ol, blockquote, pre):not([class]) { margin: 4px 14px 14px; }.ovk-count { color: var(--ovk-text); font-weight: 800; letter-spacing: 0; }.ovk-note { margin: 0; padding: 0 14px 10px; color: var(--ovk-muted); font-size: .86rem; }.ovk-section > .ovk-form, .ovk-section > form:not(.ovk-find):not(.ovk-inline) { padding: 4px 14px 14px; }@@@ -1,12 +1,12 @@{"name": "openvibe-shared",-- "version": "4.218.0",++ "version": "4.219.0","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-shared",-- "version": "4.218.0",++ "version": "4.219.0","license": "MIT","dependencies": {"clean-css": "^5.3.3",@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "4.218.0",++ "version": "4.219.0","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","bin": {@@@ -134,12 +134,13 @@ a.sc-stat:hover{border-color:var(--accent,#3b82f6)}.sc-spot h3{margin:0 0 8px;font-size:clamp(20px,2.2vw,26px);letter-spacing:-.01em}.sc-spot h3 a{color:inherit;text-decoration:none}.sc-spot h3 a:hover{color:var(--accent-light,#60a5fa)}.sc-spot p{margin:0;color:var(--text-secondary,#96a7c2);line-height:1.6;font-size:15.5px}.sc-spot-act{margin-top:16px}.sc-faq{display:grid;gap:8px;max-width:820px}--.sc-faq details{border-radius:14px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)}--.sc-faq summary{cursor:pointer;list-style:none;padding:14px 44px 14px 16px;font-weight:700;position:relative}--.sc-faq summary::-webkit-details-marker{display:none}--.sc-faq summary::after{content:'';position:absolute;right:18px;top:50%;width:8px;height:8px;margin-top:-6px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .2s;opacity:.7}--.sc-faq details[open] summary::after{transform:rotate(-135deg);margin-top:-2px}--.sc-faq p{margin:0;padding:0 16px 16px;color:var(--text-secondary,#96a7c2);line-height:1.6}++.sc-faq details,.sc-sec>details:not([class]){border-radius:14px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)}++.sc-faq summary,.sc-sec>details:not([class])>summary{cursor:pointer;list-style:none;padding:14px 44px 14px 16px;font-weight:700;position:relative}++.sc-faq summary::-webkit-details-marker,.sc-sec>details:not([class])>summary::-webkit-details-marker{display:none}++.sc-faq summary::after,.sc-sec>details:not([class])>summary::after{content:'';position:absolute;right:18px;top:50%;width:8px;height:8px;margin-top:-6px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .2s;opacity:.7}++.sc-faq details[open] summary::after,.sc-sec>details[open]:not([class])>summary::after{transform:rotate(-135deg);margin-top:-2px}++.sc-sec>details:not([class])+details:not([class]){margin-top:8px}++.sc-faq p,.sc-sec>details:not([class])>p{margin:0;padding:0 16px 16px;color:var(--text-secondary,#96a7c2);line-height:1.6}.sc-fold{border-radius:16px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)}.sc-fold>summary{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:56px;padding:0 48px 0 18px;cursor:pointer;list-style:none;position:relative}.sc-fold>summary::-webkit-details-marker{display:none}@@@ -648,12 +648,13 @@ a.sc-stat:hover{border-color:var(--accent,#3b82f6)}.sc-spot h3{margin:0 0 8px;font-size:clamp(20px,2.2vw,26px);letter-spacing:-.01em}.sc-spot h3 a{color:inherit;text-decoration:none}.sc-spot h3 a:hover{color:var(--accent-light,#60a5fa)}.sc-spot p{margin:0;color:var(--text-secondary,#96a7c2);line-height:1.6;font-size:15.5px}.sc-spot-act{margin-top:16px}.sc-faq{display:grid;gap:8px;max-width:820px}--.sc-faq details{border-radius:14px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)}--.sc-faq summary{cursor:pointer;list-style:none;padding:14px 44px 14px 16px;font-weight:700;position:relative}--.sc-faq summary::-webkit-details-marker{display:none}--.sc-faq summary::after{content:'';position:absolute;right:18px;top:50%;width:8px;height:8px;margin-top:-6px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .2s;opacity:.7}--.sc-faq details[open] summary::after{transform:rotate(-135deg);margin-top:-2px}--.sc-faq p{margin:0;padding:0 16px 16px;color:var(--text-secondary,#96a7c2);line-height:1.6}++.sc-faq details,.sc-sec>details:not([class]){border-radius:14px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)}++.sc-faq summary,.sc-sec>details:not([class])>summary{cursor:pointer;list-style:none;padding:14px 44px 14px 16px;font-weight:700;position:relative}++.sc-faq summary::-webkit-details-marker,.sc-sec>details:not([class])>summary::-webkit-details-marker{display:none}++.sc-faq summary::after,.sc-sec>details:not([class])>summary::after{content:'';position:absolute;right:18px;top:50%;width:8px;height:8px;margin-top:-6px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .2s;opacity:.7}++.sc-faq details[open] summary::after,.sc-sec>details[open]:not([class])>summary::after{transform:rotate(-135deg);margin-top:-2px}++.sc-sec>details:not([class])+details:not([class]){margin-top:8px}++.sc-faq p,.sc-sec>details:not([class])>p{margin:0;padding:0 16px 16px;color:var(--text-secondary,#96a7c2);line-height:1.6}.sc-fold{border-radius:16px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)}.sc-fold>summary{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:56px;padding:0 48px 0 18px;cursor:pointer;list-style:none;position:relative}.sc-fold>summary::-webkit-details-marker{display:none}@@@ -28,7 +28,10 @@ const PAGE = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta n<div id="co" class="ov-cover" style="--min:400px"><p>top</p><h1 class="is-centre">centred</h1><p>bottom</p></div><div id="fr" class="ov-frame" style="--ratio:2/1;width:300px"><svg viewBox="0 0 10 10"></svg></div><main class="ov-base"><h1 id="h1">A heading</h1><h2 id="h2" class="site-h2">A site's own colour</h2><p id="p">Text <a id="a" href="#">a link</a></p>--<table id="t"><tr><th>A</th><td>1</td></tr></table><input id="in"><button id="bt">Go</button></main>++<table id="t"><tr><th>A</th><td>1</td></tr></table><input id="in"><button id="bt">Go</button>++<label><input id="ra" type="radio" name="r" checked> One</label><label><input id="rb" type="radio" name="r"> Two</label><label><input id="cb" type="checkbox" checked> Yes</label>++<details id="dt"><summary id="sm">More</summary><p>Inside</p></details>++<section class="ovk-section"><h2>A section</h2><p id="sp">Words straight in it</p></section></main></body></html>`;const server = http.createServer((req, res) => {const u = new URL(req.url, 'http://x');@@@ -54,6 +57,9 @@ const MEASURE = `(() => {h2: cs('#h2').color, link: cs('#a').textDecorationLine, table: cs('#t').display,input: Math.round(r('#in').height), btnRadius: cs('#bt').borderRadius,over: document.documentElement.scrollWidth - innerWidth,++ radio: cs('#ra').appearance, radioDot: getComputedStyle(document.querySelector('#ra'), '::before').transform, radioOff: getComputedStyle(document.querySelector('#rb'), '::before').transform,++ boxBg: cs('#cb').backgroundColor, chevron: getComputedStyle(document.querySelector('#sm'), '::after').content, marker: cs('#sm').listStyleType,++ sectionPad: Math.round(r('#sp').left - document.querySelector('.ovk-section').getBoundingClientRect().left),};})()`;@@@ -90,6 +96,14 @@ const MEASURE = `(() => {assert.ok(wide.input >= 44, 'fields are a finger tall');assert.notStrictEqual(wide.btnRadius, '0px', 'buttons are finished');assert.strictEqual(phone.over, 0, 'nothing pushes a 320 px page sideways');++ // 4.219.0: radios and checkboxes drawn in the theme, a plain details gets its chevron, words in a section keep its padding.++ assert.strictEqual(wide.radio, 'none', 'a radio is drawn by the base');++ assert.ok(/matrix\(1, 0, 0, 1/.test(wide.radioDot) || wide.radioDot === 'none', `the chosen radio shows its dot (${wide.radioDot})`);++ assert.ok(/matrix\(0, 0, 0, 0/.test(wide.radioOff), `the other's dot is hidden (${wide.radioOff})`);++ assert.notStrictEqual(wide.boxBg, 'rgba(0, 0, 0, 0)', 'a ticked box is filled');++ assert.strictEqual(wide.chevron, '""', 'a plain summary has its chevron');++ assert.strictEqual(wide.marker, 'none', 'and no browser marker');++ assert.strictEqual(wide.sectionPad, 15, 'words straight in a section keep its padding (14 px inside its 1 px border)');} finally {await browser.close();server.close();
@@ -1,5 +1,27 @@
@@ -1,5 +1,27 @@
# Changelog
# Changelog
+## 4.219.0 — 2026-11-12
+
+Every plain element styled by the theme (the owner, 11-12, with circled screenshots of openvibe.exchange: "make sure all
+basic elements are styled … custom with the theme system … update the little dots … reflect and improve all existing
+elements … the spacing could be improved").
+
+- **Radios and checkboxes are drawn** under `.ov-base`: a ring in the theme's line colour, the site's fill when chosen,
+ the radio's dot growing in and the checkbox's tick popping in (Calm keeps the change, Off removes the movement), a
+ focus ring, and a label that holds its control is one tappable row.
+- **Text fields**: the placeholder in the muted colour, an accent ring on focus, room under each field.
+- **A plain details** (no class of its own) gets a chevron that turns when it opens, and no browser marker; a site's own
+ details keeps its own.
+- **A site's own questions in a showcase section** (a plain `<details>` straight in `.sc-sec`, as 20 homes have them)
+ look like `sc.faq`'s: a card each, no browser marker, a chevron that turns, the answer padded (unstyled-sweep found
+ them on Blog, Chat, Compare, Coupons, Deals, Download, Food, Games, Host, News, OpenRestream, Reviews, Run, Space, Tips,
+ Trade, VIP, Vision, Wiki and Zone).
+- **Words straight in a kit section** (`<p>`, `<a>`, `<details>`, lists, quotes, code) keep the section's padding; they sat
+ flush against the box's edge.
+
+Test: `kit-layout-chrome` (the drawn radio and its dot, the filled checkbox, the plain summary's chevron, a section's
+padding).
+
## 4.218.0 — 2026-11-12
## 4.218.0 — 2026-11-12
One composer, three verbs: **Post · Ask · Do** (research-2026-11-12/composer-post-ask-do.md; the owner, 11-12: "make it so
One composer, three verbs: **Post · Ask · Do** (research-2026-11-12/composer-post-ask-do.md; the owner, 11-12: "make it so
@@ -5,7 +5,7 @@
@@ -5,7 +5,7 @@
**Docs:** https://openvibe.services · **For AI:** https://openvibe.network/llms.txt
**Docs:** https://openvibe.services · **For AI:** https://openvibe.network/llms.txt
**Install and test:** `npm ci && npm test`
**Install and test:** `npm ci && npm test`
-**Status:** alpha, v4.218.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
+**Status:** alpha, v4.219.0 (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.218.0",
+ "version": "4.219.0",
"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)",
@@ -97,7 +97,38 @@
@@ -97,7 +97,38 @@
box-sizing: border-box; inline-size: 100%; min-block-size: var(--ovk-tap); padding: .5em .8em; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius-sm);
box-sizing: border-box; inline-size: 100%; min-block-size: var(--ovk-tap); padding: .5em .8em; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius-sm);
background: var(--ovk-input); color: var(--ovk-text); font: inherit; }
background: var(--ovk-input); color: var(--ovk-text); font: inherit; }
:where(.ov-base) :where(textarea) { min-block-size: 6em; resize: vertical; }
:where(.ov-base) :where(textarea) { min-block-size: 6em; resize: vertical; }
-:where(.ov-base) :where(input[type=checkbox], input[type=radio], input[type=range]) { accent-color: var(--ovk-fill); }
+:where(.ov-base) :where(input:not([type=checkbox], [type=radio], [type=range], [type=color], [type=hidden]), select, textarea) { margin-block-end: 1em; }
+:where(.ov-base) :where(input[type=range]) { accent-color: var(--ovk-fill); }
+/* Radios and checkboxes, drawn (4.219.0; the owner, 11-12: "update the little dots … make sure all basic elements are
+ styled … custom with the theme system"): the site's fill when chosen, a ring that grows its dot, a tick that pops. */
+:where(.ov-base) :where(input[type=radio], input[type=checkbox]) { appearance: none; -webkit-appearance: none; display: inline-grid; place-items: center;
+ inline-size: 1.25em; block-size: 1.25em; margin: 0 .45em 0 0; vertical-align: -.25em; flex: none;
+ border: 2px solid color-mix(in srgb, var(--ovk-text) 32%, transparent); background: transparent; cursor: pointer;
+ transition: border-color var(--ov-dur-2, .2s) ease, background-color var(--ov-dur-2, .2s) ease; }
+:where(.ov-base) :where(input[type=radio]) { border-radius: 50%; }
+:where(.ov-base) :where(input[type=checkbox]) { border-radius: .35em; }
+:where(.ov-base) :where(input[type=radio])::before { content: ""; inline-size: .5em; block-size: .5em; border-radius: 50%; background: var(--ovk-fill);
+ transform: scale(0); transition: transform var(--ov-dur-2, .2s) var(--ov-ease-out, cubic-bezier(.2, .9, .3, 1)); }
+:where(.ov-base) :where(input[type=checkbox])::before { content: ""; inline-size: .72em; block-size: .72em; background: var(--ovk-on-fill);
+ clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); transform: scale(0);
+ transition: transform var(--ov-dur-2, .2s) var(--ov-ease-out, cubic-bezier(.2, .9, .3, 1)); }
+:where(.ov-base) :where(input[type=radio], input[type=checkbox]):hover { border-color: color-mix(in srgb, var(--ovk-fill) 70%, transparent); }
+:where(.ov-base) :where(input[type=radio]:checked) { border-color: var(--ovk-fill); }
+:where(.ov-base) :where(input[type=checkbox]:checked) { border-color: var(--ovk-fill); background: var(--ovk-fill); }
+:where(.ov-base) :where(input[type=radio]:checked, input[type=checkbox]:checked)::before { transform: scale(1); }
+:where(.ov-base) :where(input[type=radio], input[type=checkbox]):focus-visible { outline: 2px solid var(--ovk-accent); outline-offset: 2px; }
+:where(.ov-base) :where(label:has(> input[type=radio], > input[type=checkbox])) { display: flex; align-items: center; gap: .1em; font-weight: 500; cursor: pointer; }
+:where(.ov-base) :where(input, textarea)::placeholder { color: var(--ovk-muted); opacity: .85; }
+:where(.ov-base) :where(input:not([type=checkbox], [type=radio], [type=range], [type=color]), select, textarea):focus { outline: none; border-color: var(--ovk-accent);
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--ovk-accent) 24%, transparent); }
+/* A plain details (no class of its own) gets a chevron that turns; a site's own details keeps its own. */
+:where(.ov-base) :where(details:not([class]) > summary:not([class])) { display: flex; align-items: center; justify-content: space-between; gap: .6em; list-style: none; }
+:where(.ov-base) :where(details:not([class]) > summary:not([class]))::-webkit-details-marker { display: none; }
+:where(.ov-base) :where(details:not([class]) > summary:not([class]))::after { content: ""; flex: none; inline-size: .5em; block-size: .5em; margin-inline-end: .2em;
+ border-inline-end: 2px solid currentColor; border-block-end: 2px solid currentColor; opacity: .6; transform: rotate(45deg);
+ transition: transform var(--ov-dur-2, .2s) var(--ov-ease-out, ease); }
+:where(.ov-base) :where(details[open]:not([class]) > summary:not([class]))::after { transform: rotate(225deg); }
+:where(.ov-base) :where(details[open]:not([class]) > summary:not([class])) { margin-block-end: .5em; }
:where(.ov-base) :where(button, input[type=submit], input[type=button]) { min-block-size: var(--ovk-tap); padding: .5em 1.1em; border: 0; border-radius: 999px; cursor: pointer;
:where(.ov-base) :where(button, input[type=submit], input[type=button]) { min-block-size: var(--ovk-tap); padding: .5em 1.1em; border: 0; border-radius: 999px; cursor: pointer;
background: var(--ovk-fill); color: var(--ovk-on-fill); font: inherit; font-weight: 700; }
background: var(--ovk-fill); color: var(--ovk-on-fill); font: inherit; font-weight: 700; }
:where(.ov-base) :where(button:disabled, input:disabled, select:disabled, textarea:disabled) { opacity: .55; cursor: not-allowed; }
:where(.ov-base) :where(button:disabled, input:disabled, select:disabled, textarea:disabled) { opacity: .55; cursor: not-allowed; }
@@ -269,6 +269,10 @@ html[data-ov-motion=reduced] { --ov-move: .35; --ov-dur-1: .15s; --ov-dur-2: .25
@@ -269,6 +269,10 @@ html[data-ov-motion=reduced] { --ov-move: .35; --ov-dur-1: .15s; --ov-dur-2: .25
.ovk-section-actions .ovk-btn { min-height: 44px; white-space: normal; text-align: center; }
.ovk-section-actions .ovk-btn { min-height: 44px; white-space: normal; text-align: center; }
.ovk-section-actions form { margin: 0; }
.ovk-section-actions form { margin: 0; }
.ovk-section > .ovk-empty { margin: 0 14px 14px; }
.ovk-section > .ovk-empty { margin: 0 14px 14px; }
+/* Words placed straight in a section keep its padding (4.219.0: a site's <p> and <a> sat flush against the box). */
+.ovk-section.ovk-section > p:not([class]) { margin: 4px 14px 12px; line-height: 1.5; }
+.ovk-section.ovk-section > a:not([class]) { display: inline-block; margin: 0 14px 14px; color: var(--ovk-accent); font-weight: 700; text-underline-offset: .18em; }
+.ovk-section.ovk-section > :is(details, ul, ol, blockquote, pre):not([class]) { margin: 4px 14px 14px; }
.ovk-count { color: var(--ovk-text); font-weight: 800; letter-spacing: 0; }
.ovk-count { color: var(--ovk-text); font-weight: 800; letter-spacing: 0; }
.ovk-note { margin: 0; padding: 0 14px 10px; color: var(--ovk-muted); font-size: .86rem; }
.ovk-note { margin: 0; padding: 0 14px 10px; color: var(--ovk-muted); font-size: .86rem; }
.ovk-section > .ovk-form, .ovk-section > form:not(.ovk-find):not(.ovk-inline) { padding: 4px 14px 14px; }
.ovk-section > .ovk-form, .ovk-section > form:not(.ovk-find):not(.ovk-inline) { padding: 4px 14px 14px; }
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.218.0",
+ "version": "4.219.0",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.218.0",
+ "version": "4.219.0",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"clean-css": "^5.3.3",
"clean-css": "^5.3.3",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.218.0",
+ "version": "4.219.0",
"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": {
@@ -134,12 +134,13 @@ a.sc-stat:hover{border-color:var(--accent,#3b82f6)}
@@ -134,12 +134,13 @@ a.sc-stat:hover{border-color:var(--accent,#3b82f6)}
.sc-spot h3{margin:0 0 8px;font-size:clamp(20px,2.2vw,26px);letter-spacing:-.01em}.sc-spot h3 a{color:inherit;text-decoration:none}.sc-spot h3 a:hover{color:var(--accent-light,#60a5fa)}
.sc-spot h3{margin:0 0 8px;font-size:clamp(20px,2.2vw,26px);letter-spacing:-.01em}.sc-spot h3 a{color:inherit;text-decoration:none}.sc-spot h3 a:hover{color:var(--accent-light,#60a5fa)}
.sc-spot p{margin:0;color:var(--text-secondary,#96a7c2);line-height:1.6;font-size:15.5px}.sc-spot-act{margin-top:16px}
.sc-spot p{margin:0;color:var(--text-secondary,#96a7c2);line-height:1.6;font-size:15.5px}.sc-spot-act{margin-top:16px}
.sc-faq{display:grid;gap:8px;max-width:820px}
.sc-faq{display:grid;gap:8px;max-width:820px}
-.sc-faq details{border-radius:14px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)}
-.sc-faq summary{cursor:pointer;list-style:none;padding:14px 44px 14px 16px;font-weight:700;position:relative}
-.sc-faq summary::-webkit-details-marker{display:none}
-.sc-faq summary::after{content:'';position:absolute;right:18px;top:50%;width:8px;height:8px;margin-top:-6px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .2s;opacity:.7}
-.sc-faq details[open] summary::after{transform:rotate(-135deg);margin-top:-2px}
-.sc-faq p{margin:0;padding:0 16px 16px;color:var(--text-secondary,#96a7c2);line-height:1.6}
+.sc-faq details,.sc-sec>details:not([class]){border-radius:14px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)}
+.sc-faq summary,.sc-sec>details:not([class])>summary{cursor:pointer;list-style:none;padding:14px 44px 14px 16px;font-weight:700;position:relative}
+.sc-faq summary::-webkit-details-marker,.sc-sec>details:not([class])>summary::-webkit-details-marker{display:none}
+.sc-faq summary::after,.sc-sec>details:not([class])>summary::after{content:'';position:absolute;right:18px;top:50%;width:8px;height:8px;margin-top:-6px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .2s;opacity:.7}
+.sc-faq details[open] summary::after,.sc-sec>details[open]:not([class])>summary::after{transform:rotate(-135deg);margin-top:-2px}
+.sc-sec>details:not([class])+details:not([class]){margin-top:8px}
+.sc-faq p,.sc-sec>details:not([class])>p{margin:0;padding:0 16px 16px;color:var(--text-secondary,#96a7c2);line-height:1.6}
.sc-fold{border-radius:16px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)}
.sc-fold{border-radius:16px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)}
.sc-fold>summary{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:56px;padding:0 48px 0 18px;cursor:pointer;list-style:none;position:relative}
.sc-fold>summary{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:56px;padding:0 48px 0 18px;cursor:pointer;list-style:none;position:relative}
.sc-fold>summary::-webkit-details-marker{display:none}
.sc-fold>summary::-webkit-details-marker{display:none}
@@ -648,12 +648,13 @@ a.sc-stat:hover{border-color:var(--accent,#3b82f6)}
@@ -648,12 +648,13 @@ a.sc-stat:hover{border-color:var(--accent,#3b82f6)}
.sc-spot h3{margin:0 0 8px;font-size:clamp(20px,2.2vw,26px);letter-spacing:-.01em}.sc-spot h3 a{color:inherit;text-decoration:none}.sc-spot h3 a:hover{color:var(--accent-light,#60a5fa)}
.sc-spot h3{margin:0 0 8px;font-size:clamp(20px,2.2vw,26px);letter-spacing:-.01em}.sc-spot h3 a{color:inherit;text-decoration:none}.sc-spot h3 a:hover{color:var(--accent-light,#60a5fa)}
.sc-spot p{margin:0;color:var(--text-secondary,#96a7c2);line-height:1.6;font-size:15.5px}.sc-spot-act{margin-top:16px}
.sc-spot p{margin:0;color:var(--text-secondary,#96a7c2);line-height:1.6;font-size:15.5px}.sc-spot-act{margin-top:16px}
.sc-faq{display:grid;gap:8px;max-width:820px}
.sc-faq{display:grid;gap:8px;max-width:820px}
-.sc-faq details{border-radius:14px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)}
-.sc-faq summary{cursor:pointer;list-style:none;padding:14px 44px 14px 16px;font-weight:700;position:relative}
-.sc-faq summary::-webkit-details-marker{display:none}
-.sc-faq summary::after{content:'';position:absolute;right:18px;top:50%;width:8px;height:8px;margin-top:-6px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .2s;opacity:.7}
-.sc-faq details[open] summary::after{transform:rotate(-135deg);margin-top:-2px}
-.sc-faq p{margin:0;padding:0 16px 16px;color:var(--text-secondary,#96a7c2);line-height:1.6}
+.sc-faq details,.sc-sec>details:not([class]){border-radius:14px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)}
+.sc-faq summary,.sc-sec>details:not([class])>summary{cursor:pointer;list-style:none;padding:14px 44px 14px 16px;font-weight:700;position:relative}
+.sc-faq summary::-webkit-details-marker,.sc-sec>details:not([class])>summary::-webkit-details-marker{display:none}
+.sc-faq summary::after,.sc-sec>details:not([class])>summary::after{content:'';position:absolute;right:18px;top:50%;width:8px;height:8px;margin-top:-6px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .2s;opacity:.7}
+.sc-faq details[open] summary::after,.sc-sec>details[open]:not([class])>summary::after{transform:rotate(-135deg);margin-top:-2px}
+.sc-sec>details:not([class])+details:not([class]){margin-top:8px}
+.sc-faq p,.sc-sec>details:not([class])>p{margin:0;padding:0 16px 16px;color:var(--text-secondary,#96a7c2);line-height:1.6}
.sc-fold{border-radius:16px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)}
.sc-fold{border-radius:16px;border:1px solid var(--border,rgba(255,255,255,.08));background:var(--bg-secondary,#101828)}
.sc-fold>summary{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:56px;padding:0 48px 0 18px;cursor:pointer;list-style:none;position:relative}
.sc-fold>summary{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:56px;padding:0 48px 0 18px;cursor:pointer;list-style:none;position:relative}
.sc-fold>summary::-webkit-details-marker{display:none}
.sc-fold>summary::-webkit-details-marker{display:none}
@@ -28,7 +28,10 @@ const PAGE = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta n
@@ -28,7 +28,10 @@ const PAGE = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta n
<div id="co" class="ov-cover" style="--min:400px"><p>top</p><h1 class="is-centre">centred</h1><p>bottom</p></div>
<div id="co" class="ov-cover" style="--min:400px"><p>top</p><h1 class="is-centre">centred</h1><p>bottom</p></div>
<div id="fr" class="ov-frame" style="--ratio:2/1;width:300px"><svg viewBox="0 0 10 10"></svg></div>
<div id="fr" class="ov-frame" style="--ratio:2/1;width:300px"><svg viewBox="0 0 10 10"></svg></div>
<main class="ov-base"><h1 id="h1">A heading</h1><h2 id="h2" class="site-h2">A site's own colour</h2><p id="p">Text <a id="a" href="#">a link</a></p>
<main class="ov-base"><h1 id="h1">A heading</h1><h2 id="h2" class="site-h2">A site's own colour</h2><p id="p">Text <a id="a" href="#">a link</a></p>
-<table id="t"><tr><th>A</th><td>1</td></tr></table><input id="in"><button id="bt">Go</button></main>
+<table id="t"><tr><th>A</th><td>1</td></tr></table><input id="in"><button id="bt">Go</button>
+<label><input id="ra" type="radio" name="r" checked> One</label><label><input id="rb" type="radio" name="r"> Two</label><label><input id="cb" type="checkbox" checked> Yes</label>
+<details id="dt"><summary id="sm">More</summary><p>Inside</p></details>
+<section class="ovk-section"><h2>A section</h2><p id="sp">Words straight in it</p></section></main>
</body></html>`;
</body></html>`;
const server = http.createServer((req, res) => {
const server = http.createServer((req, res) => {
const u = new URL(req.url, 'http://x');
const u = new URL(req.url, 'http://x');
@@ -54,6 +57,9 @@ const MEASURE = `(() => {
@@ -54,6 +57,9 @@ const MEASURE = `(() => {
h2: cs('#h2').color, link: cs('#a').textDecorationLine, table: cs('#t').display,
h2: cs('#h2').color, link: cs('#a').textDecorationLine, table: cs('#t').display,
input: Math.round(r('#in').height), btnRadius: cs('#bt').borderRadius,
input: Math.round(r('#in').height), btnRadius: cs('#bt').borderRadius,
over: document.documentElement.scrollWidth - innerWidth,
over: document.documentElement.scrollWidth - innerWidth,
+ radio: cs('#ra').appearance, radioDot: getComputedStyle(document.querySelector('#ra'), '::before').transform, radioOff: getComputedStyle(document.querySelector('#rb'), '::before').transform,
+ boxBg: cs('#cb').backgroundColor, chevron: getComputedStyle(document.querySelector('#sm'), '::after').content, marker: cs('#sm').listStyleType,
+ sectionPad: Math.round(r('#sp').left - document.querySelector('.ovk-section').getBoundingClientRect().left),
};
};
})()`;
})()`;
@@ -90,6 +96,14 @@ const MEASURE = `(() => {
@@ -90,6 +96,14 @@ const MEASURE = `(() => {
assert.ok(wide.input >= 44, 'fields are a finger tall');
assert.ok(wide.input >= 44, 'fields are a finger tall');
assert.notStrictEqual(wide.btnRadius, '0px', 'buttons are finished');
assert.notStrictEqual(wide.btnRadius, '0px', 'buttons are finished');
assert.strictEqual(phone.over, 0, 'nothing pushes a 320 px page sideways');
assert.strictEqual(phone.over, 0, 'nothing pushes a 320 px page sideways');
+ // 4.219.0: radios and checkboxes drawn in the theme, a plain details gets its chevron, words in a section keep its padding.
+ assert.strictEqual(wide.radio, 'none', 'a radio is drawn by the base');
+ assert.ok(/matrix\(1, 0, 0, 1/.test(wide.radioDot) || wide.radioDot === 'none', `the chosen radio shows its dot (${wide.radioDot})`);
+ assert.ok(/matrix\(0, 0, 0, 0/.test(wide.radioOff), `the other's dot is hidden (${wide.radioOff})`);
+ assert.notStrictEqual(wide.boxBg, 'rgba(0, 0, 0, 0)', 'a ticked box is filled');
+ assert.strictEqual(wide.chevron, '""', 'a plain summary has its chevron');
+ assert.strictEqual(wide.marker, 'none', 'and no browser marker');
+ assert.strictEqual(wide.sectionPad, 15, 'words straight in a section keep its padding (14 px inside its 1 px border)');
} finally {
} finally {
await browser.close();
await browser.close();
server.close();
server.close();