openvibers/openvibe.design Commits

/look: the icon styles side by side, each a screen that picks its style (#94)

OP OpenVibers committed 12 days ago · ba4b411e41 · 4 files, +63 −5

Under the Icons choice, three small phone screens drawn with the real glyphs: a header with icons above 18 px,
rows with small ones and an app bar whose current tab is solid. Line and Solid force their style on every icon;
Auto resets the person's own choice so it shows what Auto does. A screen is a button that picks its style, like
the choice above it, and the picked one wears the accent. It replaces the row of sample icons.

Changes

ddiff --git a/public/css/design.css b/public/css/design.css
iindex 16e2ce0..36319af 100644
---- a/public/css/design.css
++++ b/public/css/design.css
@@@ -573,10 +573,6 @@ html.ar-fit .ar-shot--desktop .ar-shot-frame, html.ar-fit .ar-shot--desktop::bef
} }
/* The icons sample under the Icons choice: small and large, so Auto's switch shows (plan L56). */
--.lk-icons{display:flex;flex-wrap:wrap;align-items:center;gap:10px 18px;margin:4px 0 14px;padding:10px 14px;border-radius:12px;background:var(--bg-tertiary,rgba(255,255,255,.04));color:var(--text-primary,#e6edf7)}
--.lk-icons>span{display:inline-flex;gap:12px;font-size:15px}
--.lk-icons .lk-icons-big{font-size:26px;gap:16px}
--.lk-icons small{flex-basis:100%;color:var(--text-muted,#8a96a8);font-size:12.5px}
/* ── The arena as a view of Bench's nine design kinds (plan L51 step 1) ──
A sideways rail of kinds, the blind pair right under it, then one card per kind's board (a rail on a phone). */
@@@ -676,3 +672,29 @@ html.ar-fit .ar-shot--desktop .ar-shot-frame, html.ar-fit .ar-shot--desktop::bef
.ar-who { text-align: left; }
}
@media (prefers-reduced-motion: reduce) { .ar-kind { transition: none; } }
++
++/* /look: the icon styles side by side (server/views/look.js iconStyles): three small phone screens drawn with the
++ real glyphs. Each is a button that picks its style; the picked one wears the accent. */
++.lk-styles{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin:2px 14px 16px}
++.lk-style{all:unset;box-sizing:border-box;display:flex;flex-direction:column;gap:8px;min-width:0;cursor:pointer;-webkit-tap-highlight-color:transparent}
++.lk-style:focus-visible .lk-st-screen{outline:2px solid var(--accent);outline-offset:3px}
++.lk-st-screen{display:flex;flex-direction:column;gap:7px;padding:10px 10px 0;border-radius:18px;border:1px solid var(--border,rgba(255,255,255,.1));background:var(--bg-primary);color:var(--text-primary);overflow:hidden;transition:border-color var(--ov-dur-2,.2s) var(--ov-ease-out,ease-out),transform var(--ov-dur-2,.2s) var(--ov-ease-out,ease-out)}
++.lk-style:hover .lk-st-screen{border-color:color-mix(in srgb,var(--accent) 55%,var(--border,transparent))}
++.lk-style[aria-pressed="true"] .lk-st-screen{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}
++.lk-style:active .lk-st-screen{transform:scale(.98)}
++.lk-st-head{display:flex;align-items:center;gap:8px;font-size:21px;padding-bottom:2px}
++.lk-st-title{flex:1 1 auto;height:9px;border-radius:5px;background:color-mix(in srgb,var(--text-primary) 70%,transparent);max-width:48%}
++.lk-st-row{display:flex;align-items:center;gap:8px;font-size:13px}
++.lk-st-dot{flex:none;display:grid;place-items:center;width:26px;height:26px;border-radius:50%;background:color-mix(in srgb,var(--accent) 16%,transparent);color:var(--accent)}
++.lk-st-lines{flex:1 1 auto;display:flex;flex-direction:column;gap:5px;min-width:0}
++.lk-st-lines>span{height:6px;border-radius:3px;background:color-mix(in srgb,var(--text-muted,currentColor) 40%,transparent)}
++.lk-st-lines>span+span{width:42%;opacity:.6}
++.lk-st-bar{display:flex;justify-content:space-around;margin:4px -10px 0;padding:8px 6px 9px;border-top:1px solid var(--border,rgba(255,255,255,.1));background:var(--bg-secondary,var(--bg-card));font-size:17px;color:var(--text-muted)}
++.lk-st-tab{display:grid;place-items:center;width:34px;height:24px;border-radius:12px}
++.lk-st-tab[aria-current="page"]{background:color-mix(in srgb,var(--accent) 18%,transparent);color:var(--accent)}
++.lk-st-cap{display:flex;flex-direction:column;gap:2px;padding:0 2px}
++.lk-st-cap b{font-size:14px}
++.lk-st-cap small{color:var(--text-muted);font-size:12px;line-height:1.35}
++.lk-style[aria-pressed="true"] .lk-st-cap b{color:var(--accent)}
++@media (max-width:480px){.lk-styles{gap:8px}.lk-st-screen{padding:8px 7px 0;border-radius:14px}.lk-st-head{font-size:19px;gap:6px}.lk-st-lines>span+span{display:none}.lk-st-bar{margin:4px -7px 0;font-size:15px}.lk-st-tab{width:26px}.lk-st-cap small{font-size:11px}}
++@media (prefers-reduced-motion:reduce){.lk-st-screen{transition:none}.lk-style:active .lk-st-screen{transform:none}}
ddiff --git a/public/js/look.js b/public/js/look.js
iindex a26b3b2..8970f75 100644
---- a/public/js/look.js
++++ b/public/js/look.js
@@@ -40,10 +40,18 @@
check('text', d.text);
check('motion', d.motion);
check('icons', d.icons || 'auto');
++ root.querySelectorAll('[data-pick-icons]').forEach((b) => b.setAttribute('aria-pressed', String(b.getAttribute('data-pick-icons') === (d.icons || 'auto'))));
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]; }
}
++ // A screen in the icon styles picks its style, the same as the Icons choice above it.
++ root.addEventListener('click', (e) => {
++ const b = e.target.closest && e.target.closest('[data-pick-icons]');
++ if (!b) return;
++ const r = root.querySelector(`input[name="icons"][value="${b.getAttribute('data-pick-icons')}"]`);
++ if (r && !r.checked) r.click();
++ });
root.addEventListener('change', (e) => {
const i = e.target;
if (!i || !i.name) return;
ddiff --git a/server/views/look.js b/server/views/look.js
iindex c9b2ea7..67fc20c 100644
---- a/server/views/look.js
++++ b/server/views/look.js
@@@ -14,6 +14,29 @@ const { layout, esc } = require('../render/layout');
// The sites a person can give a look of their own (service ids, as openvibe-shared/shell writes them on <html>).
const SITES = [['live', 'OpenVibe.Live'], ['chat', 'OpenVibe.Chat'], ['games', 'OpenVibe.Games'], ['community', 'OpenVibe.Community'], ['media', 'OpenVibe.Media'], ['tools', 'OpenVibe.Tools'], ['bot', 'OpenVibe.Bot'], ['actor', 'OpenVibe.Actor'], ['quest', 'OpenVibe.Quest'], ['rent', 'OpenVibe.Rent'], ['deals', 'OpenVibe.Deals'], ['network', 'OpenVibe.Network'], ['design', 'OpenVibe.Design']];
++/**
++ * The three icon styles side by side, each a small phone screen drawn with the real glyphs (ov-glyphs.js): a header
++ * with icons above 18 px, rows with small ones and an app bar whose current tab is solid. Line and Solid force their
++ * style on every icon (.ovg-line, .ovg-solid); Auto resets the person's own choice (--ovg-pref) so it shows what
++ * Auto does: solid where an icon is small, lines where it is large. A screen is a button that picks its style
++ * (public/js/look.js), the same as the Icons choice above it.
++ */
++function iconStyles() {
++ const STYLES = [
++ ['auto', 'Auto', 'Solid when small, lines when large. The default.'],
++ ['line', 'Line', 'Outlines at every size.'],
++ ['solid', 'Solid', 'Filled at every size.'],
++ ];
++ const i = (name, force) => `<i class="fa-solid ${name}${force ? ` ovg-${force}` : ''}" aria-hidden="true"></i>`;
++ const screen = (style) => {
++ const force = style === 'auto' ? '' : style;
++ const rows = [['fa-tower-broadcast', 72], ['fa-comments', 54], ['fa-heart', 64]].map(([n, w]) => `<span class="lk-st-row"><span class="lk-st-dot">${i(n, force)}</span><span class="lk-st-lines"><span style="width:${w}%"></span><span></span></span></span>`).join('');
++ const bar = ['fa-house', 'fa-compass', 'fa-bell', 'fa-user'].map((n, k) => `<span class="lk-st-tab"${k === 0 ? ' aria-current="page"' : ''}>${i(n, force)}</span>`).join('');
++ return `<span class="lk-st-screen"${style === 'auto' ? ' style="--ovg-pref:18.4px"' : ''}><span class="lk-st-head"><span class="lk-st-title"></span>${i('fa-magnifying-glass', force)}${i('fa-bell', force)}</span>${rows}<span class="lk-st-bar">${bar}</span></span>`;
++ };
++ return `<div class="lk-styles" role="group" aria-label="The icon styles, side by side">${STYLES.map(([id, name, what]) => `<button type="button" class="lk-style" data-pick-icons="${id}" aria-pressed="${id === 'auto' ? 'true' : 'false'}">${screen(id)}<span class="lk-st-cap"><b>${name}</b><small>${what}</small></span></button>`).join('')}</div>`;
++}
++
function lookPage({ themes = [], me = {} } = {}) {
const list = (themes || []).filter((t) => t && t.id).map((t) => ({ id: t.id, name: t.name, mode: t.mode, dots: ['--bg-primary', '--bg-card', '--accent', '--text-primary'].map((k) => String((t.variables || {})[k] || '')).filter((v) => /^#[0-9a-f]{3,8}$/i.test(v)) }));
const signedIn = !!me.signedIn;
@@@ -31,7 +54,7 @@ function lookPage({ themes = [], me = {} } = {}) {
// configurable for users in most cases"): the sample under it is ordinary page markup, drawn by ov-glyphs.js, so
// it changes as the choice does.
+ kit.seg({ name: 'icons', label: 'Icons', options: [['auto', 'Auto'], ['line', 'Line'], ['solid', 'Solid']], value: 'auto' })
-- + `<p class="lk-icons" aria-label="A sample of the icons"><span>${['fa-house', 'fa-tower-broadcast', 'fa-comments', 'fa-bell', 'fa-heart', 'fa-user'].map((n) => `<i class="fa-solid ${n}" aria-hidden="true"></i>`).join('')}</span><span class="lk-icons-big">${['fa-house', 'fa-tower-broadcast', 'fa-comments', 'fa-bell'].map((n) => `<i class="fa-solid ${n}" aria-hidden="true"></i>`).join('')}</span><small>Auto: line, solid when small and for the tab you are on.</small></p>`
++ + iconStyles()
+ 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
ddiff --git a/test/tokens.test.js b/test/tokens.test.js
iindex 901c7d7..b770954 100644
---- a/test/tokens.test.js
++++ b/test/tokens.test.js
@@@ -98,6 +98,11 @@ const { boot, check, done } = require('./helpers/boot');
assert.match(r.text, /Your look, everywhere/);
for (const name of ['scheme', 'text', 'motion']) assert.match(r.text, new RegExp(`role="radiogroup"[^>]*>(<label><input type="radio" name="${name}"[^<]*<span>[^<]+</span></label>)+`), name);
assert.match(r.text, /name="seasonal"/);
++ // The icon styles side by side (plan L56): three screens drawn with the real glyphs, each a button for its style.
++ for (const id of ['auto', 'line', 'solid']) assert.match(r.text, new RegExp(`<button type="button" class="lk-style" data-pick-icons="${id}" aria-pressed="(true|false)">`), id);
++ assert.match(r.text, /<span class="lk-st-screen" style="--ovg-pref:18\.4px">/, 'Auto shows Auto whatever the person chose');
++ assert.match(r.text, /class="fa-solid fa-bell ovg-line"/, 'Line forces lines');
++ assert.match(r.text, /class="fa-solid fa-bell ovg-solid"/, 'Solid forces solids');
assert.match(r.text, /\/js\/look\.js\?v=[0-9a-f]{10}/);
assert.match(r.text, /<script type="application\/json" id="lk-themes">\[\{"id":"vibe"/);
assert.match(r.text, /Sign in so it follows you/);