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)
Changes
@@@ -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 -->@@@ -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": [@@@ -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",@@@ -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": {@@@ -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; } }@@@ -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('');}@@@ -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' });@@@ -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();
@@ -197,5 +197,5 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by
@@ -197,5 +197,5 @@ Part of the [OpenVibe network](https://openvibe.network). Built in the open by
<!-- versions:start -->
<!-- versions:start -->
- openvibe-contracts: v0.236.0
- openvibe-contracts: v0.236.0
- openvibe-sdk: v0.70.0
- openvibe-sdk: v0.70.0
-- openvibe-shared: v4.40.0
+- openvibe-shared: v4.41.0
<!-- versions:end -->
<!-- versions:end -->
@@ -10,11 +10,11 @@
@@ -10,11 +10,11 @@
"domain": "openvibe.design",
"domain": "openvibe.design",
"contracts": "openvibe-contracts v0.236.0",
"contracts": "openvibe-contracts v0.236.0",
"sdk": "openvibe-sdk v0.70.0",
"sdk": "openvibe-sdk v0.70.0",
- "shared": "openvibe-shared v4.40.0",
+ "shared": "openvibe-shared v4.41.0",
"packages": [
"packages": [
"openvibe-contracts v0.236.0",
"openvibe-contracts v0.236.0",
"openvibe-sdk v0.70.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.",
"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": [
"works": [
@@ -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.40.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.41.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.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",
"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.40.0",
+ "openvibe-shared": "https://codeload.github.com/OpenVibers/OpenVibe.Shared/tar.gz/refs/tags/v4.41.0",
"pg": "^8.23.1"
"pg": "^8.23.1"
},
},
"engines": {
"engines": {
@@ -409,3 +409,12 @@ html.ar-fit .ar-shot--desktop .ar-shot-frame, html.ar-fit .ar-shot--desktop::bef
@@ -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 { display: inline-flex; align-items: center; min-height: 44px; text-decoration: none; }
.ds .sc-room-devlinks a:hover { text-decoration: underline; }
.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; } }
@@ -108,6 +108,9 @@
@@ -108,6 +108,9 @@
paint();
paint();
const code = $('#ds-code');
const code = $('#ds-code');
if (code) code.href = `/tokens?theme=${encodeURIComponent(id)}`;
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) { /* */ } }
if (!quiet) { try { history.replaceState(null, '', `/studio?theme=${encodeURIComponent(id)}`); } catch (e) { /* */ } }
say('');
say('');
}
}
@@ -33,6 +33,15 @@ function varsStyle(v) {
@@ -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(';');
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). */
/** A theme as a swatch button (the studio) or link (the home and gallery without JavaScript). */
function swatch(t, { tag = 'a' } = {}) {
function swatch(t, { tag = 'a' } = {}) {
const v = t.variables || {};
const v = t.variables || {};
@@ -146,6 +155,7 @@ ${signedIn ? `<p class="ds-sign ds-fine">Signed in as <b>${esc((me.user && (me.u
@@ -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>
<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>
<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>
<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>
<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>
<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>
<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 } = {}) {
@@ -160,7 +170,7 @@ function theme({ theme: t } = {}) {
const v = (t && t.variables) || {};
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 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>` : ''}
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>` : ''}
${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>`;
<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' });
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' });
@@ -78,10 +78,15 @@ const { boot, check, done } = require('./helpers/boot');
@@ -78,10 +78,15 @@ const { boot, check, done } = require('./helpers/boot');
assert.match(page.text, /object EvilImportOsTokens/);
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('/studio')).text, /id="ds-code" href="\/tokens"/);
assert.match((await t.get('/t/vibe')).text, /href="\/tokens\?theme=vibe"/);
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=/);
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();
await t.close();