openvibers/openvibe.shared Commits

openvibe-shared 4.219.0: every plain element styled by the theme (radios, checkboxes, fields, details, section text) (#494)

OP OpenVibers committed 1 days ago · a93d516f8a · 10 files, +92 −19

* openvibe-shared 4.219.0: every plain element styled by the theme (radios, checkboxes, fields, details, section text)

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.
- **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).

* Showcase: a site's own questions in a section look like sc.faq's (no browser marker, a chevron, padded answers)

Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 681b53a..74b5ea3 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -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-12
One composer, three verbs: **Post · Ask · Do** (research-2026-11-12/composer-post-ask-do.md; the owner, 11-12: "make it so
ddiff --git a/README.md b/README.md
iindex a205458..2c1a2e6 100644
---- a/README.md
++++ b/README.md
@@@ -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 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 02d6659..3c4d2d4 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -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)",
ddiff --git a/kit-layout.css b/kit-layout.css
iindex 4774b1a..c0f2a8d 100644
---- a/kit-layout.css
++++ b/kit-layout.css
@@@ -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; }
ddiff --git a/kit.css b/kit.css
iindex 8cde492..b5c380e 100644
---- a/kit.css
++++ b/kit.css
@@@ -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; }
ddiff --git a/package-lock.json b/package-lock.json
iindex e2fac8c..16581f0 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -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",
ddiff --git a/package.json b/package.json
iindex 688885a..d2f900a 100644
---- a/package.json
++++ b/package.json
@@@ -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": {
ddiff --git a/showcase.css b/showcase.css
iindex 83860e9..dfdad1b 100644
---- a/showcase.css
++++ b/showcase.css
@@@ -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}
ddiff --git a/showcase.js b/showcase.js
iindex 07dca1a..e9ac4d1 100644
---- a/showcase.js
++++ b/showcase.js
@@@ -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}
ddiff --git a/test/kit-layout-chrome.test.js b/test/kit-layout-chrome.test.js
iindex d37a962..d4f36f3 100644
---- a/test/kit-layout-chrome.test.js
++++ b/test/kit-layout-chrome.test.js
@@@ -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();