openvibers/openvibe.network Commits

Front door: the five things people do now as a ranked list; Try next rows as doors (#834)

OP OpenVibers committed 2 days ago · ce8e14be14 · 4 files, +60 −10

The popular block (plan L43) was a plain list of names. Now each row has its place, the site's mark, the reason
Report gives and the number behind it: growth when it is rising (+44%, or 35x for a jump), else the week's opens.
A number Report did not send is left out, so the fallback list shows none. Rows on a phone, five cards on a computer.
/my: Try next rows end in a chevron and are a touch taller; a site's name under its icon wraps instead of being cut.
Perf budget: HTML 210.5 KB (measured 209.9).

Changes

ddiff --git a/public/css/my.css b/public/css/my.css
iindex d257daba..297a83db 100644
---- a/public/css/my.css
++++ b/public/css/my.css
@@@ -332,12 +332,15 @@ p.rc-quiet { margin: 0; color: var(--text-muted); }
.td-box { display: grid; align-content: start; gap: 12px; padding: 16px; min-width: 0; }
.td-sites { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
.td-site { display: grid; justify-items: center; gap: 6px; padding: 10px 4px; border-radius: 12px; border: 1px solid transparent; color: var(--text); text-decoration: none; font-size: 12px; font-weight: 650; min-width: 0; }
--.td-site span:last-child { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
++.td-site span:last-child { max-width: 100%; overflow-wrap: anywhere; text-align: center; line-height: 1.25; }
.td-site .td-site-ic { width: 40px; height: 40px; border-radius: 12px; font-size: 16px; }
.td-tries { display: grid; gap: 8px; }
.td-tries::before { content: "Try next"; font-size: 11.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--text-muted); }
--.td-try { display: flex; align-items: center; gap: 10px; padding: 10px; border-radius: 12px; border: 1px solid var(--border); color: var(--text); text-decoration: none; }
--.td-try-t { display: grid; gap: 1px; min-width: 0; }
++.td-try { display: flex; align-items: center; gap: 12px; min-height: 56px; padding: 8px 14px 8px 8px; border-radius: 12px; border: 1px solid var(--border); background: color-mix(in srgb, var(--accent) 4%, transparent); color: var(--text); text-decoration: none; }
++/* Try next (Opus, 2026-11-11): a row you can tell is a door, the chevron at its end. */
++.td-try::after { content: ""; flex: none; width: 7px; height: 7px; margin-left: auto; border-top: 2px solid var(--text-muted); border-right: 2px solid var(--text-muted); transform: rotate(45deg); }
++.td-try:hover::after { border-color: var(--accent); }
++.td-try-t { display: grid; gap: 1px; min-width: 0; flex: 1; }
.td-try-t b { font-size: 13.5px; }
.td-try-t small { font-size: 12px; color: var(--text-muted); }
.td-bal { display: flex; align-items: baseline; gap: 8px; }
ddiff --git a/server/home/render.js b/server/home/render.js
iindex cfa0e4fa..3e13f26b 100644
---- a/server/home/render.js
++++ b/server/home/render.js
@@@ -208,9 +208,26 @@ function moreParts(parts) {
return `<div class="more"><h2 class="more-pick-h">${sectionIcon('grid')}More about OpenVibe</h2><nav class="more-pick" data-ovk-scroll aria-label="More about OpenVibe">${chips}</nav>\n${shown.map(([, , html]) => html.replace(/^<section class="([^"]*)" id="([^"]*)"/, '<section class="$1" id="$2" data-more-part')).join('\n')}</div>`;
}
--// The five things people do now (plan L43): a plain list, no new styles. The order and the numbers are Report's;
--// when Report does not answer the fixed starter list is used and the section says so in data-source. Opus places and
--// styles the block; this only renders it.
++// The five things people do now (plan L43; designed by Opus 2026-11-11): a ranked list, each row its place, the site's
++// mark, the reason Report gives and the number behind it (growth when it is rising, else the week's opens; a number
++// Report did not send is left out). The order and the numbers are Report's; when Report does not answer the fixed
++// starter list is used and the section says so in data-source. A phone reads it as rows, a computer as five cards.
++const POP_ICON = { 'media-hub': 'fa-play', video: 'fa-play', tours: 'fa-route', community: 'fa-users', news: 'fa-newspaper',
++ design: 'fa-palette', report: 'fa-chart-column', network: 'fa-circle-nodes', music: 'fa-music', trade: 'fa-chart-line',
++ coupons: 'fa-ticket', deals: 'fa-tags', download: 'fa-folder', cam: 'fa-camera', openre: 'fa-share-nodes', host: 'fa-server',
++ reviews: 'fa-star', media: 'fa-photo-film', exchange: 'fa-arrow-right-arrow-left', compare: 'fa-scale-balanced' };
++const popIcon = (it) => POP_ICON[it.id] || POP_ICON[it.icon] || (INTENTS.find((x) => x[0] === it.id || x[0] === it.icon) || [])[1] || 'fa-circle-nodes';
++/** The number behind a row: "+44%" or "35×" this week when it is rising, else the week's opens; '' when Report sent none. */
++function popFigure(it) {
++ const g = Number(it.growth_7d_pct);
++ if (/rising/i.test(it.reason) && Number.isFinite(g) && g > 0) {
++ const big = g >= 200 && Number(it.opens_prev_7d) > 0;
++ const v = big ? `${Math.round(Number(it.opens_7d) / Number(it.opens_prev_7d))}×` : `+${g}%`;
++ return `<span class="pop-n is-up" title="${esc(big ? `${v} last week's opens` : `${v} on last week`)}"><i class="fa-solid fa-arrow-trend-up" aria-hidden="true"></i>${esc(v)}</span>`;
++ }
++ const n = Number(it.opens_7d);
++ return Number.isFinite(n) && n > 0 ? `<span class="pop-n" title="${esc(`${n.toLocaleString('en-US')} opens this week`)}">${esc(fmtVisits(n))}<small>this week</small></span>` : '';
++}
function popularNow() {
const p = homePopular.peek();
const items = Array.isArray(p.items) ? p.items.slice(0, 5) : [];
@@@ -218,7 +235,10 @@ function popularNow() {
const lede = p.source === 'report'
? "Ranked from OpenVibe.Report's numbers: what people opened today and this week."
: 'A fixed starter list while OpenVibe.Report is away.';
-- return `<section class="sc-sec" id="popular" data-source="${esc(p.source)}" aria-labelledby="h-popular"><h2 id="h-popular" class="ovk-titled">${sectionIcon('grid')}Five things people do now</h2><p class="sc-lede">${esc(lede)}</p><ul class="now-list">${items.map((it) => `<li><a href="${esc(it.url)}"><b>${esc(it.name)}</b><small>${esc(it.reason)}</small></a></li>`).join('')}</ul></section>`;
++ const row = (it, i) => `<li><a class="pop-row" href="${esc(it.url)}"><span class="pop-rank" aria-hidden="true">${i + 1}</span>`
++ + `<span class="pop-ic" aria-hidden="true"><i class="fa-solid ${esc(popIcon(it))}"></i></span>`
++ + `<span class="pop-t"><b>${esc(it.name)}</b><small>${esc(it.reason)}</small></span>${popFigure(it)}</a></li>`;
++ return `<section class="sc-sec pop" id="popular" data-source="${esc(p.source)}" aria-labelledby="h-popular"><h2 id="h-popular" class="ovk-titled">${sectionIcon('grid')}Five things people do now</h2><p class="sc-lede">${esc(lede)}</p><ol class="pop-list">${items.map(row).join('')}</ol></section>`;
}
// The hero's numbers: only what Network read itself within the last minute (server/home/activity.js); a number it
@@@ -347,6 +367,31 @@ const CSS = `
}
.now-code{display:inline-block;margin:0 0 3px;padding:1px 7px;border-radius:6px;border:1px dashed var(--accent,#3b82f6);color:var(--accent-light,#60a5fa);font:700 12.5px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;letter-spacing:.3px}
.now-list b{overflow-wrap:anywhere}
++.pop-list{list-style:none;margin:14px 0 0;padding:0;display:grid;gap:8px;counter-reset:none}
++.pop-row{display:grid;grid-template-columns:auto auto minmax(0,1fr) auto;align-items:center;gap:12px;min-height:60px;padding:10px 14px 10px 10px;border-radius:14px;border:1px solid var(--border,rgba(255,255,255,.08));background:color-mix(in srgb,var(--bg-secondary,#101828) 80%,transparent);color:inherit;text-decoration:none;transition:border-color .15s ease,transform .15s ease}
++.pop-row:hover,.pop-row:focus-visible{border-color:var(--accent,#3b82f6)}
++.pop-row:focus-visible{outline:2px solid var(--accent,#3b82f6);outline-offset:2px}
++.pop-rank{display:grid;place-items:center;width:24px;font-size:13px;font-weight:800;color:var(--text-muted,#7d8aa0);font-variant-numeric:tabular-nums}
++.pop-list li:first-child .pop-rank{color:var(--accent-light,#60a5fa)}
++.pop-ic{display:grid;place-items:center;width:40px;height:40px;border-radius:12px;background:color-mix(in srgb,var(--accent,#3b82f6) 14%,transparent);color:var(--accent-light,#60a5fa);font-size:16px}
++.pop-t{display:grid;gap:2px;min-width:0}
++.pop-t b{font-size:14.5px;line-height:1.3;overflow-wrap:anywhere}
++.pop-t small{font-size:12.5px;line-height:1.35;color:var(--text-secondary,#a8b3c4)}
++.pop-n{display:grid;justify-items:end;gap:0;font-size:14px;font-weight:750;font-variant-numeric:tabular-nums;color:var(--text-primary,#e6edf7);white-space:nowrap}
++.pop-n small{font-size:11px;font-weight:500;color:var(--text-muted,#7d8aa0)}
++.pop-n.is-up{display:inline-flex;align-items:center;gap:5px;color:var(--success,#22c55e)}
++.pop-n.is-up i{font-size:12px}
++@media (min-width:861px){
++.pop-list{grid-template-columns:repeat(5,minmax(0,1fr));gap:12px}
++.pop-row{grid-template-columns:auto minmax(0,1fr);grid-template-rows:auto auto 1fr;align-items:start;gap:10px 10px;min-height:0;height:100%;padding:14px;position:relative}
++.pop-row:hover{transform:translateY(-2px)}
++.pop-rank{position:absolute;top:12px;right:12px;width:auto}
++.pop-ic{grid-column:1;grid-row:1}
++.pop-t{grid-column:1/-1;grid-row:2}
++.pop-n{grid-column:1/-1;grid-row:3;justify-items:start;align-self:end}
++.pop-n.is-up{justify-content:flex-start}
++}
++@media (prefers-reduced-motion:reduce){.pop-row{transition:none}.pop-row:hover{transform:none}}
.net-num{margin-top:18px;padding:16px 18px;border-radius:16px;border:1px solid var(--border,rgba(255,255,255,.08));background:color-mix(in srgb,var(--bg-secondary,#101828) 85%,transparent)}
.net-num h2{font-size:12px;font-weight:800;letter-spacing:.7px;text-transform:uppercase;color:var(--text-muted,#7d8aa0);margin:0 0 10px}
.net-num ul{list-style:none;margin:0;padding:0;display:grid;gap:8px;grid-template-columns:repeat(auto-fill,minmax(min(140px,100%),1fr))}
ddiff --git a/test/home-front-door.test.js b/test/home-front-door.test.js
iindex 19a01ba7..4aec3a6e 100644
---- a/test/home-front-door.test.js
++++ b/test/home-front-door.test.js
@@@ -92,8 +92,10 @@ for (const [id, , verb, , href] of home.INTENTS) {
}
assert.ok(/Go live<\/b><small>From a browser tab\. No OBS, no follower minimum\./.test(html), 'browser go-live stays the headline promise');
// The five most popular list (plan L43) is a plain list; with no Report wired in this process it says it is the floor.
--assert.ok(/<section class="sc-sec" id="popular" data-source="fallback"/.test(html), 'the signed-out popularity list is on the page and names its source');
--assert.ok(html.includes('Five things people do now') && /<ul class="now-list">/.test(html), 'the list is plain, with the existing classes');
++assert.ok(/<section class="sc-sec pop" id="popular" data-source="fallback"/.test(html), 'the signed-out popularity list is on the page and names its source');
++assert.ok(html.includes('Five things people do now') && /<ol class="pop-list">/.test(html), 'the five are a ranked list');
++assert.ok(/<a class="pop-row" href="https:\/\/openvibe\.live\/"><span class="pop-rank" aria-hidden="true">1<\/span><span class="pop-ic" aria-hidden="true"><i class="fa-solid fa-tower-broadcast"><\/i><\/span>/.test(html), 'each row: its place, the site\'s mark');
++assert.ok(!/class="pop-n/.test(html.slice(html.indexOf('id="popular"'), html.indexOf('</ol>', html.indexOf('id="popular"')))), 'the fallback list has no numbers, so it shows none');
assert.ok(/Nobody is live right now/.test(html) && /Go live from your browser/.test(html), 'an honest empty state');
const open = sites.filter((s) => s.status === 'open' && s.id !== 'network');
// The hero's orbit (2026-10-28): the most used sites around the mark, the count of every site in its middle.
ddiff --git a/test/perf-budget.test.js b/test/perf-budget.test.js
iindex 45218689..daeb6126 100644
---- a/test/perf-budget.test.js
++++ b/test/perf-budget.test.js
@@@ -12,7 +12,7 @@ const { spawn } = require('child_process');
const { measure, check, format } = require('openvibe-shared/perf-budget');
const BUDGETS = {
-- htmlRawKB: 209.5, // 209.5: measured 209 KB with openvibe-shared 4.190.0 (2026-11-10: the site name's dropdown, the footer's online strip, the balances pill); 206: measured 205.8 KB with openvibe-shared 4.185.0 (the navbar's links in the first HTML since 4.184.0), 2026-11-10; 205.4: measured 205.4 KB with the front door's five most popular destinations (plan L43, 2026-11-09: Report's ranked list adds about 1.2 KB of plain list markup); 204.2: measured 204.2 KB with openvibe-contracts 0.373.0 (the Git service in the network list) on openvibe-shared 4.173.0, 2026-11-09; 204: measured 203.5 KB with the shared page head's kit CSS on the home (openvibe-shared 4.163.0 + page head 1369721), 2026-11-09; 198.8 with openvibe-contracts 0.348.0 (2026-11-08: Studio joins the menu; Studio and Locker get menu lines); 197.4 with Email open (contracts 0.330.0, 2026-11-07: the mail site in the network list); 196 with the Node's own item in the navbar (openvibe-shared 4.117.0, 2026-11-06: the Node link and the llms.txt section); 194.9 with the Organization naming every open site in sameAs (SEO-27, 2026-11-03); 192.4 with Tours and Shoes open (contracts 0.265.0, 2026-10-31); 189.9 with Casino open (contracts 0.264.0, 2026-10-31); 189 with the look-boot script inline above the stylesheets on every page (L42, 2026-10-31: +1 KB); 188 with the agent layer's rel=alternate/describedby link tags on every public page (SEO-1/SEO-2, 2026-10-29: +0.2 KB); 187 with Bench open (contracts 0.238.0, 2026-10-28); 186 with Cam open (contracts 0.237.0, 2026-10-28); 184.4 with Fun open (contracts 0.236.0, 2026-10-28); 182.7 with Vision and Exchange open (contracts 0.235.0, 2026-10-28); 179 with Meme open (contracts 0.234.0, 2026-10-28); 174.1 with Domains, Report and Design open (contracts 0.226-0.228, 2026-10-28); 168.1 the hero's orbit and rich cards for live streams, recordings and screenshots (2026-10-28: the owner asked for animated visuals and YouTube-style cards); 152.4 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 147.9 with the home's FAQ (six questions and their FAQPage JSON-LD), the numbers panel and the service map's group headings (2026-10-22); 141.2 with Tips and VIP open (contracts 0.200.0, 37 sites; the Opening next section is gone); 134 with Play (contracts 0.172.1); 128.3 with Events, AI and Search added (31 sites, contracts 0.135.0); 122.3 (shared 2.21.0: a real glyph for every product card, where ten drew the 'V'); was 110.6 with 33 live sites (contracts 0.119.0). The home page lists every site, so it grows with the network
++ htmlRawKB: 210.5, // 210.5: measured 209.9 KB with the popular block designed (2026-11-11: ranks, marks, numbers and its CSS, +0.9 KB); 209.5: measured 209 KB with openvibe-shared 4.190.0 (2026-11-10: the site name's dropdown, the footer's online strip, the balances pill); 206: measured 205.8 KB with openvibe-shared 4.185.0 (the navbar's links in the first HTML since 4.184.0), 2026-11-10; 205.4: measured 205.4 KB with the front door's five most popular destinations (plan L43, 2026-11-09: Report's ranked list adds about 1.2 KB of plain list markup); 204.2: measured 204.2 KB with openvibe-contracts 0.373.0 (the Git service in the network list) on openvibe-shared 4.173.0, 2026-11-09; 204: measured 203.5 KB with the shared page head's kit CSS on the home (openvibe-shared 4.163.0 + page head 1369721), 2026-11-09; 198.8 with openvibe-contracts 0.348.0 (2026-11-08: Studio joins the menu; Studio and Locker get menu lines); 197.4 with Email open (contracts 0.330.0, 2026-11-07: the mail site in the network list); 196 with the Node's own item in the navbar (openvibe-shared 4.117.0, 2026-11-06: the Node link and the llms.txt section); 194.9 with the Organization naming every open site in sameAs (SEO-27, 2026-11-03); 192.4 with Tours and Shoes open (contracts 0.265.0, 2026-10-31); 189.9 with Casino open (contracts 0.264.0, 2026-10-31); 189 with the look-boot script inline above the stylesheets on every page (L42, 2026-10-31: +1 KB); 188 with the agent layer's rel=alternate/describedby link tags on every public page (SEO-1/SEO-2, 2026-10-29: +0.2 KB); 187 with Bench open (contracts 0.238.0, 2026-10-28); 186 with Cam open (contracts 0.237.0, 2026-10-28); 184.4 with Fun open (contracts 0.236.0, 2026-10-28); 182.7 with Vision and Exchange open (contracts 0.235.0, 2026-10-28); 179 with Meme open (contracts 0.234.0, 2026-10-28); 174.1 with Domains, Report and Design open (contracts 0.226-0.228, 2026-10-28); 168.1 the hero's orbit and rich cards for live streams, recordings and screenshots (2026-10-28: the owner asked for animated visuals and YouTube-style cards); 152.4 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 147.9 with the home's FAQ (six questions and their FAQPage JSON-LD), the numbers panel and the service map's group headings (2026-10-22); 141.2 with Tips and VIP open (contracts 0.200.0, 37 sites; the Opening next section is gone); 134 with Play (contracts 0.172.1); 128.3 with Events, AI and Search added (31 sites, contracts 0.135.0); 122.3 (shared 2.21.0: a real glyph for every product card, where ten drew the 'V'); was 110.6 with 33 live sites (contracts 0.119.0). The home page lists every site, so it grows with the network
htmlBrotliKB: 39.5, // 39.5: measured 39.3 KB brotli with openvibe-shared 4.190.0 (2026-11-10: the site name's dropdown, the footer's online strip, the balances pill); 39: measured 38.6 KB brotli with the shared page head's kit CSS on the home (openvibe-shared 4.163.0 + page head 1369721), 2026-11-09; 38.1 with openvibe-contracts 0.348.0 (2026-11-08: Studio joins the menu; Studio and Locker get menu lines); 37.9 with openvibe-shared 4.129.0 (2026-11-07: the one menu (the burger opens the OpenVibe menu)); 37.8 with openvibe-shared 4.129.0 (2026-11-07: the one menu (the burger opens the OpenVibe menu)); 36.1 with the Node's own item in the navbar (openvibe-shared 4.117.0, 2026-11-06); 35.5 with Tours and Shoes open (2026-10-31); 35.1 with the look-boot script inline above the stylesheets on every page (L42, 2026-10-31: +0.6 KB); 34.2 with the lower half as one block of chips on phones (2026-10-30); 33.1 with Games granted its Bench match capabilities (BENCH-7, 2026-10-29: the home lists what each service may do); 32.0 with Fun, Vision and Exchange open (contracts 0.236.0, 2026-10-28); 31.2 with Domains, Report and Design open (2026-10-28); 30.1 with the Organization saying who OpenVibe is (description, other names, what it is not, 15 sites; 2026-10-28); 28.5 with the FAQ, numbers and groups (2026-10-22); 26.1 with Play; 25.1 (31 sites, contracts 0.135.0); 24.1 (shared 2.21.0, the same glyphs); 20.5 (contracts 0.116.0)
jsFiles: 8, // 7 with boost.js, was 6 (2026-10-13, plan T11 step 3: one more script on every boosted page; it loads route-transition.js on the first move only)
jsRawKB: 448.5, // 448.5 openvibe-shared v4.205.0 (2026-11-11: measured 448.4, raised by pin-bump from 445); 445 openvibe-shared v4.202.0 (2026-11-11: measured 444.6, raised by pin-bump from 444); 444 openvibe-shared v4.201.0 (2026-11-11: measured 443.7, raised by pin-bump from 440.5); 440.5 openvibe-shared v4.200.0 (2026-11-10: measured 440.2, raised by pin-bump from 440.0); 440.0: measured 439.6 KB raw / 106.7 KB brotli with openvibe-shared 4.197.0 (2026-11-10: the menu's row of places, the You tab's row, the Sign in mark, the tooltip); 438.5: measured 438 KB raw / 106.5 KB brotli with openvibe-shared 4.190.0 (2026-11-10: the site name's dropdown, the footer's online strip, the balances pill); 421: measured 420.9 KB raw / 102.2 KB brotli with openvibe-shared 4.186.0 (the mark's Working and Thinking states, the Dock and the menu as one sheet, bare controls), 2026-11-10; 420.5: measured 419.9 KB raw / 101.9 KB brotli with openvibe-shared 4.185.0 (Calm reduces, the solids and site marks, the You tab, the Dock out of the orb), 2026-11-10; 417: measured 416.4 KB raw / 100.9 KB brotli with openvibe-shared 4.179.0, 2026-11-09; 410: measured 409.2 KB raw / 99.4 KB brotli with openvibe-shared 4.173.0 (the wallet's cookie mode), 2026-11-09; 409: measured 408.1 KB raw / 99.0 KB brotli after merging main's Frame feed work (openvibe-shared 4.167.0), 2026-11-09; 406: measured 405.7 KB raw / 98.4 KB brotli with the shared page head's kit script on the home (openvibe-shared 4.163.0 + page head 1369721), 2026-11-09; 381.8: measured 375.8 KB raw / 91.1 KB brotli with openvibe-shared 4.159.0, plus room for 4.160.0–4.161.0 (the navbar's slots loader, the keyboard watcher, the pop motion), 2026-11-09; 373 with openvibe-shared 4.153.0 (2026-11-08: signed in to the network, signed in here, on phones too; a page can turn away a carried card); 372.3 with openvibe-shared 4.152.0 (2026-11-08: the menu marks the page you are on and opens without the keyboard on a phone, floating cards follow you across OpenVibe sites (overlay.js), and the navbar preconnects to the next OpenVibe site on intent); 369.6 with openvibe-shared 4.150.0 (2026-11-08: the bell opens the Dock on Activity, the Dock's phone header is in two rows, and the menu's orb is the close button with an open/close animation); 368.6 with openvibe-shared 4.149.0 (2026-11-08: the menu has tabs at the top; the phone's menu button is the orb of 4.148.0); 366.5 with openvibe-shared 4.118.0 (2026-11-06: the Locker re-auth link, the 44 px footer and TLD targets; main's 4.117.0 navbar item and the account-safety helper included); about 363.8 with both (2026-11-06: openvibe-shared 4.117.0's featured navbar item, +1.7 KB, and the account-safety helper, +8.5 KB; merged, to be confirmed by CI's measure); 353.6 with the account-safety helper on every page (2026-11-06: the one re-authentication path and the scheduled-deletion banner, +8.5 KB); 345.1 with openvibe-shared 4.114.0 (2026-11-06: the one Share sheet in the navbar (ov-share.js and ov-qr.js load on the first press); navbar.js gains its click handler); 343.9 with openvibe-shared 4.112.1 (2026-11-06: theme-loader.js (4.111.1) and the footer/appbar phone CSS (4.112.1)); 342.3 with openvibe-shared 4.109.2 (2026-11-03: OpenVibe's own select, the navbar loads ov-forms); 339.6 with openvibe-shared 4.107.0 (2026-11-03: open.<tld> sites are named for their domain; the brand knows its front door); 337.8 with openvibe-shared 4.103.0 (2026-11-02: the puzzle glyph, two-line sidebar rows, boost lets parsed pages go, eight solids) (2026-11-02); 334.1 openvibe-shared 4.70.0 (2026-11-01: the app bar, busy(), skeletons, view transitions); 333.2 with openvibe-shared 4.53.0: the footer's columns fold on phones (2026-10-30); 330.1 with openvibe-shared 4.52.0 (from 4.47.2): the seasonal fall, rich embeds, the Dock and its Chats tab (2026-10-30); 323.2 with openvibe-shared 4.47.2 (the comfort settings in theme-loader 4.45, touch targets 4.46, the grouped updates log 4.47.2); 313.7 with openvibe-shared 4.43.0 (room.css carries the kit tokens, 4.39.1; the kit's 4.39.0 rules); 306.4 openvibe-shared 4.25.0: the menu's orb beside the brand, both balances on phones, messages, the account sheet on phones and the bar stepping away on phones; 298.9 openvibe-shared 4.22.0: the navbar's magnifier search button and More dropdown (search.js and loading.js load only when used); 293.1 Shared 4.17.0: OpenCoins and Vibes chips (wallet: true) in the navbar; 279.1 openvibe-shared 4.15 footer: the compact footer opens into the whole one, and the pulse line; 269.3 with shared 4.11.0's seasonal navbar (Pumpkin Night and the season badge, +2.6); 249.8 with boost.js (13.1); 228.3