openvibers/openvibe.live Commits
Live guides: '(optional)' stays beside its label in the go-live setup (#135)
Changes
@@@ -117,6 +117,7 @@ body.ovg-lock { overflow: hidden; }.ovg-error { padding: 12px; border-radius: 12px; background: color-mix(in srgb, #ff5a5f 12%, var(--bg-secondary)); border: 1px solid #ff5a5f; }.ovg-field { display: grid; gap: 6px; font-size: 0.8rem; font-weight: 700; color: var(--text-secondary); margin-bottom: 10px; }.ovg-field-label { font-size: 0.8rem; font-weight: 700; color: var(--text-secondary); margin: 2px 0 6px; }++.ovg-field-text .muted { font-weight: 500; color: var(--text-muted); }.ovg-prefix { display: flex; align-items: center; gap: 0; } .ovg-prefix span { padding: 9px 12px; border: 1px solid var(--border); border-right: 0; border-radius: 10px 0 0 10px; background: var(--bg-tertiary); font-weight: 900; } .ovg-prefix .form-input { border-radius: 0 10px 10px 0; }.ovg-drop { display: flex; align-items: center; justify-content: center; gap: 10px; min-height: 96px; border: 2px dashed color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 14px; background: var(--bg-secondary); cursor: pointer; color: var(--text-secondary); font-weight: 700; padding: 12px; transition: border-color .15s, background .15s; }.ovg-drop:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, var(--bg-secondary)); }@@@ -201,7 +201,8 @@secret: (label, value) => `<div class="ovg-kv"><span>${cfg.esc(label)}</span><code class="ovg-secret" data-secret="${cfg.esc(value)}">••••••••••••••••</code><button type="button" class="ovg-copy" data-copy="${cfg.esc(value)}"><i class="fa-regular fa-copy"></i> Copy</button><button type="button" class="ovg-copy" data-reveal="1"><i class="fa-regular fa-eye"></i> Show</button></div>`,steps: (items) => `<ol class="ovg-obs">${items.map(i => `<li>${i}</li>`).join('')}</ol>`,chips: (items, name) => `<div class="ovg-chips" data-chips="${cfg.esc(name || '')}">${items.map(([v, l, i]) => `<button type="button" class="ovg-chip" data-chip="${cfg.esc(v)}">${i ? `<i class="fa-solid ${i}"></i> ` : ''}${cfg.esc(l)}</button>`).join('')}</div>`,-- field: (label, inputHtml) => `<label class="ovg-field">${label}${inputHtml}</label>`,++ // The label is one grid row (its text and any "(optional)" note together), the control the next.++ field: (label, inputHtml) => `<label class="ovg-field"><span class="ovg-field-text">${label}</span>${inputHtml}</label>`,wire(root) {root.querySelectorAll('[data-copy]').forEach(b => b.addEventListener('click', async () => { try { await navigator.clipboard.writeText(b.dataset.copy); b.innerHTML = '<i class="fa-solid fa-check"></i> Copied'; setTimeout(() => { b.innerHTML = '<i class="fa-regular fa-copy"></i> Copy'; }, 1500); } catch { cfg.toast('Copy failed — long-press to copy', 'error'); } }));root.querySelectorAll('[data-reveal]').forEach(b => b.addEventListener('click', () => { const c = b.parentElement.querySelector('.ovg-secret'); if (!c) return; const on = c.dataset.on === '1'; c.textContent = on ? '••••••••••••••••' : c.dataset.secret; c.dataset.on = on ? '' : '1'; b.innerHTML = on ? '<i class="fa-regular fa-eye"></i> Show' : '<i class="fa-regular fa-eye-slash"></i> Hide'; }));nnew file mode 100644@@@ -0,0 +1,13 @@++'use strict';++// The guides' form field (public/js/ov-guide.js ui.field): .ovg-field is a grid, so the label's text and its++// "(optional)" note must be ONE grid item, or the note lands on a row of its own in heading weight (the go-live++// setup's "One line about it", 2026-10-15). The note itself is lighter than the label.++const assert = require('assert');++const fs = require('fs');++const path = require('path');++++const js = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'ov-guide.js'), 'utf8');++const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'ov-guide.css'), 'utf8');++assert.ok(js.includes('field: (label, inputHtml) => `<label class="ovg-field"><span class="ovg-field-text">${label}</span>${inputHtml}</label>`'), 'the label text is wrapped as one grid item');++assert.match(css, /\.ovg-field-text \.muted \{ font-weight: 500;/, 'the optional note is lighter than the label');++console.log('guide field: the label and its note share one row');
@@ -117,6 +117,7 @@ body.ovg-lock { overflow: hidden; }
@@ -117,6 +117,7 @@ body.ovg-lock { overflow: hidden; }
.ovg-error { padding: 12px; border-radius: 12px; background: color-mix(in srgb, #ff5a5f 12%, var(--bg-secondary)); border: 1px solid #ff5a5f; }
.ovg-error { padding: 12px; border-radius: 12px; background: color-mix(in srgb, #ff5a5f 12%, var(--bg-secondary)); border: 1px solid #ff5a5f; }
.ovg-field { display: grid; gap: 6px; font-size: 0.8rem; font-weight: 700; color: var(--text-secondary); margin-bottom: 10px; }
.ovg-field { display: grid; gap: 6px; font-size: 0.8rem; font-weight: 700; color: var(--text-secondary); margin-bottom: 10px; }
.ovg-field-label { font-size: 0.8rem; font-weight: 700; color: var(--text-secondary); margin: 2px 0 6px; }
.ovg-field-label { font-size: 0.8rem; font-weight: 700; color: var(--text-secondary); margin: 2px 0 6px; }
+.ovg-field-text .muted { font-weight: 500; color: var(--text-muted); }
.ovg-prefix { display: flex; align-items: center; gap: 0; } .ovg-prefix span { padding: 9px 12px; border: 1px solid var(--border); border-right: 0; border-radius: 10px 0 0 10px; background: var(--bg-tertiary); font-weight: 900; } .ovg-prefix .form-input { border-radius: 0 10px 10px 0; }
.ovg-prefix { display: flex; align-items: center; gap: 0; } .ovg-prefix span { padding: 9px 12px; border: 1px solid var(--border); border-right: 0; border-radius: 10px 0 0 10px; background: var(--bg-tertiary); font-weight: 900; } .ovg-prefix .form-input { border-radius: 0 10px 10px 0; }
.ovg-drop { display: flex; align-items: center; justify-content: center; gap: 10px; min-height: 96px; border: 2px dashed color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 14px; background: var(--bg-secondary); cursor: pointer; color: var(--text-secondary); font-weight: 700; padding: 12px; transition: border-color .15s, background .15s; }
.ovg-drop { display: flex; align-items: center; justify-content: center; gap: 10px; min-height: 96px; border: 2px dashed color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 14px; background: var(--bg-secondary); cursor: pointer; color: var(--text-secondary); font-weight: 700; padding: 12px; transition: border-color .15s, background .15s; }
.ovg-drop:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, var(--bg-secondary)); }
.ovg-drop:hover { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 6%, var(--bg-secondary)); }
@@ -201,7 +201,8 @@
@@ -201,7 +201,8 @@
secret: (label, value) => `<div class="ovg-kv"><span>${cfg.esc(label)}</span><code class="ovg-secret" data-secret="${cfg.esc(value)}">••••••••••••••••</code><button type="button" class="ovg-copy" data-copy="${cfg.esc(value)}"><i class="fa-regular fa-copy"></i> Copy</button><button type="button" class="ovg-copy" data-reveal="1"><i class="fa-regular fa-eye"></i> Show</button></div>`,
secret: (label, value) => `<div class="ovg-kv"><span>${cfg.esc(label)}</span><code class="ovg-secret" data-secret="${cfg.esc(value)}">••••••••••••••••</code><button type="button" class="ovg-copy" data-copy="${cfg.esc(value)}"><i class="fa-regular fa-copy"></i> Copy</button><button type="button" class="ovg-copy" data-reveal="1"><i class="fa-regular fa-eye"></i> Show</button></div>`,
steps: (items) => `<ol class="ovg-obs">${items.map(i => `<li>${i}</li>`).join('')}</ol>`,
steps: (items) => `<ol class="ovg-obs">${items.map(i => `<li>${i}</li>`).join('')}</ol>`,
chips: (items, name) => `<div class="ovg-chips" data-chips="${cfg.esc(name || '')}">${items.map(([v, l, i]) => `<button type="button" class="ovg-chip" data-chip="${cfg.esc(v)}">${i ? `<i class="fa-solid ${i}"></i> ` : ''}${cfg.esc(l)}</button>`).join('')}</div>`,
chips: (items, name) => `<div class="ovg-chips" data-chips="${cfg.esc(name || '')}">${items.map(([v, l, i]) => `<button type="button" class="ovg-chip" data-chip="${cfg.esc(v)}">${i ? `<i class="fa-solid ${i}"></i> ` : ''}${cfg.esc(l)}</button>`).join('')}</div>`,
- field: (label, inputHtml) => `<label class="ovg-field">${label}${inputHtml}</label>`,
+ // The label is one grid row (its text and any "(optional)" note together), the control the next.
+ field: (label, inputHtml) => `<label class="ovg-field"><span class="ovg-field-text">${label}</span>${inputHtml}</label>`,
wire(root) {
wire(root) {
root.querySelectorAll('[data-copy]').forEach(b => b.addEventListener('click', async () => { try { await navigator.clipboard.writeText(b.dataset.copy); b.innerHTML = '<i class="fa-solid fa-check"></i> Copied'; setTimeout(() => { b.innerHTML = '<i class="fa-regular fa-copy"></i> Copy'; }, 1500); } catch { cfg.toast('Copy failed — long-press to copy', 'error'); } }));
root.querySelectorAll('[data-copy]').forEach(b => b.addEventListener('click', async () => { try { await navigator.clipboard.writeText(b.dataset.copy); b.innerHTML = '<i class="fa-solid fa-check"></i> Copied'; setTimeout(() => { b.innerHTML = '<i class="fa-regular fa-copy"></i> Copy'; }, 1500); } catch { cfg.toast('Copy failed — long-press to copy', 'error'); } }));
root.querySelectorAll('[data-reveal]').forEach(b => b.addEventListener('click', () => { const c = b.parentElement.querySelector('.ovg-secret'); if (!c) return; const on = c.dataset.on === '1'; c.textContent = on ? '••••••••••••••••' : c.dataset.secret; c.dataset.on = on ? '' : '1'; b.innerHTML = on ? '<i class="fa-regular fa-eye"></i> Show' : '<i class="fa-regular fa-eye-slash"></i> Hide'; }));
root.querySelectorAll('[data-reveal]').forEach(b => b.addEventListener('click', () => { const c = b.parentElement.querySelector('.ovg-secret'); if (!c) return; const on = c.dataset.on === '1'; c.textContent = on ? '••••••••••••••••' : c.dataset.secret; c.dataset.on = on ? '' : '1'; b.innerHTML = on ? '<i class="fa-regular fa-eye"></i> Show' : '<i class="fa-regular fa-eye-slash"></i> Hide'; }));
new file mode 100644
new file mode 100644
@@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
+'use strict';
+// The guides' form field (public/js/ov-guide.js ui.field): .ovg-field is a grid, so the label's text and its
+// "(optional)" note must be ONE grid item, or the note lands on a row of its own in heading weight (the go-live
+// setup's "One line about it", 2026-10-15). The note itself is lighter than the label.
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+
+const js = fs.readFileSync(path.join(__dirname, '..', 'public', 'js', 'ov-guide.js'), 'utf8');
+const css = fs.readFileSync(path.join(__dirname, '..', 'public', 'css', 'ov-guide.css'), 'utf8');
+assert.ok(js.includes('field: (label, inputHtml) => `<label class="ovg-field"><span class="ovg-field-text">${label}</span>${inputHtml}</label>`'), 'the label text is wrapped as one grid item');
+assert.match(css, /\.ovg-field-text \.muted \{ font-weight: 500;/, 'the optional note is lighter than the label');
+console.log('guide field: the label and its note share one row');