openvibers/openvibe.shared Commits
No pill above the headline: the hero drops its eyebrow (#356)
Changes
@@@ -23,6 +23,9 @@on every page" (a switch that says what off means) and Open in a window of its own. More and the timer close on a tapanywhere else, in the Dock or on the page, and on Escape. The Chats tab is **Messages**.++- **No pill above the headline.** `sc.hero()` no longer draws an eyebrow (the owner, 11-09: the pill above every home's++ headline "looks really cheesy… generic"); the headline and its lede say what the site is. `eyebrow` is ignored.++## 4.158.0 — 2026-11-09- **The menu's Home.** A site whose links never name its home (Exchange: Models, Chat, Arena…) now gets Home first in@@@ -67,6 +67,8 @@ const SITE_NAME_RE = /^(?:OpenVibe\.[A-Za-z]+|OpenRestream|[A-Za-z]+\.OpenVibe\.// "OpenVibe.Food · What should we eat?": the name repeats the navbar's, so the eyebrow keeps only what follows it// (4.26.1; the owner, 2026-10-28: "why is [the name] twice… in the navbar and then right after it").const SITE_NAME_LEAD_RE = /^(?:OpenVibe\.[A-Za-z]+|OpenRestream|[A-Za-z]+\.OpenVibe\.[A-Za-z]+)\s*[·•|—–:-]\s*/;++// 4.159.0: the hero shows no eyebrow at all (the owner, 11-09, on the pill above every home's headline: "looks really++// cheesy… lame… every site has this generic look"). The headline and its lede say what the site is; `eyebrow` is ignored.function eyebrowHTML(eyebrow) {const e = eyebrow && typeof eyebrow === 'object' ? eyebrow : { text: eyebrow };let text = String(e.text || '').trim();@@@ -111,7 +113,7 @@ function hero({ eyebrow, title, accent, lede, actions: acts, note, aside, html,// the aura. visual: false keeps it plain.const vis = !aside && html == null && visual !== false;const hasOrbit = vis && orbit && (has(orbit.inner) || has(orbit.outer));-- return `<section class="sc-hero${aside || html || vis ? ' sc-split' : ''}${vis ? ` sc-has-vis${hasOrbit ? ' sc-has-orbit' : ''}` : ''}"><div class="sc-hero-main">${eyebrowHTML(eyebrow)}<h1>${esc(title)}${accent ? `<span class="sc-accent"> ${esc(accent)}</span>` : ''}</h1>${lede ? `<p class="sc-hero-lede">${esc(lede)}</p>` : ''}${actions(acts)}${note ? `<p class="sc-note">${esc(note)}</p>` : ''}</div>${aside ? `<div class="sc-hero-aside">${aside.html != null ? aside.html : esc(aside)}</div>` : html != null ? `<div class="sc-hero-aside">${html}</div>` : vis ? `<div class="sc-hero-aside sc-hero-vis">${visualHTML(hasOrbit ? orbit : null)}</div>` : ''}</section>`;++ return `<section class="sc-hero${aside || html || vis ? ' sc-split' : ''}${vis ? ` sc-has-vis${hasOrbit ? ' sc-has-orbit' : ''}` : ''}"><div class="sc-hero-main"><h1>${esc(title)}${accent ? `<span class="sc-accent"> ${esc(accent)}</span>` : ''}</h1>${lede ? `<p class="sc-hero-lede">${esc(lede)}</p>` : ''}${actions(acts)}${note ? `<p class="sc-note">${esc(note)}</p>` : ''}</div>${aside ? `<div class="sc-hero-aside">${aside.html != null ? aside.html : esc(aside)}</div>` : html != null ? `<div class="sc-hero-aside">${html}</div>` : vis ? `<div class="sc-hero-aside sc-hero-vis">${visualHTML(hasOrbit ? orbit : null)}</div>` : ''}</section>`;}/** A grid of what the product does; each card may link to where it starts. */@@@ -134,12 +134,13 @@ assert.ok(/@container \(min-width:520px\) and \(max-width:1035px\)\{\.sc-grid:ha}// The hero on a phone (4.23.0; the owner on openvibe.quest: "why is openvibe.quest twice… empty space at the top"): an++// 4.159.0 (owner, 11-09: the pill above every headline "looks really cheesy"): the hero never shows an eyebrow.++['OpenVibe.Quest', 'Free, nothing to install', { icon: 'fa-star', text: 'New' }, 'OpenVibe.Food · What should we eat?'].forEach((e) => {++ assert.ok(!sc.hero({ eyebrow: e, title: 'T' }).includes('sc-eyebrow'), `no eyebrow for ${JSON.stringify(e)}`);++});// eyebrow that is only the site's name is left out (the navbar shows it), any other eyebrow stays, with an icon when// given; numbers take an icon; phones start the hero near the bar, put the buttons side by side and the numbers three across.{-- ['OpenVibe.Quest', 'OpenRestream', 'Play.OpenVibe.Network'].forEach((name) => assert.ok(!sc.hero({ eyebrow: name, title: 'T' }).includes('sc-eyebrow'), `${name}: no eyebrow under the navbar's own brand`));-- assert.ok(/<p class="sc-eyebrow">Free, nothing to install<\/p>/.test(sc.hero({ eyebrow: 'Free, nothing to install', title: 'T' })), 'an eyebrow that says something stays');-- assert.ok(/<p class="sc-eyebrow"><(i|span)[^>]*>.*<\/(i|span)>New<\/p>/.test(sc.hero({ eyebrow: { icon: 'fa-star', text: 'New' }, title: 'T' })), 'an eyebrow can carry an icon');assert.ok(/<dd><span class="sc-stat-ic">.*<\/span>39<\/dd>/.test(sc.stats({ items: [{ value: 39, label: 'quests', icon: 'fa-star' }] })), 'a number can carry an icon');const phone = sc.CSS.slice(sc.CSS.indexOf('@media (max-width:640px){'));assert.ok(/\.sc-hero\{padding-top:14px\}/.test(phone) && /\.sc-actions\{display:grid;grid-template-columns:repeat\(auto-fit,minmax\(0,1fr\)\)/.test(phone) && /\.sc-stats\{grid-template-columns:repeat\(3,minmax\(0,1fr\)\)/.test(phone), 'phones: near the bar, buttons side by side, numbers three across');@@@ -163,14 +164,7 @@ assert.ok(/@container \(min-width:520px\) and \(max-width:1035px\)\{\.sc-grid:haassert.ok(/@media \(prefers-reduced-motion:reduce\)\{\.sc-ring,\.sc-aura-glow,\.sc-orb-ring,\.sc-orb-face\{animation:none\}\}/.test(sc.CSS), 'still under reduced motion');}--// The site's name leading an eyebrow is left out, the rest kept (4.26.1): 14 homes said "OpenVibe.X · …" under a navbar--// that already says it.--{-- assert.ok(/<p class="sc-eyebrow">What should we eat\?<\/p>/.test(sc.hero({ eyebrow: 'OpenVibe.Food · What should we eat?', title: 'T' })), 'the name leaves, the phrase stays');-- assert.ok(/<p class="sc-eyebrow">Open restreaming<\/p>/.test(sc.hero({ eyebrow: 'OpenRestream · open restreaming', title: 'T' })), 'and starts with a capital');-- assert.ok(/<p class="sc-eyebrow">Alpha<\/p>/.test(sc.hero({ eyebrow: 'OpenVibe.Bot · alpha', title: 'T' })));-- assert.ok(/<p class="sc-eyebrow">Free for OpenVibe\.Live streamers<\/p>/.test(sc.hero({ eyebrow: 'Free for OpenVibe.Live streamers', title: 'T' })), 'a name later in the phrase stays');--}++// (4.26.1 kept the eyebrow without the site name; 4.159.0 drops the eyebrow from the hero altogether, tested above.)// ── apps(): the launcher ──{
@@ -23,6 +23,9 @@
@@ -23,6 +23,9 @@
on every page" (a switch that says what off means) and Open in a window of its own. More and the timer close on a tap
on every page" (a switch that says what off means) and Open in a window of its own. More and the timer close on a tap
anywhere else, in the Dock or on the page, and on Escape. The Chats tab is **Messages**.
anywhere else, in the Dock or on the page, and on Escape. The Chats tab is **Messages**.
+- **No pill above the headline.** `sc.hero()` no longer draws an eyebrow (the owner, 11-09: the pill above every home's
+ headline "looks really cheesy… generic"); the headline and its lede say what the site is. `eyebrow` is ignored.
+
## 4.158.0 — 2026-11-09
## 4.158.0 — 2026-11-09
- **The menu's Home.** A site whose links never name its home (Exchange: Models, Chat, Arena…) now gets Home first in
- **The menu's Home.** A site whose links never name its home (Exchange: Models, Chat, Arena…) now gets Home first in
@@ -67,6 +67,8 @@ const SITE_NAME_RE = /^(?:OpenVibe\.[A-Za-z]+|OpenRestream|[A-Za-z]+\.OpenVibe\.
@@ -67,6 +67,8 @@ const SITE_NAME_RE = /^(?:OpenVibe\.[A-Za-z]+|OpenRestream|[A-Za-z]+\.OpenVibe\.
// "OpenVibe.Food · What should we eat?": the name repeats the navbar's, so the eyebrow keeps only what follows it
// "OpenVibe.Food · What should we eat?": the name repeats the navbar's, so the eyebrow keeps only what follows it
// (4.26.1; the owner, 2026-10-28: "why is [the name] twice… in the navbar and then right after it").
// (4.26.1; the owner, 2026-10-28: "why is [the name] twice… in the navbar and then right after it").
const SITE_NAME_LEAD_RE = /^(?:OpenVibe\.[A-Za-z]+|OpenRestream|[A-Za-z]+\.OpenVibe\.[A-Za-z]+)\s*[·•|—–:-]\s*/;
const SITE_NAME_LEAD_RE = /^(?:OpenVibe\.[A-Za-z]+|OpenRestream|[A-Za-z]+\.OpenVibe\.[A-Za-z]+)\s*[·•|—–:-]\s*/;
+// 4.159.0: the hero shows no eyebrow at all (the owner, 11-09, on the pill above every home's headline: "looks really
+// cheesy… lame… every site has this generic look"). The headline and its lede say what the site is; `eyebrow` is ignored.
function eyebrowHTML(eyebrow) {
function eyebrowHTML(eyebrow) {
const e = eyebrow && typeof eyebrow === 'object' ? eyebrow : { text: eyebrow };
const e = eyebrow && typeof eyebrow === 'object' ? eyebrow : { text: eyebrow };
let text = String(e.text || '').trim();
let text = String(e.text || '').trim();
@@ -111,7 +113,7 @@ function hero({ eyebrow, title, accent, lede, actions: acts, note, aside, html,
@@ -111,7 +113,7 @@ function hero({ eyebrow, title, accent, lede, actions: acts, note, aside, html,
// the aura. visual: false keeps it plain.
// the aura. visual: false keeps it plain.
const vis = !aside && html == null && visual !== false;
const vis = !aside && html == null && visual !== false;
const hasOrbit = vis && orbit && (has(orbit.inner) || has(orbit.outer));
const hasOrbit = vis && orbit && (has(orbit.inner) || has(orbit.outer));
- return `<section class="sc-hero${aside || html || vis ? ' sc-split' : ''}${vis ? ` sc-has-vis${hasOrbit ? ' sc-has-orbit' : ''}` : ''}"><div class="sc-hero-main">${eyebrowHTML(eyebrow)}<h1>${esc(title)}${accent ? `<span class="sc-accent"> ${esc(accent)}</span>` : ''}</h1>${lede ? `<p class="sc-hero-lede">${esc(lede)}</p>` : ''}${actions(acts)}${note ? `<p class="sc-note">${esc(note)}</p>` : ''}</div>${aside ? `<div class="sc-hero-aside">${aside.html != null ? aside.html : esc(aside)}</div>` : html != null ? `<div class="sc-hero-aside">${html}</div>` : vis ? `<div class="sc-hero-aside sc-hero-vis">${visualHTML(hasOrbit ? orbit : null)}</div>` : ''}</section>`;
+ return `<section class="sc-hero${aside || html || vis ? ' sc-split' : ''}${vis ? ` sc-has-vis${hasOrbit ? ' sc-has-orbit' : ''}` : ''}"><div class="sc-hero-main"><h1>${esc(title)}${accent ? `<span class="sc-accent"> ${esc(accent)}</span>` : ''}</h1>${lede ? `<p class="sc-hero-lede">${esc(lede)}</p>` : ''}${actions(acts)}${note ? `<p class="sc-note">${esc(note)}</p>` : ''}</div>${aside ? `<div class="sc-hero-aside">${aside.html != null ? aside.html : esc(aside)}</div>` : html != null ? `<div class="sc-hero-aside">${html}</div>` : vis ? `<div class="sc-hero-aside sc-hero-vis">${visualHTML(hasOrbit ? orbit : null)}</div>` : ''}</section>`;
}
}
/** A grid of what the product does; each card may link to where it starts. */
/** A grid of what the product does; each card may link to where it starts. */
@@ -134,12 +134,13 @@ assert.ok(/@container \(min-width:520px\) and \(max-width:1035px\)\{\.sc-grid:ha
@@ -134,12 +134,13 @@ assert.ok(/@container \(min-width:520px\) and \(max-width:1035px\)\{\.sc-grid:ha
}
}
// The hero on a phone (4.23.0; the owner on openvibe.quest: "why is openvibe.quest twice… empty space at the top"): an
// The hero on a phone (4.23.0; the owner on openvibe.quest: "why is openvibe.quest twice… empty space at the top"): an
+// 4.159.0 (owner, 11-09: the pill above every headline "looks really cheesy"): the hero never shows an eyebrow.
+['OpenVibe.Quest', 'Free, nothing to install', { icon: 'fa-star', text: 'New' }, 'OpenVibe.Food · What should we eat?'].forEach((e) => {
+ assert.ok(!sc.hero({ eyebrow: e, title: 'T' }).includes('sc-eyebrow'), `no eyebrow for ${JSON.stringify(e)}`);
+});
// eyebrow that is only the site's name is left out (the navbar shows it), any other eyebrow stays, with an icon when
// eyebrow that is only the site's name is left out (the navbar shows it), any other eyebrow stays, with an icon when
// given; numbers take an icon; phones start the hero near the bar, put the buttons side by side and the numbers three across.
// given; numbers take an icon; phones start the hero near the bar, put the buttons side by side and the numbers three across.
{
{
- ['OpenVibe.Quest', 'OpenRestream', 'Play.OpenVibe.Network'].forEach((name) => assert.ok(!sc.hero({ eyebrow: name, title: 'T' }).includes('sc-eyebrow'), `${name}: no eyebrow under the navbar's own brand`));
- assert.ok(/<p class="sc-eyebrow">Free, nothing to install<\/p>/.test(sc.hero({ eyebrow: 'Free, nothing to install', title: 'T' })), 'an eyebrow that says something stays');
- assert.ok(/<p class="sc-eyebrow"><(i|span)[^>]*>.*<\/(i|span)>New<\/p>/.test(sc.hero({ eyebrow: { icon: 'fa-star', text: 'New' }, title: 'T' })), 'an eyebrow can carry an icon');
assert.ok(/<dd><span class="sc-stat-ic">.*<\/span>39<\/dd>/.test(sc.stats({ items: [{ value: 39, label: 'quests', icon: 'fa-star' }] })), 'a number can carry an icon');
assert.ok(/<dd><span class="sc-stat-ic">.*<\/span>39<\/dd>/.test(sc.stats({ items: [{ value: 39, label: 'quests', icon: 'fa-star' }] })), 'a number can carry an icon');
const phone = sc.CSS.slice(sc.CSS.indexOf('@media (max-width:640px){'));
const phone = sc.CSS.slice(sc.CSS.indexOf('@media (max-width:640px){'));
assert.ok(/\.sc-hero\{padding-top:14px\}/.test(phone) && /\.sc-actions\{display:grid;grid-template-columns:repeat\(auto-fit,minmax\(0,1fr\)\)/.test(phone) && /\.sc-stats\{grid-template-columns:repeat\(3,minmax\(0,1fr\)\)/.test(phone), 'phones: near the bar, buttons side by side, numbers three across');
assert.ok(/\.sc-hero\{padding-top:14px\}/.test(phone) && /\.sc-actions\{display:grid;grid-template-columns:repeat\(auto-fit,minmax\(0,1fr\)\)/.test(phone) && /\.sc-stats\{grid-template-columns:repeat\(3,minmax\(0,1fr\)\)/.test(phone), 'phones: near the bar, buttons side by side, numbers three across');
@@ -163,14 +164,7 @@ assert.ok(/@container \(min-width:520px\) and \(max-width:1035px\)\{\.sc-grid:ha
@@ -163,14 +164,7 @@ assert.ok(/@container \(min-width:520px\) and \(max-width:1035px\)\{\.sc-grid:ha
assert.ok(/@media \(prefers-reduced-motion:reduce\)\{\.sc-ring,\.sc-aura-glow,\.sc-orb-ring,\.sc-orb-face\{animation:none\}\}/.test(sc.CSS), 'still under reduced motion');
assert.ok(/@media \(prefers-reduced-motion:reduce\)\{\.sc-ring,\.sc-aura-glow,\.sc-orb-ring,\.sc-orb-face\{animation:none\}\}/.test(sc.CSS), 'still under reduced motion');
}
}
-// The site's name leading an eyebrow is left out, the rest kept (4.26.1): 14 homes said "OpenVibe.X · …" under a navbar
-// that already says it.
-{
- assert.ok(/<p class="sc-eyebrow">What should we eat\?<\/p>/.test(sc.hero({ eyebrow: 'OpenVibe.Food · What should we eat?', title: 'T' })), 'the name leaves, the phrase stays');
- assert.ok(/<p class="sc-eyebrow">Open restreaming<\/p>/.test(sc.hero({ eyebrow: 'OpenRestream · open restreaming', title: 'T' })), 'and starts with a capital');
- assert.ok(/<p class="sc-eyebrow">Alpha<\/p>/.test(sc.hero({ eyebrow: 'OpenVibe.Bot · alpha', title: 'T' })));
- assert.ok(/<p class="sc-eyebrow">Free for OpenVibe\.Live streamers<\/p>/.test(sc.hero({ eyebrow: 'Free for OpenVibe.Live streamers', title: 'T' })), 'a name later in the phrase stays');
-}
+// (4.26.1 kept the eyebrow without the site name; 4.159.0 drops the eyebrow from the hero altogether, tested above.)
// ── apps(): the launcher ──
// ── apps(): the launcher ──
{
{