openvibers/openvibe.quest Commits

Quest's home says what you get in plain words (no repeated name, buttons side by side, numbers with icons) and its bar is tidied (#110)

OP OpenVibers committed 16 days ago · 0784dbb5b9 · 4 files, +29 −26

* Quest's bar: Quests and Your quest log with icons, How it works under More, What shipped in the footer, the phone app bar

* Quest's home: 'Earn badges for what you already do on OpenVibe', a short lede, Start earning and Browse quests with icons, no repeated site name; the numbers are the shared strip with icons (three across on a phone)

* Quest's tests read the new hero: the numbers with icons, 'earns a badge and OpenCoins'

* Quest's home budget: 67.5 KB raw with the hero's and the numbers' icons

Changes

ddiff --git a/server/http/pages.js b/server/http/pages.js
iindex b017307..b86d04a 100644
---- a/server/http/pages.js
++++ b/server/http/pages.js
@@@ -80,16 +80,17 @@ function createPageRoutes(ctx) {
const countText = (n) => n.toLocaleString('en-US');
/** The figures under the hero: each one only when it has something to say. */
-- const statsStrip = (p) => {
-- const items = [
-- [catalog.all().length, `quests on ${siteCount} sites`],
-- p.completions > 0 && [p.completions, p.completions === 1 ? 'quest completed' : 'quests completed'],
-- p.people > 0 && [p.people, p.people === 1 ? 'person has earned badges' : 'people have earned badges'],
-- p.recent > 0 && [p.recent, 'completed in the last 7 days'],
-- coinsUi.coinsEnabled && p.coins > 0 && [p.coins, 'OpenCoins credited'],
-- ].filter(Boolean);
-- return html`<ul class="q-stats" aria-label="${SITE_NAME} in numbers">${items.map(([n, label]) => html`<li><b>${countText(n)}</b><span>${label}</span></li>`)}</ul>`;
-- };
++ // The numbers under the hero: the shared strip (three across on a phone), each with its icon.
++ const statsStrip = (p) => raw(showcase.stats({
++ title: `${SITE_NAME} in numbers`,
++ items: [
++ { value: countText(catalog.all().length), label: `quests on ${siteCount} sites`, icon: 'ov:quest' },
++ p.completions > 0 && { value: countText(p.completions), label: p.completions === 1 ? 'quest finished' : 'quests finished', icon: 'ov:check' },
++ p.people > 0 && { value: countText(p.people), label: p.people === 1 ? 'person with badges' : 'people with badges', icon: 'ov:account' },
++ p.recent > 0 && { value: countText(p.recent), label: 'finished this week', icon: 'ov:history' },
++ coinsUi.coinsEnabled && p.coins > 0 && { value: countText(p.coins), label: 'OpenCoins given', icon: 'ov:tips' },
++ ].filter(Boolean),
++ }));
/** The badges people hold most (all time), with the quest that pays each. */
const topBadges = (p) => {
@@@ -133,15 +134,16 @@ function createPageRoutes(ctx) {
r.get('/', async (req, res) => {
const [appList, p] = await Promise.all([ctx.appQuests ? ctx.appQuests.listed(24) : [], pulse()]);
const site = String(config.baseUrl).replace(/\/+$/, '');
++ // The navbar already says OpenVibe.Quest: the hero says what you get, in plain words, and where to start.
const hero = showcase.hero({
-- eyebrow: SITE_NAME,
-- title: 'Quests across OpenVibe.',
-- accent: 'Badges for what you really do.',
-- lede: `Go live, start a thread, publish a world, share a picture: ${SITE_NAME} counts what you really do on ${siteCount} OpenVibe sites and rewards it with badges${coinsUi.coinsEnabled ? ' and OpenCoins' : ' (and OpenCoins, once they are switched on)'}. One quest log for the whole network, with nothing new to install or learn.`,
++ eyebrow: { icon: 'ov:quest', text: 'Free, nothing to install' },
++ title: 'Earn badges',
++ accent: 'for what you already do on OpenVibe.',
++ lede: `Go live, post in the forum, publish a world: every quest you finish on any of ${siteCount} OpenVibe sites earns a badge${coinsUi.coinsEnabled ? ' and OpenCoins' : ''}. One quest log follows you everywhere.`,
actions: signedIn(req)
-- ? [{ label: 'Your quest log', href: '/me', primary: true }, { label: 'See the quests', href: '#the-quests' }]
-- : [{ label: 'Sign in to start', href: '/auth/login?next=%2Fme', primary: true }, { label: 'See the quests', href: '#the-quests' }],
-- note: 'Open source (AGPL-3.0). Rewards are badges, items and OpenCoins: loyalty points, never money and never Vibes.',
++ ? [{ label: 'Your quest log', href: '/me', primary: true, icon: 'ov:quest' }, { label: 'Browse quests', href: '#the-quests', icon: 'ov:search' }]
++ : [{ label: 'Start earning', href: '/auth/login?next=%2Fme', primary: true, icon: 'ov:quest' }, { label: 'Browse quests', href: '#the-quests', icon: 'ov:search' }],
++ note: 'Badges, items and OpenCoins: loyalty points, never money. Open source (AGPL-3.0).',
});
page(req, res, {
index: true, cache: signedIn(req) ? null : PUBLIC_CACHE,
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex 67d8738..2aacd43 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -22,12 +22,12 @@ const NETWORK_URL = 'https://openvibe.network';
const SITE_NAME = 'OpenVibe.Quest';
const TAGLINE = 'Quests across the whole network.';
const PUBLIC_DIR = path.join(__dirname, '..', '..', 'public');
--// The product's own navigation: one entry per public page it serves.
++// The product's own places: the main ones carry an icon (phones get them as the bottom tab bar), the rest fold into
++// the navbar's More (Shared 4.22). Search is the navbar's magnifier and What shipped is the footer's.
const NAV = [
-- { label: 'Quests', href: '/' },
-- { label: 'Your quest log', href: '/me' },
-- { label: 'How it works', href: '/how-it-works' },
-- { label: 'What shipped', href: '/updates' },
++ { label: 'Quests', href: '/', icon: 'fa-star' },
++ { label: 'Your quest log', href: '/me', icon: 'fa-list-check' },
++ { label: 'How it works', href: '/how-it-works', icon: 'fa-circle-info', more: true },
];
const hashes = new Map();
@@@ -58,7 +58,8 @@ function renderPage(o) {
const nav = {
service: 'quest',
apiBase: NETWORK_URL,
-- links: NAV.map((l) => ({ label: l.label, href: l.href, active: pathNow === l.href || pathNow.startsWith(`${l.href}/`) })),
++ links: NAV.map((l) => ({ label: l.label, href: l.href, ...(l.icon ? { icon: l.icon } : {}), ...(l.more ? { more: true } : {}), active: pathNow === l.href || pathNow.startsWith(`${l.href}/`) })),
++ appBar: [{ label: 'Quests', href: '/', icon: 'fa-star' }, { label: 'Quest log', href: '/me', icon: 'fa-list-check' }, { label: 'How it works', href: '/how-it-works', icon: 'fa-circle-info' }], // phones: the bottom tab bar (Shared 4.20)
history: { type: 'page', title: o.title || SITE_NAME },
silentLogin: `${o.config.baseUrl}/auth/login?silent=1&next={url}`,
sessionUrl: '/auth/me',
ddiff --git a/test/perf-budget.test.js b/test/perf-budget.test.js
iindex c676422..4c42205 100644
---- a/test/perf-budget.test.js
++++ b/test/perf-budget.test.js
@@@ -21,7 +21,7 @@ const BUDGETS = {
// Raised 2026-10-22 with the rich home: figures under the hero, the most-earned badges, each card's "earned by",
// the badges nobody holds yet, the sites Quest follows, a FAQ and the quests as ItemList + FAQPage JSON-LD.
// Measured 54.3 KB raw / 10.6 KB brotli on a fresh database.
-- htmlRawKB: 66, // 61.2 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; measured 54.3 (the home page: hero, figures, badges, features, the quests, sites, FAQ, cta, JSON-LD)
++ htmlRawKB: 69, // 67.5 the hero's and the numbers' icons (inline ring icons, 2026-10-28); 61.2 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; measured 54.3 (the home page: hero, figures, badges, features, the quests, sites, FAQ, cta, JSON-LD)
htmlBrotliKB: 13, // 11.8 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 10.6
jsFiles: 5, // 5 (theme-loader, web-runtime, navbar, footer, boost: openvibe-shared/shell)
jsRawKB: 297, // 293 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 287.2 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 273.2 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 264.3 with Shared 4.13.0 (2026-10-26: account links); 239.8
ddiff --git a/test/quests-api.test.js b/test/quests-api.test.js
iindex 0c26faa..b9aa830 100644
---- a/test/quests-api.test.js
++++ b/test/quests-api.test.js
@@@ -133,7 +133,7 @@ const { render } = require('../server/render/html');
// The live parts are counts, never people: kim's completion shows as a number and nothing about her.
assert.ok(home.text.includes('Badges people earn most'), 'the most-earned badges');
assert.ok(home.text.includes('Earned by 1 person'), 'a quest card says how many hold its badge');
-- assert.ok(home.text.includes('<b>1</b><span>quest completed</span>'), 'the figures under the hero');
++ assert.ok(/<dt>quest finished<\/dt><dd><span class="sc-stat-ic">[\s\S]*?<\/span>1<\/dd>/.test(home.text), 'the figures under the hero, each with its icon');
assert.ok(home.text.includes('Nobody has these badges yet'), 'the quests nobody has finished');
assert.ok(home.text.includes('"@type":"FAQPage"') && home.text.includes('Can other people see my progress?'), 'the FAQ and its JSON-LD');
assert.ok(home.text.includes('"@type":"ItemList"'), 'the quests as an ItemList');
@@@ -200,7 +200,7 @@ const { render } = require('../server/render/html');
const home = await on.get('/');
assert.strictEqual(home.status, 200);
assert.ok(!home.text.includes('once they are switched on'), 'no promise of something already on');
-- assert.ok(home.text.includes('rewards it with badges and OpenCoins'));
++ assert.ok(home.text.includes('earns a badge and OpenCoins'));
assert.ok(home.text.includes('OpenCoins into your OpenVibe.Network wallet'));
});
} finally { await on.close(); }