openvibers/openvibe.exchange Commits

The bar: six places, the rest under More; an even grid of doors on the home (#109)

OP OpenVibers committed 6 days ago · 1b3f32df85 · 4 files, +37 −16

Changes

ddiff --git a/public/css/exchange.css b/public/css/exchange.css
iindex 8e229fc..42274fe 100644
---- a/public/css/exchange.css
++++ b/public/css/exchange.css
@@@ -77,7 +77,9 @@
.ex-sec-body > .ovk-list > .ovk-row:first-child, .ex-sec-body > .ex-mlist > .ex-mrow:first-child { border-top: 0; }
.ex-lines { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; margin: 0 0 var(--ex-gap); }
--@media (min-width: 860px) { .ex-lines { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
++/* Eight doors: two columns, then four from 1120 px, so no card is left alone on a row (11-07). */
++@media (min-width: 720px) { .ex-lines { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
++@media (min-width: 1120px) { .ex-lines { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.ex-line {
display: flex; align-items: center; gap: 12px;
padding: 14px; border: 1px solid var(--ovk-line); border-radius: 16px; background: var(--ovk-panel);
ddiff --git a/server/render/layout.js b/server/render/layout.js
iindex ff0a61f..b2d74b8 100644
---- a/server/render/layout.js
++++ b/server/render/layout.js
@@@ -18,21 +18,22 @@ const NETWORK_URL = 'https://openvibe.network';
const SITE_NAME = 'OpenVibe.Exchange';
const TAGLINE = 'Every AI model, one account.';
const PUBLIC_DIR = path.join(__dirname, '..', '..', 'public');
++// Six places in the bar, the rest under More (11-07: fourteen links wrapped into two lines and cut the name to
++// "OpenVibe..."). Icons are the navbar's own glyphs; the brand is the way home.
const NAV = [
-- { label: 'Home', href: '/' },
-- { label: 'Models', href: '/models' },
-- { label: 'Chat', href: '/chat' },
-- { label: 'Arena', href: '/arena' },
-- { label: 'Rankings', href: '/rankings' },
-- { label: 'API', href: '/api' },
-- { label: 'Start', href: '/start' },
-- { label: 'Why this model', href: '/why' },
-- { label: 'Sell compute', href: '/sell' },
-- { label: 'Your endpoint', href: '/own' },
-- { label: 'Uncensored', href: '/uncensored' },
-- { label: 'AI in OpenVibe', href: '/ai' },
-- { label: 'Questions', href: '/faq' },
-- { label: 'Status', href: '/status' },
++ { label: 'Models', href: '/models', icon: 'fa-microchip' },
++ { label: 'Chat', href: '/chat', icon: 'fa-comments' },
++ { label: 'Arena', href: '/arena', icon: 'fa-hand-fist' },
++ { label: 'Rankings', href: '/rankings', icon: 'fa-chart-line' },
++ { label: 'Start building', href: '/start', icon: 'fa-bolt' },
++ { label: 'API', href: '/api', icon: 'fa-code' },
++ { label: 'Why this model', href: '/why', icon: 'fa-diagram-project', more: true },
++ { label: 'Your own endpoint', href: '/own', icon: 'fa-server', more: true },
++ { label: 'Sell compute', href: '/sell', icon: 'fa-microchip', more: true },
++ { label: 'Uncensored (18+)', href: '/uncensored', icon: 'fa-lock', more: true },
++ { label: 'AI in OpenVibe', href: '/ai', icon: 'fa-circle-nodes', more: true },
++ { label: 'Questions', href: '/faq', icon: 'fa-circle-info', more: true },
++ { label: 'Status', href: '/status', icon: 'fa-gauge', more: true },
];
const hashes = new Map();
@@@ -62,7 +63,7 @@ function renderPage(o) {
const nav = {
service: 'exchange',
apiBase: NETWORK_URL,
-- links: NAV.map((l) => ({ label: l.label, href: l.href, active: active(pathNow, l.href) })),
++ links: NAV.map((l) => ({ label: l.label, href: l.href, icon: l.icon, ...(l.more ? { more: true } : {}), active: active(pathNow, l.href) })),
history: { type: 'page', title: o.title || SITE_NAME },
silentLogin: `${o.config.baseUrl}/auth/login?silent=1&next={url}`,
sessionUrl: '/auth/me',
ddiff --git a/server/views/exchange.js b/server/views/exchange.js
iindex 032762f..2ffc3d8 100644
---- a/server/views/exchange.js
++++ b/server/views/exchange.js
@@@ -204,6 +204,7 @@ ${lineCard({ href: '/why', name: 'Why this model', text: 'Every answer carries i
${lineCard({ href: '/start', name: 'Start building', text: 'One key, one endpoint and the SDK you already use; four steps from a key to a receipt.', mark: mark('Qwen') })}
${lineCard({ href: '/sell', name: 'Run on people’s machines', text: 'Community GPUs through OpenVibe Node, labelled on every answer.', mark: mark('Qwen') })}
${lineCard({ href: '/api', name: 'Bring your own key, 0% fee', text: 'Your provider key pays the provider directly; the router never adds a fee on it.', mark: mark('GLM') })}
++${lineCard({ href: '/own', name: 'Your own endpoint', text: 'A private GPU for one model or your own weights: only you reach it, and it scales to zero when idle.', mark: mark('Llama') })}
</div>
${panel({
title: 'Use it from code', note: 'One endpoint, compatible with OpenAI and with Anthropic.', id: 'code', body: `
ddiff --git a/test/nav.test.js b/test/nav.test.js
nnew file mode 100644
iindex 0000000..fdb6b5c
---- /dev/null
++++ b/test/nav.test.js
@@@ -0,0 +1,17 @@
++'use strict';
++// The bar (11-07): fourteen links wrapped into two lines and cut the site's name to "OpenVibe…". Now at most six
++// places sit in the bar, each with an icon; everything else is under More, and every link is a page this site serves.
++const assert = require('assert');
++const { NAV } = require('../server/render/layout');
++const { PAGES } = (() => { try { return require('../server/http/discovery'); } catch { return {}; } })();
++
++const inBar = NAV.filter((l) => !l.more);
++assert.ok(inBar.length <= 6, `at most six places in the bar, not ${inBar.length}`);
++assert.ok(NAV.every((l) => /^fa-[a-z0-9-]+$/.test(l.icon || '')), 'every place has an icon');
++assert.ok(!NAV.some((l) => l.href === '/'), 'the brand is the way home');
++assert.deepStrictEqual(new Set(NAV.map((l) => l.href)).size, NAV.length, 'no place twice');
++for (const want of ['/models', '/chat', '/arena', '/start', '/api', '/why', '/own', '/sell', '/uncensored', '/ai', '/faq', '/status']) {
++ assert.ok(NAV.some((l) => l.href === want), `${want} is reachable from the bar or More`);
++}
++if (Array.isArray(PAGES)) for (const l of NAV) assert.ok(PAGES.some((p) => p.path === l.href) || ['/chat', '/status', '/arena', '/rankings'].includes(l.href), `${l.href} is a page`);
++console.log('nav: six places in the bar with icons, the rest under More, every page reachable');