openvibers/openvibe.design Commits

Design: try a theme on the real sites (Live, Chat, Games, Community, Network) from its page and the studio; openvibe-shared v4.41.0 (#16)

OP OpenVibers committed 15 days ago · ae80b49a89 · 8 files, +37 −10

Each link opens that site with ?ov-try=<theme> (openvibe-shared 4.41.0): the site wears the theme for the tab, nothing saved until Use it there. In the studio the links follow the picked theme.

Changes

ddiff --git a/README.md b/README.md
iindex 96e187c..ae0243d 100644
---- a/README.md
++++ b/README.md
@@@ -197,5 +197,5 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by
<!-- versions:start -->
- openvibe-contracts: v0.236.0
- openvibe-sdk: v0.70.0
--- openvibe-shared: v4.40.0
++- openvibe-shared: v4.41.0
<!-- versions:end -->
ddiff --git a/STATUS.json b/STATUS.json
iindex 4ce7602..3b00d87 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -10,11 +10,11 @@
"domain": "openvibe.design",
"contracts": "openvibe-contracts v0.236.0",
"sdk": "openvibe-sdk v0.70.0",
-- "shared": "openvibe-shared v4.40.0",
++ "shared": "openvibe-shared v4.41.0",
"packages": [
"openvibe-contracts v0.236.0",
"openvibe-sdk v0.70.0",
-- "openvibe-shared v4.40.0"
++ "openvibe-shared v4.41.0"
],
"summary": "The service skeleton for openvibe.design: Network sign-in as OAuth client `design` (PKCE S256, httpOnly design_at/design_rt cookies) and a theme API proxy to OpenVibe.Network's /api/themes. The proxy serves the public catalogue (cached 5 minutes), one theme, the person's active theme and custom variables, saving a theme with source 'studio', its history and a restore by id, and a submission to the gallery. Writes need this site's own Origin; Network 4xx answers pass through and a Network failure is 502. The studio is designed: a gallery, previews on a Live channel, a Chat room and a shop's grid (with colour-vision views), a six-colour editor that derives the full token set, WCAG checks that gate Use, sharing to the gallery, and History with Restore. The Arena queues one prompt for three models through OpenVibe.AI (client credentials, ai.run.create), stores sanitized HTML with reported model/provider/cost/time/tokens, renders it only in sandboxed CSP-capped iframes, draws blind pairs, records one vote per person per round, and keeps Elo ratings per category plus median cost and time. The prompts, outputs and votes are an open JSONL dataset; rounds and votes follow ADR-033 export and deletion.",
"works": [
ddiff --git a/package-lock.json b/package-lock.json
iindex b60f2ea..1672a85 100644
---- a/package-lock.json
++++ b/package-lock.json
@@@ -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.40.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.41.0",
"pg": "^8.23.1"
},
"devDependencies": {
@@@ -1114,9 +1114,9 @@
}
},
"node_modules/openvibe-shared": {
-- "version": "4.40.0",
-- "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.40.0",
-- "integrity": "sha512-5eFN1Es2d5wvR9Uo8PjZUANIBU/hFcBs6SSa8t5K9vmFW7yTBeaRMWzWMfWOLnDv/kR1kwaD+w3QP4SKaoos+Q==",
++ "version": "4.41.0",
++ "resolved": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.41.0",
++ "integrity": "sha512-WMd2s+SQI2lOa0o4NDSM+jMmgBdkGIXiEfGM31EX+ASN/pWZMdsuXeRGKGsd9KJKgq5I9PT8Sf8sM25u0vyyNA==",
"license": "MIT",
"dependencies": {
"jsonwebtoken": "^9.0.0",
ddiff --git a/package.json b/package.json
iindex d2417a0..bf78c61 100644
---- a/package.json
++++ b/package.json
@@@ -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.40.0",
++ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.41.0",
"pg": "^8.23.1"
},
"engines": {
ddiff --git a/public/css/design.css b/public/css/design.css
iindex ad07705..57ad479 100644
---- a/public/css/design.css
++++ b/public/css/design.css
@@@ -409,3 +409,12 @@ html.ar-fit .ar-shot--desktop .ar-shot-frame, html.ar-fit .ar-shot--desktop::bef
}
.ds .sc-room-devlinks a { display: inline-flex; align-items: center; min-height: 44px; text-decoration: none; }
.ds .sc-room-devlinks a:hover { text-decoration: underline; }
++
++/* "Try it on the real sites" (openvibe-shared 4.41.0 ?ov-try=): a row of site pills. */
++.ds-try { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 14px 0 0; color: var(--ovk-muted); font-size: .9rem; }
++.ds-try span { flex-basis: 100%; }
++.ds-try a { display: inline-flex; align-items: center; min-height: 44px; padding: 0 16px; border-radius: 999px; border: 1px solid var(--ovk-line); background: var(--ovk-panel); color: var(--ovk-text); font-weight: 700; text-decoration: none; }
++.ds-try a:hover, .ds-try a:focus-visible { border-color: var(--room-accent, var(--ovk-accent)); color: var(--room-strong, var(--ovk-accent)); }
++.ds-editor .ds-try { margin-top: 12px; }
++.ds-editor .ds-try a { min-height: 40px; padding: 0 12px; font-size: .86rem; }
++@media (pointer: coarse) { .ds-editor .ds-try a { min-height: 44px; } }
ddiff --git a/public/js/studio.js b/public/js/studio.js
iindex b0a4904..54d767a 100644
---- a/public/js/studio.js
++++ b/public/js/studio.js
@@@ -108,6 +108,9 @@
paint();
const code = $('#ds-code');
if (code) code.href = `/tokens?theme=${encodeURIComponent(id)}`;
++ // Try it on the real sites: the picked catalog theme (edits are tried here, in the previews).
++ const tryBox = $('#ds-try');
++ if (tryBox) { tryBox.hidden = false; $$('#ds-try a[data-try]').forEach((a) => { a.href = `${a.getAttribute('data-try')}?ov-try=${encodeURIComponent(id)}`; }); }
if (!quiet) { try { history.replaceState(null, '', `/studio?theme=${encodeURIComponent(id)}`); } catch (e) { /* */ } }
say('');
}
ddiff --git a/server/views/index.js b/server/views/index.js
iindex 9a0639c..c3848c7 100644
---- a/server/views/index.js
++++ b/server/views/index.js
@@@ -33,6 +33,15 @@ function varsStyle(v) {
return Object.entries(v || {}).filter(([k, x]) => /^--[a-z][a-z0-9-]{1,40}$/.test(k) && /^(#[0-9a-f]{3,8}|rgba?\([0-9., ]+\)|[0-9., ]+)$/i.test(String(x))).map(([k, x]) => `${k}:${x}`).join(';');
}
++// The sites a theme can be tried on for real (openvibe-shared 4.41.0's ?ov-try=: painted on that site for the tab, nothing
++// saved until Use it there).
++const TRY_SITES = [['Live', 'https://openvibe.live/'], ['Chat', 'https://openvibe.chat/'], ['Games', 'https://openvibe.games/'], ['Community', 'https://openvibe.community/'], ['Network', 'https://openvibe.network/']];
++/** "Try it on" links for a catalog theme (id: its slug); data-try lets the studio point them at the picked theme. */
++function tryOn(id) {
++ const q = id ? `?ov-try=${encodeURIComponent(id)}` : '';
++ return `<p class="ds-try"><span>Try it on the real sites, nothing saved:</span>${TRY_SITES.map(([name, url]) => `<a href="${esc(url + q)}" data-try="${esc(url)}" rel="noopener">${esc(name)}</a>`).join('')}</p>`;
++}
++
/** A theme as a swatch button (the studio) or link (the home and gallery without JavaScript). */
function swatch(t, { tag = 'a' } = {}) {
const v = t.variables || {};
@@@ -146,6 +155,7 @@ ${signedIn ? `<p class="ds-sign ds-fine">Signed in as <b>${esc((me.user && (me.u
<div class="ds-actions"><button type="button" class="ovk-btn ovk-btn--primary" id="ds-use" disabled>${signedIn ? 'Use this theme' : 'Sign in to use it'}</button>
<button type="button" class="ovk-btn ovk-btn--quiet" id="ds-reset" disabled>Undo my edits</button>
<a class="ovk-btn ovk-btn--quiet" id="ds-code" href="/tokens" title="This theme for the web, Android, iOS, Flutter and Figma">As code</a></div>
++<div id="ds-try" hidden>${tryOn('')}</div>
<p class="ds-status" id="ds-status" role="status"></p>
<details class="ds-submit"><summary>Share it in the gallery</summary><form id="ds-submit-form"><label>Name <input type="text" id="ds-name" maxlength="60" minlength="2" required placeholder="Cozy autumn"></label>
<label>One line about it <input type="text" id="ds-desc" maxlength="300" placeholder="Warm browns and a pumpkin accent"></label>
@@@ -160,7 +170,7 @@ function theme({ theme: t } = {}) {
const v = (t && t.variables) || {};
const rows = CORE.map(([k, l]) => (HEX.test(String(v[k] || '')) ? `<div><dt>${esc(l)}</dt><dd><i style="background:${esc(v[k])}"></i><code>${esc(v[k])}</code></dd></div>` : '')).join('');
const body = `<header class="ds-page-h"><p class="ds-kicker">${t.mode === 'light' ? 'Light theme' : 'Dark theme'}</p><h1>${esc(t.name)}</h1>${t.description ? `<p class="ds-lede">${esc(t.description)}</p>` : ''}
--<p class="ds-act">${kit.button({ href: `/studio?theme=${encodeURIComponent(t.id)}`, label: 'Open in the studio' })} ${kit.button({ href: `/tokens?theme=${encodeURIComponent(t.slug || t.id)}`, label: 'As code', kind: 'quiet' })}</p></header>
++<p class="ds-act">${kit.button({ href: `/studio?theme=${encodeURIComponent(t.id)}`, label: 'Open in the studio' })} ${kit.button({ href: `/tokens?theme=${encodeURIComponent(t.slug || t.id)}`, label: 'As code', kind: 'quiet' })}</p>${tryOn(t.slug || t.id)}</header>
${rows ? `<dl class="ds-colours">${rows}</dl>` : ''}
<section aria-labelledby="ds-pv-h"><h2 id="ds-pv-h">How it looks</h2><div class="ds-previews ds-previews--static" style="${esc(varsStyle(v))}">${previews()}</div></section>`;
return layout({ title: `${t.name}: an OpenVibe theme`, description: t.description || `${t.name}, a ${t.mode === 'light' ? 'light' : 'dark'} OpenVibe theme: its colours and how Live, Chat and a shop look in it.`, path: `/t/${encodeURIComponent(t.slug || t.id)}`, body: `<div class="ds-wrap">${body}</div>`, active: '/studio' });
ddiff --git a/test/tokens.test.js b/test/tokens.test.js
iindex 670c74b..47229b3 100644
---- a/test/tokens.test.js
++++ b/test/tokens.test.js
@@@ -78,10 +78,15 @@ const { boot, check, done } = require('./helpers/boot');
assert.match(page.text, /object EvilImportOsTokens/);
});
-- await check('the studio and a theme page link to the theme as code', async () => {
++ await check('the studio and a theme page link to the theme as code and to trying it on the real sites', async () => {
assert.match((await t.get('/studio')).text, /id="ds-code" href="\/tokens"/);
assert.match((await t.get('/t/vibe')).text, /href="\/tokens\?theme=vibe"/);
assert.match((await t.get('/developers')).text, /GET \/api\/v1\/tokens\/:theme\?format=/);
++ // Try it on the real sites (openvibe-shared 4.41.0 ?ov-try=): the theme page links each site with the theme's name.
++ const page = (await t.get('/t/vibe')).text;
++ assert.match(page, /href="https:\/\/openvibe\.live\/\?ov-try=vibe"/);
++ assert.match(page, /href="https:\/\/openvibe\.chat\/\?ov-try=vibe"/);
++ assert.match((await t.get('/studio')).text, /<div id="ds-try" hidden>[\s\S]*data-try="https:\/\/openvibe\.live\/"/, 'the studio points them at the picked theme');
});
await t.close();