openvibers/openvibe.network Commits
Network home: what the network is now, no doubled periods, 1 tool (#213)
Changes
@@@ -198,7 +198,7 @@ footer{border-top:1px solid var(--border);background:var(--bg-secondary)}<div class="hero-main"><div class="hero-badge"><span class="ov-mark" data-size="20" data-variant="network"></span> The OpenVibe Network — open source, community run</div><h1>The open network<br><span class="grad">for what you do online.</span></h1>-- <p class="sub">Live streaming, <!--OV:COUNT--> online tools, chat, games, community pastes, a wiki, a blog and a shared media library. Open source, run by the people who use it, and open to everyone without an account. Sign in once and every site already knows you.</p>++ <p class="sub">Live streaming from a browser tab, <!--OV:COUNT--> online tools, chat, games, a wiki and a blog; agents that do tasks for you; jobs, food help and places to rent; and the platform underneath, for anyone to build on. Open source, run by the people who use it, and open to everyone without an account. Sign in once and every site already knows you.</p><form class="intent-bar" action="https://openvibe.tools/search" method="get" role="search"><label for="intent-q" class="sr-only">What do you want to do?</label><i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>@@@ -136,6 +136,8 @@ ${icons.CSS}`;// The sites section's heading; the JSON-LD ItemList of the same sites carries it as its name, so the// structured data names something a visitor can see (browser check, WS-Q task 3).const sitesHeading = () => `${inWords(OPEN.length)} sites, one front door`;++/** A tagline as the start of a sentence: a period only when it does not already end one ("What should we eat?"). */++const asSentence = (t) => (/[.!?…]$/.test(String(t).trim()) ? String(t).trim() : `${String(t).trim()}.`);// Markup inside a kit section after its grid (the kit closes the section itself); an empty section stays empty.const inside = (section, extra) => (section && extra ? section.replace(/<\/section>$/, () => `${extra}</section>`) : section);@@@ -151,12 +153,12 @@ function body(catalog, act) {const sites = showcase.features({id: 'network', title: sitesHeading(),lede: 'Stream, chat, build, share, play, write and store. Everything is open to visitors; signing in once carries your name, theme and notifications to all of it.',-- items: OPEN.map((site) => ({ icon: `ov:${site.icon}`, title: siteName(site), text: site.tagline ? `${site.tagline}. ${site.what}` : site.what, href: `https://${site.host}/` })),++ items: OPEN.map((site) => ({ icon: `ov:${site.icon}`, title: siteName(site), text: site.tagline ? `${asSentence(site.tagline)} ${site.what}` : site.what, href: `https://${site.host}/` })),});const tools = inside(showcase.features({id: 'tools', title: catalog.tools.length ? `${catalog.tools.length} tools that just open` : 'Tools that just open',lede: 'No installs and no sign-up wall. Every tool has its own short address, so yt.openvibe.tools or dns.openvibe.tools takes you straight there.',-- items: fams.length ? fams.map((f) => ({ icon: `ov:${f.icon}`, title: f.name, text: `${count(f)} tools · ${f.tagline || ''}`, href: f.path ? `https://openvibe.tools${f.path}` : f.url }))++ items: fams.length ? fams.map((f) => ({ icon: `ov:${f.icon}`, title: f.name, text: `${count(f)} ${count(f) === 1 ? 'tool' : 'tools'} · ${f.tagline || ''}`, href: f.path ? `https://openvibe.tools${f.path}` : f.url })): [{ icon: 'ov:tools', title: 'OpenVibe.Tools', text: 'Converters, downloaders, PDF and image tools, developer utilities and network diagnostics.', href: 'https://openvibe.tools/' }],}), `${chips(popular.map((t) => [t.url, t.tagline || '', t.icon, t.name]))}<p class="home-more"><a href="https://openvibe.tools/">Browse every tool at openvibe.tools →</a> · <a href="https://yt.openvibe.tools/">yt.openvibe.tools</a> · <a href="https://dns.openvibe.tools/">dns.openvibe.tools</a></p>`);const account = showcase.features({@@@ -55,6 +55,10 @@ assert.ok(!/class="home-(sec|card|grid|fams|fam|dev)\b/.test(html), 'no hand-buiassert.strictEqual((html.match(/<h1[\s>]/g) || []).length, 1, 'one h1: the hero');assert.match(html, /<meta name="ai-summary" content="One account for \d+ open sites \(live, /);assert.ok(/"@type":"WebPage"/.test(html), 'the summary has its WebPage JSON-LD');++// A tagline that already ends a sentence ("What should we eat?") gets no second period; a family of one is "1 tool".++assert.ok(!/[.?!]\. /.test(html.replace(/<[^>]+>/g, ' ')), 'no doubled sentence end after a site tagline');++assert.ok(!/\b1 tools\b/.test(html), 'one tool is "1 tool"');++assert.ok(/Live streaming from a browser tab, /.test(html), 'the hero line still leads with going live from a browser');for (const s of open) assert.ok(html.includes(`<a class="sc-card" href="https://${s.host}/">`), `the sites section links ${s.host}`);for (const href of ['https://openvibe.tools/api/catalog.json', '/llms.txt', 'https://openvibe.tools/llms.txt', 'https://github.com/OpenVibers', 'https://openvibe.codes/', 'https://yt.openvibe.tools/', 'https://dns.openvibe.tools/']) assert.ok(html.includes(`href="${href}"`), `the page still links ${href}`);assert.strictEqual((html.match(/<section[\s>]/g) || []).length, (html.match(/<\/section>/g) || []).length, 'every section is closed once');
@@ -198,7 +198,7 @@ footer{border-top:1px solid var(--border);background:var(--bg-secondary)}
@@ -198,7 +198,7 @@ footer{border-top:1px solid var(--border);background:var(--bg-secondary)}
<div class="hero-main">
<div class="hero-main">
<div class="hero-badge"><span class="ov-mark" data-size="20" data-variant="network"></span> The OpenVibe Network — open source, community run</div>
<div class="hero-badge"><span class="ov-mark" data-size="20" data-variant="network"></span> The OpenVibe Network — open source, community run</div>
<h1>The open network<br><span class="grad">for what you do online.</span></h1>
<h1>The open network<br><span class="grad">for what you do online.</span></h1>
- <p class="sub">Live streaming, <!--OV:COUNT--> online tools, chat, games, community pastes, a wiki, a blog and a shared media library. Open source, run by the people who use it, and open to everyone without an account. Sign in once and every site already knows you.</p>
+ <p class="sub">Live streaming from a browser tab, <!--OV:COUNT--> online tools, chat, games, a wiki and a blog; agents that do tasks for you; jobs, food help and places to rent; and the platform underneath, for anyone to build on. Open source, run by the people who use it, and open to everyone without an account. Sign in once and every site already knows you.</p>
<form class="intent-bar" action="https://openvibe.tools/search" method="get" role="search">
<form class="intent-bar" action="https://openvibe.tools/search" method="get" role="search">
<label for="intent-q" class="sr-only">What do you want to do?</label>
<label for="intent-q" class="sr-only">What do you want to do?</label>
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
<i class="fa-solid fa-magnifying-glass" aria-hidden="true"></i>
@@ -136,6 +136,8 @@ ${icons.CSS}`;
@@ -136,6 +136,8 @@ ${icons.CSS}`;
// The sites section's heading; the JSON-LD ItemList of the same sites carries it as its name, so the
// The sites section's heading; the JSON-LD ItemList of the same sites carries it as its name, so the
// structured data names something a visitor can see (browser check, WS-Q task 3).
// structured data names something a visitor can see (browser check, WS-Q task 3).
const sitesHeading = () => `${inWords(OPEN.length)} sites, one front door`;
const sitesHeading = () => `${inWords(OPEN.length)} sites, one front door`;
+/** A tagline as the start of a sentence: a period only when it does not already end one ("What should we eat?"). */
+const asSentence = (t) => (/[.!?…]$/.test(String(t).trim()) ? String(t).trim() : `${String(t).trim()}.`);
// Markup inside a kit section after its grid (the kit closes the section itself); an empty section stays empty.
// Markup inside a kit section after its grid (the kit closes the section itself); an empty section stays empty.
const inside = (section, extra) => (section && extra ? section.replace(/<\/section>$/, () => `${extra}</section>`) : section);
const inside = (section, extra) => (section && extra ? section.replace(/<\/section>$/, () => `${extra}</section>`) : section);
@@ -151,12 +153,12 @@ function body(catalog, act) {
@@ -151,12 +153,12 @@ function body(catalog, act) {
const sites = showcase.features({
const sites = showcase.features({
id: 'network', title: sitesHeading(),
id: 'network', title: sitesHeading(),
lede: 'Stream, chat, build, share, play, write and store. Everything is open to visitors; signing in once carries your name, theme and notifications to all of it.',
lede: 'Stream, chat, build, share, play, write and store. Everything is open to visitors; signing in once carries your name, theme and notifications to all of it.',
- items: OPEN.map((site) => ({ icon: `ov:${site.icon}`, title: siteName(site), text: site.tagline ? `${site.tagline}. ${site.what}` : site.what, href: `https://${site.host}/` })),
+ items: OPEN.map((site) => ({ icon: `ov:${site.icon}`, title: siteName(site), text: site.tagline ? `${asSentence(site.tagline)} ${site.what}` : site.what, href: `https://${site.host}/` })),
});
});
const tools = inside(showcase.features({
const tools = inside(showcase.features({
id: 'tools', title: catalog.tools.length ? `${catalog.tools.length} tools that just open` : 'Tools that just open',
id: 'tools', title: catalog.tools.length ? `${catalog.tools.length} tools that just open` : 'Tools that just open',
lede: 'No installs and no sign-up wall. Every tool has its own short address, so yt.openvibe.tools or dns.openvibe.tools takes you straight there.',
lede: 'No installs and no sign-up wall. Every tool has its own short address, so yt.openvibe.tools or dns.openvibe.tools takes you straight there.',
- items: fams.length ? fams.map((f) => ({ icon: `ov:${f.icon}`, title: f.name, text: `${count(f)} tools · ${f.tagline || ''}`, href: f.path ? `https://openvibe.tools${f.path}` : f.url }))
+ items: fams.length ? fams.map((f) => ({ icon: `ov:${f.icon}`, title: f.name, text: `${count(f)} ${count(f) === 1 ? 'tool' : 'tools'} · ${f.tagline || ''}`, href: f.path ? `https://openvibe.tools${f.path}` : f.url }))
: [{ icon: 'ov:tools', title: 'OpenVibe.Tools', text: 'Converters, downloaders, PDF and image tools, developer utilities and network diagnostics.', href: 'https://openvibe.tools/' }],
: [{ icon: 'ov:tools', title: 'OpenVibe.Tools', text: 'Converters, downloaders, PDF and image tools, developer utilities and network diagnostics.', href: 'https://openvibe.tools/' }],
}), `${chips(popular.map((t) => [t.url, t.tagline || '', t.icon, t.name]))}<p class="home-more"><a href="https://openvibe.tools/">Browse every tool at openvibe.tools →</a> · <a href="https://yt.openvibe.tools/">yt.openvibe.tools</a> · <a href="https://dns.openvibe.tools/">dns.openvibe.tools</a></p>`);
}), `${chips(popular.map((t) => [t.url, t.tagline || '', t.icon, t.name]))}<p class="home-more"><a href="https://openvibe.tools/">Browse every tool at openvibe.tools →</a> · <a href="https://yt.openvibe.tools/">yt.openvibe.tools</a> · <a href="https://dns.openvibe.tools/">dns.openvibe.tools</a></p>`);
const account = showcase.features({
const account = showcase.features({
@@ -55,6 +55,10 @@ assert.ok(!/class="home-(sec|card|grid|fams|fam|dev)\b/.test(html), 'no hand-bui
@@ -55,6 +55,10 @@ assert.ok(!/class="home-(sec|card|grid|fams|fam|dev)\b/.test(html), 'no hand-bui
assert.strictEqual((html.match(/<h1[\s>]/g) || []).length, 1, 'one h1: the hero');
assert.strictEqual((html.match(/<h1[\s>]/g) || []).length, 1, 'one h1: the hero');
assert.match(html, /<meta name="ai-summary" content="One account for \d+ open sites \(live, /);
assert.match(html, /<meta name="ai-summary" content="One account for \d+ open sites \(live, /);
assert.ok(/"@type":"WebPage"/.test(html), 'the summary has its WebPage JSON-LD');
assert.ok(/"@type":"WebPage"/.test(html), 'the summary has its WebPage JSON-LD');
+// A tagline that already ends a sentence ("What should we eat?") gets no second period; a family of one is "1 tool".
+assert.ok(!/[.?!]\. /.test(html.replace(/<[^>]+>/g, ' ')), 'no doubled sentence end after a site tagline');
+assert.ok(!/\b1 tools\b/.test(html), 'one tool is "1 tool"');
+assert.ok(/Live streaming from a browser tab, /.test(html), 'the hero line still leads with going live from a browser');
for (const s of open) assert.ok(html.includes(`<a class="sc-card" href="https://${s.host}/">`), `the sites section links ${s.host}`);
for (const s of open) assert.ok(html.includes(`<a class="sc-card" href="https://${s.host}/">`), `the sites section links ${s.host}`);
for (const href of ['https://openvibe.tools/api/catalog.json', '/llms.txt', 'https://openvibe.tools/llms.txt', 'https://github.com/OpenVibers', 'https://openvibe.codes/', 'https://yt.openvibe.tools/', 'https://dns.openvibe.tools/']) assert.ok(html.includes(`href="${href}"`), `the page still links ${href}`);
for (const href of ['https://openvibe.tools/api/catalog.json', '/llms.txt', 'https://openvibe.tools/llms.txt', 'https://github.com/OpenVibers', 'https://openvibe.codes/', 'https://yt.openvibe.tools/', 'https://dns.openvibe.tools/']) assert.ok(html.includes(`href="${href}"`), `the page still links ${href}`);
assert.strictEqual((html.match(/<section[\s>]/g) || []).length, (html.match(/<\/section>/g) || []).length, 'every section is closed once');
assert.strictEqual((html.match(/<section[\s>]/g) || []).length, (html.match(/<\/section>/g) || []).length, 'every section is closed once');