openvibers/openvibe.design Commits
Your look: comfort settings on top of any theme; openvibe-shared v4.45.0 (#21)
Changes
@@@ -16,7 +16,7 @@"helmet": "^8.1.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.236.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.70.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.43.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.45.0","pg": "^8.23.1"},"devDependencies": {@@@ -1114,9 +1114,9 @@}},"node_modules/openvibe-shared": {-- "version": "4.43.0",-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.43.0",-- "integrity": "sha512-OANEzCEB2QPgbkb9Yt0O67k6DxcK2pHMO1nhKYCjTI7OhUF8LW8EpLf6FJmpHmDTmpFTUq4pJ3lr3u2hPXMDYQ==",++ "version": "4.45.0",++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.45.0",++ "integrity": "sha512-/ceTZV1Nz5z5hRK1lMO0fBwDMAzeJ+ajvPa+5gZCSMjdeMR3s1qg1noiC4yOnJ14+7lfNcfVULX+ZHjieXDgmA==","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -22,7 +22,7 @@"helmet": "^8.1.0","openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.236.0","openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.70.0",-- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.43.0",++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.45.0","pg": "^8.23.1"},"engines": {@@@ -476,3 +476,9 @@ html.ar-fit .ar-shot--desktop .ar-shot-frame, html.ar-fit .ar-shot--desktop::bef.lk-site-form label { display: grid; gap: 4px; font-size: .86rem; font-weight: 700; color: var(--ovk-muted); }.lk-site-form select { min-height: 44px; max-width: 70vw; padding: 0 10px; border: 1px solid var(--ovk-line); border-radius: 12px; background: var(--ovk-input); color: var(--ovk-text); font: inherit; }.lk-site-form .ovk-btn { min-height: 44px; }++++/* /look Comfort (2026-10-29): the sample reads like any page, so each switch shows what it changes. */++.lk-sample { margin: 14px 0 0; padding: 14px 16px; border: 1px solid var(--border, #1f2d47); border-radius: 12px; background: var(--bg-secondary, #101828); }++.lk-sample p { margin: 0; }++.lk-sample-muted { color: var(--text-muted, #7386a3); }++.lk-sample-badges { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px !important; }@@@ -29,6 +29,8 @@const code = $('#lk-code');if (code) code.href = `/tokens?theme=${encodeURIComponent(id)}`;}++ // Comfort switches: [on, off] values of each display setting (openvibe-shared 4.45.0).++ const COMFORT = { contrast: ['more', 'normal'], font: ['readable', 'default'], colors: ['cb-safe', 'default'], oled: ['on', 'off'] };function check(name, value) {root.querySelectorAll(`input[name="${name}"]`).forEach((i) => { if (i.type === 'radio') i.checked = i.value === value; });}@@@ -39,6 +41,7 @@check('motion', d.motion);const s = root.querySelector('input[name="seasonal"]');if (s) s.checked = d.seasonal !== 'off';++ for (const k of Object.keys(COMFORT)) { const c = root.querySelector(`input[name="${k}"]`); if (c) c.checked = d[k] === COMFORT[k][0]; }}root.addEventListener('change', (e) => {const i = e.target;@@@ -46,6 +49,7 @@if (i.name === 'scheme') L.scheme.set(i.value);else if (i.name === 'seasonal') L.display.set({ seasonal: i.checked ? 'on' : 'off' });else if (i.name === 'text' || i.name === 'motion') L.display.set({ [i.name]: i.value });++ else if (COMFORT[i.name]) L.display.set({ [i.name]: COMFORT[i.name][i.checked ? 0 : 1] });showTheme();showSettings();say(root.getAttribute('data-signed-in') === '1' ? 'Saved. Every OpenVibe site follows.' : 'Saved on this device.');@@@ -1,7 +1,8 @@'use strict';/*** /look: your look on every OpenVibe site, in one place. The theme you wear (and the way to change it), Light / Dark /-- * Auto, text size, motion and the season's themes. The settings belong to theme-loader.js (openvibe-shared), which++ * Auto, text size, motion, the season's themes and the comfort settings (more contrast, a readable font, colour-blind-safe++ * colours, true black). The settings belong to theme-loader.js (openvibe-shared), which* applies them at once on this device and saves them to the account when the person is signed in, so every OpenVibe* site follows. public/js/look.js only wires these controls to it. Without JavaScript the page says so.*@@@ -28,6 +29,18 @@ function lookPage({ themes = [], me = {} } = {}) {+ kit.seg({ name: 'motion', label: 'Motion', options: [['auto', 'Full'], ['reduced', 'Reduced']], value: 'auto' })+ kit.switchRow({ name: 'seasonal', label: 'The season\'s themes', hint: 'Halloween and winter stand in for the default theme while they last', on: true }),});++ // Comfort (openvibe-shared 4.45.0): how any theme reads for this person. The sample under the switches is ordinary page++ // markup, so it changes as each switch does: quieter text, a link in running text, the three status colours.++ const comfort = kit.section({++ id: 'lk-comfort',++ title: 'Comfort',++ note: 'On top of any theme, on every OpenVibe site.',++ body: kit.switchRow({ name: 'contrast', label: 'More contrast', hint: 'Quieter text and borders stand out more; links in text are underlined', on: false })++ + kit.switchRow({ name: 'font', label: 'Readable font', hint: 'A typeface made for legibility, with more space between letters, words and lines', on: false })++ + kit.switchRow({ name: 'colors', label: 'Colour-blind-safe colours', hint: 'Success, warning and danger in blue, yellow and orange, never red against green', on: false })++ + kit.switchRow({ name: 'oled', label: 'True black', hint: 'Black backgrounds on dark themes, for OLED screens', on: false })++ + `<div class="lk-sample" aria-label="A sample of how text and status colours read"><p>Body text, <span class="lk-sample-muted">quieter text</span> and <a href="/look#lk-comfort">a link in a sentence</a>.</p><p class="lk-sample-badges">${kit.badge('Working', 'ok')} ${kit.badge('Ends soon', 'warn')} ${kit.badge('Expired', 'bad')}</p></div>`,++ });const themeOpts = list.map((t) => `<option value="${esc(t.id)}">${esc(t.name)}</option>`).join('');const sites = signedIn ? kit.section({id: 'lk-sites',@@@ -40,11 +53,11 @@ function lookPage({ themes = [], me = {} } = {}) {const body = `<header class="ds-page-h"><p class="ds-kicker">Your look</p><h1>Your look, everywhere</h1><p class="ds-lede">One theme and one set of comfort settings for every OpenVibe site: Live, Chat, Games, the shops and the rest.</p></header><noscript>${kit.notice({ kind: 'info', text: 'These settings need JavaScript in this browser. Your theme still follows your account.' })}</noscript>--<div class="lk" data-signed-in="${signedIn ? '1' : '0'}">${current}${settings}${sites}++<div class="lk" data-signed-in="${signedIn ? '1' : '0'}">${current}${settings}${comfort}${sites}<p class="lk-status" id="lk-status" role="status"></p>${signedIn ? `<p class="ds-fine">Every change keeps the look it replaced: <a href="/studio#ds-history">your history</a> brings any of the last fifty back.</p>` : `<p>${kit.button({ href: '/auth/login?next=%2Flook', label: 'Sign in so it follows you', kind: 'quiet', small: true })}</p>`}</div><script type="application/json" id="lk-themes">${JSON.stringify(list).replace(/</g, '\\u003c')}</script>`;-- return layout({ title: 'Your look', description: 'Your theme, light or dark, text size, motion and seasonal themes for every OpenVibe site, in one place.', path: '/look', body: `<div class="ds-wrap tk">${body}</div>`, active: '/look', scripts: ['js/look.js'] });++ return layout({ title: 'Your look', description: 'Your theme, light or dark, text size, motion, seasonal themes and comfort settings (contrast, a readable font, colour-blind-safe colours, true black) for every OpenVibe site, in one place.', path: '/look', body: `<div class="ds-wrap tk">${body}</div>`, active: '/look', scripts: ['js/look.js'] });}module.exports = { lookPage, esc, SITES };
@@ -16,7 +16,7 @@
@@ -16,7 +16,7 @@
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.236.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.236.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.70.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.70.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.43.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.45.0",
"pg": "^8.23.1"
"pg": "^8.23.1"
},
},
"devDependencies": {
"devDependencies": {
@@ -1114,9 +1114,9 @@
@@ -1114,9 +1114,9 @@
}
}
},
},
"node_modules/openvibe-shared": {
"node_modules/openvibe-shared": {
- "version": "4.43.0",
- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.43.0",
- "integrity": "sha512-OANEzCEB2QPgbkb9Yt0O67k6DxcK2pHMO1nhKYCjTI7OhUF8LW8EpLf6FJmpHmDTmpFTUq4pJ3lr3u2hPXMDYQ==",
+ "version": "4.45.0",
+ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.45.0",
+ "integrity": "sha512-/ceTZV1Nz5z5hRK1lMO0fBwDMAzeJ+ajvPa+5gZCSMjdeMR3s1qg1noiC4yOnJ14+7lfNcfVULX+ZHjieXDgmA==",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
@@ -22,7 +22,7 @@
@@ -22,7 +22,7 @@
"helmet": "^8.1.0",
"helmet": "^8.1.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.236.0",
"openvibe-contracts": "https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/v0.236.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.70.0",
"openvibe-sdk": "https://codeload.github.com/OpenVibers/OpenVibe.SDK/tar.gz/refs/tags/v0.70.0",
- "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.43.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.45.0",
"pg": "^8.23.1"
"pg": "^8.23.1"
},
},
"engines": {
"engines": {
@@ -476,3 +476,9 @@ html.ar-fit .ar-shot--desktop .ar-shot-frame, html.ar-fit .ar-shot--desktop::bef
@@ -476,3 +476,9 @@ html.ar-fit .ar-shot--desktop .ar-shot-frame, html.ar-fit .ar-shot--desktop::bef
.lk-site-form label { display: grid; gap: 4px; font-size: .86rem; font-weight: 700; color: var(--ovk-muted); }
.lk-site-form label { display: grid; gap: 4px; font-size: .86rem; font-weight: 700; color: var(--ovk-muted); }
.lk-site-form select { min-height: 44px; max-width: 70vw; padding: 0 10px; border: 1px solid var(--ovk-line); border-radius: 12px; background: var(--ovk-input); color: var(--ovk-text); font: inherit; }
.lk-site-form select { min-height: 44px; max-width: 70vw; padding: 0 10px; border: 1px solid var(--ovk-line); border-radius: 12px; background: var(--ovk-input); color: var(--ovk-text); font: inherit; }
.lk-site-form .ovk-btn { min-height: 44px; }
.lk-site-form .ovk-btn { min-height: 44px; }
+
+/* /look Comfort (2026-10-29): the sample reads like any page, so each switch shows what it changes. */
+.lk-sample { margin: 14px 0 0; padding: 14px 16px; border: 1px solid var(--border, #1f2d47); border-radius: 12px; background: var(--bg-secondary, #101828); }
+.lk-sample p { margin: 0; }
+.lk-sample-muted { color: var(--text-muted, #7386a3); }
+.lk-sample-badges { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px !important; }
@@ -29,6 +29,8 @@
@@ -29,6 +29,8 @@
const code = $('#lk-code');
const code = $('#lk-code');
if (code) code.href = `/tokens?theme=${encodeURIComponent(id)}`;
if (code) code.href = `/tokens?theme=${encodeURIComponent(id)}`;
}
}
+ // Comfort switches: [on, off] values of each display setting (openvibe-shared 4.45.0).
+ const COMFORT = { contrast: ['more', 'normal'], font: ['readable', 'default'], colors: ['cb-safe', 'default'], oled: ['on', 'off'] };
function check(name, value) {
function check(name, value) {
root.querySelectorAll(`input[name="${name}"]`).forEach((i) => { if (i.type === 'radio') i.checked = i.value === value; });
root.querySelectorAll(`input[name="${name}"]`).forEach((i) => { if (i.type === 'radio') i.checked = i.value === value; });
}
}
@@ -39,6 +41,7 @@
@@ -39,6 +41,7 @@
check('motion', d.motion);
check('motion', d.motion);
const s = root.querySelector('input[name="seasonal"]');
const s = root.querySelector('input[name="seasonal"]');
if (s) s.checked = d.seasonal !== 'off';
if (s) s.checked = d.seasonal !== 'off';
+ for (const k of Object.keys(COMFORT)) { const c = root.querySelector(`input[name="${k}"]`); if (c) c.checked = d[k] === COMFORT[k][0]; }
}
}
root.addEventListener('change', (e) => {
root.addEventListener('change', (e) => {
const i = e.target;
const i = e.target;
@@ -46,6 +49,7 @@
@@ -46,6 +49,7 @@
if (i.name === 'scheme') L.scheme.set(i.value);
if (i.name === 'scheme') L.scheme.set(i.value);
else if (i.name === 'seasonal') L.display.set({ seasonal: i.checked ? 'on' : 'off' });
else if (i.name === 'seasonal') L.display.set({ seasonal: i.checked ? 'on' : 'off' });
else if (i.name === 'text' || i.name === 'motion') L.display.set({ [i.name]: i.value });
else if (i.name === 'text' || i.name === 'motion') L.display.set({ [i.name]: i.value });
+ else if (COMFORT[i.name]) L.display.set({ [i.name]: COMFORT[i.name][i.checked ? 0 : 1] });
showTheme();
showTheme();
showSettings();
showSettings();
say(root.getAttribute('data-signed-in') === '1' ? 'Saved. Every OpenVibe site follows.' : 'Saved on this device.');
say(root.getAttribute('data-signed-in') === '1' ? 'Saved. Every OpenVibe site follows.' : 'Saved on this device.');
@@ -1,7 +1,8 @@
@@ -1,7 +1,8 @@
'use strict';
'use strict';
/**
/**
* /look: your look on every OpenVibe site, in one place. The theme you wear (and the way to change it), Light / Dark /
* /look: your look on every OpenVibe site, in one place. The theme you wear (and the way to change it), Light / Dark /
- * Auto, text size, motion and the season's themes. The settings belong to theme-loader.js (openvibe-shared), which
+ * Auto, text size, motion, the season's themes and the comfort settings (more contrast, a readable font, colour-blind-safe
+ * colours, true black). The settings belong to theme-loader.js (openvibe-shared), which
* applies them at once on this device and saves them to the account when the person is signed in, so every OpenVibe
* applies them at once on this device and saves them to the account when the person is signed in, so every OpenVibe
* site follows. public/js/look.js only wires these controls to it. Without JavaScript the page says so.
* site follows. public/js/look.js only wires these controls to it. Without JavaScript the page says so.
*
*
@@ -28,6 +29,18 @@ function lookPage({ themes = [], me = {} } = {}) {
@@ -28,6 +29,18 @@ function lookPage({ themes = [], me = {} } = {}) {
+ kit.seg({ name: 'motion', label: 'Motion', options: [['auto', 'Full'], ['reduced', 'Reduced']], value: 'auto' })
+ kit.seg({ name: 'motion', label: 'Motion', options: [['auto', 'Full'], ['reduced', 'Reduced']], value: 'auto' })
+ kit.switchRow({ name: 'seasonal', label: 'The season\'s themes', hint: 'Halloween and winter stand in for the default theme while they last', on: true }),
+ kit.switchRow({ name: 'seasonal', label: 'The season\'s themes', hint: 'Halloween and winter stand in for the default theme while they last', on: true }),
});
});
+ // Comfort (openvibe-shared 4.45.0): how any theme reads for this person. The sample under the switches is ordinary page
+ // markup, so it changes as each switch does: quieter text, a link in running text, the three status colours.
+ const comfort = kit.section({
+ id: 'lk-comfort',
+ title: 'Comfort',
+ note: 'On top of any theme, on every OpenVibe site.',
+ body: kit.switchRow({ name: 'contrast', label: 'More contrast', hint: 'Quieter text and borders stand out more; links in text are underlined', on: false })
+ + kit.switchRow({ name: 'font', label: 'Readable font', hint: 'A typeface made for legibility, with more space between letters, words and lines', on: false })
+ + kit.switchRow({ name: 'colors', label: 'Colour-blind-safe colours', hint: 'Success, warning and danger in blue, yellow and orange, never red against green', on: false })
+ + kit.switchRow({ name: 'oled', label: 'True black', hint: 'Black backgrounds on dark themes, for OLED screens', on: false })
+ + `<div class="lk-sample" aria-label="A sample of how text and status colours read"><p>Body text, <span class="lk-sample-muted">quieter text</span> and <a href="/look#lk-comfort">a link in a sentence</a>.</p><p class="lk-sample-badges">${kit.badge('Working', 'ok')} ${kit.badge('Ends soon', 'warn')} ${kit.badge('Expired', 'bad')}</p></div>`,
+ });
const themeOpts = list.map((t) => `<option value="${esc(t.id)}">${esc(t.name)}</option>`).join('');
const themeOpts = list.map((t) => `<option value="${esc(t.id)}">${esc(t.name)}</option>`).join('');
const sites = signedIn ? kit.section({
const sites = signedIn ? kit.section({
id: 'lk-sites',
id: 'lk-sites',
@@ -40,11 +53,11 @@ function lookPage({ themes = [], me = {} } = {}) {
@@ -40,11 +53,11 @@ function lookPage({ themes = [], me = {} } = {}) {
const body = `<header class="ds-page-h"><p class="ds-kicker">Your look</p><h1>Your look, everywhere</h1>
const body = `<header class="ds-page-h"><p class="ds-kicker">Your look</p><h1>Your look, everywhere</h1>
<p class="ds-lede">One theme and one set of comfort settings for every OpenVibe site: Live, Chat, Games, the shops and the rest.</p></header>
<p class="ds-lede">One theme and one set of comfort settings for every OpenVibe site: Live, Chat, Games, the shops and the rest.</p></header>
<noscript>${kit.notice({ kind: 'info', text: 'These settings need JavaScript in this browser. Your theme still follows your account.' })}</noscript>
<noscript>${kit.notice({ kind: 'info', text: 'These settings need JavaScript in this browser. Your theme still follows your account.' })}</noscript>
-<div class="lk" data-signed-in="${signedIn ? '1' : '0'}">${current}${settings}${sites}
+<div class="lk" data-signed-in="${signedIn ? '1' : '0'}">${current}${settings}${comfort}${sites}
<p class="lk-status" id="lk-status" role="status"></p>
<p class="lk-status" id="lk-status" role="status"></p>
${signedIn ? `<p class="ds-fine">Every change keeps the look it replaced: <a href="/studio#ds-history">your history</a> brings any of the last fifty back.</p>` : `<p>${kit.button({ href: '/auth/login?next=%2Flook', label: 'Sign in so it follows you', kind: 'quiet', small: true })}</p>`}</div>
${signedIn ? `<p class="ds-fine">Every change keeps the look it replaced: <a href="/studio#ds-history">your history</a> brings any of the last fifty back.</p>` : `<p>${kit.button({ href: '/auth/login?next=%2Flook', label: 'Sign in so it follows you', kind: 'quiet', small: true })}</p>`}</div>
<script type="application/json" id="lk-themes">${JSON.stringify(list).replace(/</g, '\\u003c')}</script>`;
<script type="application/json" id="lk-themes">${JSON.stringify(list).replace(/</g, '\\u003c')}</script>`;
- return layout({ title: 'Your look', description: 'Your theme, light or dark, text size, motion and seasonal themes for every OpenVibe site, in one place.', path: '/look', body: `<div class="ds-wrap tk">${body}</div>`, active: '/look', scripts: ['js/look.js'] });
+ return layout({ title: 'Your look', description: 'Your theme, light or dark, text size, motion, seasonal themes and comfort settings (contrast, a readable font, colour-blind-safe colours, true black) for every OpenVibe site, in one place.', path: '/look', body: `<div class="ds-wrap tk">${body}</div>`, active: '/look', scripts: ['js/look.js'] });
}
}
module.exports = { lookPage, esc, SITES };
module.exports = { lookPage, esc, SITES };